From 9a7ed5021f89fc9a4e6d75a0dee0e17d06d18021 Mon Sep 17 00:00:00 2001 From: intoxicated Date: Tue, 14 Nov 2017 16:41:11 +0900 Subject: [PATCH 1/2] Implemented typing feature --- CHANGELOG.md | 4 + CHPlugin.podspec | 2 +- CHPlugin.xcodeproj/project.pbxproj | 28 +- .../xcschemes/xcschememanagement.plist | 2 +- .../UserInterfaceState.xcuserstate | Bin 13875 -> 162821 bytes .../typing.imageset/Contents.json | 23 ++ .../typing.imageset/typing.gif | Bin 0 -> 4977 bytes .../typing.imageset/typing@2x.gif | Bin 0 -> 4977 bytes .../typing.imageset/typing@3x.gif | Bin 0 -> 6934 bytes CHPlugin/Info.plist | 2 +- .../Source/ChannelPlugin/ChannelPlugin.swift | 10 +- .../Controllers/UserChatViewController.swift | 283 +++++++++++++----- .../Controllers/UserChatsViewController.swift | 2 +- CHPlugin/Source/Models/CHAssets.swift | 16 + CHPlugin/Source/Models/CHManager.swift | 6 + CHPlugin/Source/Models/CHTypingEntity.swift | 58 ++++ CHPlugin/Source/Models/CHi18n.swift | 3 +- .../Source/Selectors/PersonSelector.swift | 5 +- CHPlugin/Source/Services/WsService.swift | 86 +++++- CHPlugin/Source/Utils/CHAnimations.swift | 76 +++++ CHPlugin/Source/Utils/CHUtils.swift | 63 +++- CHPlugin/Source/Views/CHMultiAvatarView.swift | 137 +++++++++ .../Views/Cells/TypingIndicatorCell.swift | 92 ++++++ .../ChatNotificationView.swift | 0 .../{ => DialogView}/DialogActionView.swift | 0 .../project.pbxproj | 8 +- 26 files changed, 789 insertions(+), 117 deletions(-) create mode 100644 CHPlugin/Assets/Images.xcassets/typing.imageset/Contents.json create mode 100644 CHPlugin/Assets/Images.xcassets/typing.imageset/typing.gif create mode 100644 CHPlugin/Assets/Images.xcassets/typing.imageset/typing@2x.gif create mode 100644 CHPlugin/Assets/Images.xcassets/typing.imageset/typing@3x.gif create mode 100644 CHPlugin/Source/Models/CHTypingEntity.swift create mode 100644 CHPlugin/Source/Utils/CHAnimations.swift create mode 100644 CHPlugin/Source/Views/CHMultiAvatarView.swift create mode 100644 CHPlugin/Source/Views/Cells/TypingIndicatorCell.swift rename CHPlugin/Source/Views/{ => ChatNotificationView}/ChatNotificationView.swift (100%) rename CHPlugin/Source/Views/{ => DialogView}/DialogActionView.swift (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index d961c6c8..051c69b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +### 2.3.0 +#### Update +* Added live typing indicator + ### 2.2.4 * SwiftyJSON 4.0 migration * Rolled back to deployment target 8.0 diff --git a/CHPlugin.podspec b/CHPlugin.podspec index bd0c66cf..00acfc59 100644 --- a/CHPlugin.podspec +++ b/CHPlugin.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'CHPlugin' - s.version = '2.2.5' + s.version = '2.3.0' s.summary = 'Channel plugin for iOS' # This description is used to generate tags and improve search results. # * Think: What does it do? Why did you write it? What is the focus? diff --git a/CHPlugin.xcodeproj/project.pbxproj b/CHPlugin.xcodeproj/project.pbxproj index 33174983..538be95a 100644 --- a/CHPlugin.xcodeproj/project.pbxproj +++ b/CHPlugin.xcodeproj/project.pbxproj @@ -17,6 +17,10 @@ 14D2AA831E24AE07006FEE22 /* CHPluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14D2AA821E24AE07006FEE22 /* CHPluginTests.swift */; }; 14D2AA851E24AE07006FEE22 /* CHPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 14D2AA771E24AE06006FEE22 /* CHPlugin.h */; settings = {ATTRIBUTES = (Public, ); }; }; 14DFAC681E4A189D00130119 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 14DFAC671E4A189D00130119 /* Images.xcassets */; }; + 222086B81FBAAD37002CFA88 /* CHAnimations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 222086B71FBAAD37002CFA88 /* CHAnimations.swift */; }; + 222086BA1FBAB28D002CFA88 /* CHTypingEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 222086B91FBAB28D002CFA88 /* CHTypingEntity.swift */; }; + 22448B061FBA9C1900EDE528 /* TypingIndicatorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22448B051FBA9C1900EDE528 /* TypingIndicatorCell.swift */; }; + 22448B081FBA9D5800EDE528 /* CHMultiAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22448B071FBA9D5800EDE528 /* CHMultiAvatarView.swift */; }; 225B05F81E4ED913001DE109 /* WsServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225B05F71E4ED913001DE109 /* WsServiceTests.swift */; }; 225B05FA1E4ED9F5001DE109 /* ChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225B05F91E4ED9F5001DE109 /* ChannelTests.swift */; }; 225B05FE1E4EDA14001DE109 /* GuestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225B05FD1E4EDA14001DE109 /* GuestTests.swift */; }; @@ -227,6 +231,10 @@ 14D2AA821E24AE07006FEE22 /* CHPluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CHPluginTests.swift; sourceTree = ""; }; 14D2AA841E24AE07006FEE22 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 14DFAC671E4A189D00130119 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Assets/Images.xcassets; sourceTree = ""; }; + 222086B71FBAAD37002CFA88 /* CHAnimations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CHAnimations.swift; sourceTree = ""; }; + 222086B91FBAB28D002CFA88 /* CHTypingEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CHTypingEntity.swift; sourceTree = ""; }; + 22448B051FBA9C1900EDE528 /* TypingIndicatorCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingIndicatorCell.swift; sourceTree = ""; }; + 22448B071FBA9D5800EDE528 /* CHMultiAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CHMultiAvatarView.swift; sourceTree = ""; }; 225B05F71E4ED913001DE109 /* WsServiceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WsServiceTests.swift; sourceTree = ""; }; 225B05F91E4ED9F5001DE109 /* ChannelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelTests.swift; sourceTree = ""; }; 225B05FB1E4EDA00001DE109 /* MessageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageTests.swift; sourceTree = ""; }; @@ -634,6 +642,7 @@ 659792B61FA7167A00840F6A /* CustomTransform.swift */, 659792B71FA7167A00840F6A /* LocalMessageFactory.swift */, 659792B81FA7167A00840F6A /* PrefStore.swift */, + 222086B71FBAAD37002CFA88 /* CHAnimations.swift */, ); name = Utils; path = Source/Utils; @@ -714,6 +723,7 @@ 659792EB1FA7167B00840F6A /* DataTransferObject.swift */, 659792EC1FA7167B00840F6A /* ModelType.swift */, 6556A29B1FA821D300B80F4E /* CHError.swift */, + 222086B91FBAB28D002CFA88 /* CHTypingEntity.swift */, ); name = Models; path = Source/Models; @@ -778,7 +788,6 @@ 6597930B1FA7167C00840F6A /* Cells */, 6597931B1FA7167C00840F6A /* ChatBannerView.swift */, 6597931C1FA7167C00840F6A /* ChatNotificationView */, - 6597931E1FA7167C00840F6A /* ChatNotificationView.swift */, 6597931F1FA7167C00840F6A /* CHAvatar.swift */, 659793201FA7167C00840F6A /* CHMBubbleView.swift */, 659793211FA7167C00840F6A /* CHMFileView.swift */, @@ -787,11 +796,9 @@ 659793241FA7167C00840F6A /* CHPhoneField.swift */, 659793251FA7167C00840F6A /* CHTextField.swift */, 659793261FA7167C00840F6A /* CountryCodePickerView.swift */, - 659793271FA7167C00840F6A /* DialogActionView.swift */, 659793281FA7167C00840F6A /* DialogView */, 6597932B1FA7167C00840F6A /* ErrorToastView.swift */, 6597932C1FA7167C00840F6A /* LauncherView */, - 6597932F1FA7167C00840F6A /* MessageViews */, 659793301FA7167C00840F6A /* MultiAvatarView.swift */, 659793311FA7167C00840F6A /* NavigationItem.swift */, 659793321FA7167C00840F6A /* NewChatView.swift */, @@ -799,6 +806,7 @@ 659793341FA7167C00840F6A /* ProfileView */, 6597933A1FA7167C00840F6A /* TextActionView.swift */, 6597933B1FA7167C00840F6A /* UserChatsEmptyView.swift */, + 22448B071FBA9D5800EDE528 /* CHMultiAvatarView.swift */, ); name = Views; path = Source/Views; @@ -824,6 +832,7 @@ 6597930F1FA7167C00840F6A /* MessageCell */, 659793121FA7167C00840F6A /* NewMessageDividerCell.swift */, 659793131FA7167C00840F6A /* SatisfactionCompleteCell.swift */, + 22448B051FBA9C1900EDE528 /* TypingIndicatorCell.swift */, 659793141FA7167C00840F6A /* SatisfactionFeedbackCell.swift */, 659793151FA7167C00840F6A /* SwitchCell.swift */, 659793161FA7167C00840F6A /* TextInputCell.swift */, @@ -854,6 +863,7 @@ 6597931C1FA7167C00840F6A /* ChatNotificationView */ = { isa = PBXGroup; children = ( + 6597931E1FA7167C00840F6A /* ChatNotificationView.swift */, 6597931D1FA7167C00840F6A /* ChatNotificationViewModel.swift */, ); path = ChatNotificationView; @@ -863,6 +873,7 @@ isa = PBXGroup; children = ( 659793291FA7167C00840F6A /* DialogView.swift */, + 659793271FA7167C00840F6A /* DialogActionView.swift */, 6597932A1FA7167C00840F6A /* DialogViewModel.swift */, ); path = DialogView; @@ -877,13 +888,6 @@ path = LauncherView; sourceTree = ""; }; - 6597932F1FA7167C00840F6A /* MessageViews */ = { - isa = PBXGroup; - children = ( - ); - path = MessageViews; - sourceTree = ""; - }; 659793341FA7167C00840F6A /* ProfileView */ = { isa = PBXGroup; children = ( @@ -1242,6 +1246,7 @@ 6597935E1FA716CB00840F6A /* CRToast+Extensions.swift in Sources */, 6597935F1FA716CB00840F6A /* Date+Extensions.swift in Sources */, 659793601FA716CB00840F6A /* String+BoundingRect.swift in Sources */, + 22448B081FBA9D5800EDE528 /* CHMultiAvatarView.swift in Sources */, 659793611FA716CB00840F6A /* String+Utils.swift in Sources */, 659793621FA716CB00840F6A /* UIButton+Extensions.swift in Sources */, 659793631FA716CB00840F6A /* UIDevice+Extenions.swift in Sources */, @@ -1280,6 +1285,7 @@ 659793831FA716CB00840F6A /* DataTransferObject.swift in Sources */, 659793841FA716CB00840F6A /* ModelType.swift in Sources */, 659793861FA716CB00840F6A /* EventPromise.swift in Sources */, + 222086B81FBAAD37002CFA88 /* CHAnimations.swift in Sources */, 659793871FA716CB00840F6A /* GuestPromise.swift in Sources */, 659793881FA716CB00840F6A /* PluginPromise.swift in Sources */, 659793891FA716CB00840F6A /* ScriptPromise.swift in Sources */, @@ -1292,6 +1298,7 @@ 659793901FA716CB00840F6A /* MessagesReducer.swift in Sources */, 659793911FA716CB00840F6A /* PluginReducer.swift in Sources */, 659793921FA716CB00840F6A /* PushReducer.swift in Sources */, + 222086BA1FBAB28D002CFA88 /* CHTypingEntity.swift in Sources */, 659793931FA716CB00840F6A /* ScriptsReducer.swift in Sources */, 659793941FA716CB00840F6A /* SessionsReducer.swift in Sources */, 659793951FA716CB00840F6A /* UIReducer.swift in Sources */, @@ -1310,6 +1317,7 @@ 659793A21FA716CB00840F6A /* AvatarView.swift in Sources */, 659793A31FA716CB00840F6A /* Badge.swift in Sources */, 659793A41FA716CB00840F6A /* BaseButton.swift in Sources */, + 22448B061FBA9C1900EDE528 /* TypingIndicatorCell.swift in Sources */, 659793A51FA716CB00840F6A /* BaseTableViewCell.swift in Sources */, 659793A61FA716CB00840F6A /* BaseView.swift in Sources */, 659793A71FA716CB00840F6A /* NeverClearView.swift in Sources */, diff --git a/CHPlugin.xcodeproj/xcuserdata/R3alFr3e.xcuserdatad/xcschemes/xcschememanagement.plist b/CHPlugin.xcodeproj/xcuserdata/R3alFr3e.xcuserdatad/xcschemes/xcschememanagement.plist index 9d54cd28..b1eafc12 100644 --- a/CHPlugin.xcodeproj/xcuserdata/R3alFr3e.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/CHPlugin.xcodeproj/xcuserdata/R3alFr3e.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ CHPlugin.xcscheme orderHint - 32 + 31 diff --git a/CHPlugin.xcworkspace/xcuserdata/R3alFr3e.xcuserdatad/UserInterfaceState.xcuserstate b/CHPlugin.xcworkspace/xcuserdata/R3alFr3e.xcuserdatad/UserInterfaceState.xcuserstate index 7e6773c07f5fb95a0276c927e943722f361ea4db..660f906f37ca5be66d2dc703cc3d340817a84ba3 100644 GIT binary patch literal 162821 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 delta 8017 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 literal 0 HcmV?d00001 diff --git a/CHPlugin/Assets/Images.xcassets/typing.imageset/typing@2x.gif b/CHPlugin/Assets/Images.xcassets/typing.imageset/typing@2x.gif new file mode 100644 index 0000000000000000000000000000000000000000..bc969d7cdc864658c287b9ae6fb1fe0230c1a708 GIT binary patch literal 4977 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 literal 0 HcmV?d00001 diff --git a/CHPlugin/Assets/Images.xcassets/typing.imageset/typing@3x.gif b/CHPlugin/Assets/Images.xcassets/typing.imageset/typing@3x.gif new file mode 100644 index 0000000000000000000000000000000000000000..89add703e61ddd419bd5255a7006766667454582 GIT binary patch literal 6934 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 literal 0 HcmV?d00001 diff --git a/CHPlugin/Info.plist b/CHPlugin/Info.plist index 02085881..352f4c72 100644 --- a/CHPlugin/Info.plist +++ b/CHPlugin/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.2.2 + 2.3.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSAppTransportSecurity diff --git a/CHPlugin/Source/ChannelPlugin/ChannelPlugin.swift b/CHPlugin/Source/ChannelPlugin/ChannelPlugin.swift index 1e49177b..83eb413a 100644 --- a/CHPlugin/Source/ChannelPlugin/ChannelPlugin.swift +++ b/CHPlugin/Source/ChannelPlugin/ChannelPlugin.swift @@ -213,7 +213,7 @@ public final class ChannelPlugin : NSObject { }).disposed(by: disposeBeg) - WsService.sharedService.disconnect() + WsService.shared.disconnect() mainStore.dispatch(CheckOutSuccess()) ChannelPlugin.isCheckedIn = false } @@ -460,7 +460,7 @@ public final class ChannelPlugin : NSObject { return } - WsService.sharedService.connect() + WsService.shared.connect() mainStore.dispatch(CheckInSuccess(payload: data)) ChannelPlugin.isCheckedIn = true @@ -468,7 +468,7 @@ public final class ChannelPlugin : NSObject { ChannelPlugin.track(name: "Checkin", properties: nil) } - WsService.sharedService.ready() + WsService.shared.ready() .subscribe(onNext: { _ in subscriber.onNext(data) subscriber.onCompleted() @@ -629,13 +629,13 @@ extension ChannelPlugin { } @objc private class func disconnectWebsocket() { - WsService.sharedService.disconnect() + WsService.shared.disconnect() } @objc private class func connectWebsocket() { guard ChannelPlugin.isCheckedIn == true else { return } - WsService.sharedService.connect() + WsService.shared.connect() } } diff --git a/CHPlugin/Source/Controllers/UserChatViewController.swift b/CHPlugin/Source/Controllers/UserChatViewController.swift index c882835d..da70e078 100644 --- a/CHPlugin/Source/Controllers/UserChatViewController.swift +++ b/CHPlugin/Source/Controllers/UserChatViewController.swift @@ -47,10 +47,10 @@ final class UserChatViewController: BaseSLKTextViewController { var isFetching = false var isRequstingReadAll = false var photoUrls = [String]() - var newMessageView = ChatBannerView().then { - $0.isHidden = true - } + var typingManagers = [CHManager]() + var timeStorage = [String: Timer]() + var diffCalculator: SingleSectionTableViewDiffCalculator? var messages = [CHMessage]() { didSet { @@ -58,14 +58,18 @@ final class UserChatViewController: BaseSLKTextViewController { } } + var createdFeedback = false + var createdFeedbackComplete = false + var disposeBag = DisposeBag() var photoBrowser : MWPhotoBrowser? = nil var errorToastView = ErrorToastView().then { $0.isHidden = true } + var newMessageView = ChatBannerView().then { + $0.isHidden = true + } - var createdFeedback = false - var createdFeedbackComplete = false var newChatSubject = PublishSubject() var profileSubject = PublishSubject() @@ -89,9 +93,10 @@ final class UserChatViewController: BaseSLKTextViewController { chNavigation.chDelegate = self self.initSLKTextView() - self.initInputViews() self.initTableView() + self.initInputViews() self.initViews() + self.initLiveTyping() self.shouldShowGuide = (mainStore.state.guest.ghost == true || mainStore.state.guest.mobileNumber == nil) && @@ -112,16 +117,19 @@ final class UserChatViewController: BaseSLKTextViewController { mainStore.subscribe(self) if let userChatId = self.userChatId { self.state = .ChatJoining - WsService.sharedService.join(chatId: userChatId) + WsService.shared.join(chatId: userChatId) } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) mainStore.unsubscribe(self) + + self.sendTyping(isStop: true) + if let userChatId = self.userChatId { //self.loaded = false - WsService.sharedService.leave(chatId: userChatId) + WsService.shared.leave(chatId: userChatId) } } @@ -171,6 +179,7 @@ final class UserChatViewController: BaseSLKTextViewController { self.tableView.register(cellType: SatisfactionFeedbackCell.self) self.tableView.register(cellType: SatisfactionCompleteCell.self) self.tableView.register(cellType: LogCell.self) + self.tableView.register(cellType: TypingIndicatorCell.self) self.tableView.clipsToBounds = true self.tableView.separatorStyle = .none @@ -183,7 +192,9 @@ final class UserChatViewController: BaseSLKTextViewController { self.tableView.reloadData() //self.tableView.scrollToBottom(false) self.diffCalculator = SingleSectionTableViewDiffCalculator( - tableView: self.tableView, initialRows: self.messages + tableView: self.tableView, + initialRows: self.messages, + sectionIndex: 1 ) self.diffCalculator?.forceOffAnimationEnabled = true self.diffCalculator?.insertionAnimation = UITableViewRowAnimation.none @@ -213,7 +224,7 @@ final class UserChatViewController: BaseSLKTextViewController { self.errorToastView.refreshImageView.signalForClick() .subscribe(onNext: { [weak self] _ in - WsService.sharedService.connect() + WsService.shared.connect() self?.resetUserChat() self?.fetchMessages() }).disposed(by: self.disposeBag) @@ -445,7 +456,6 @@ extension UserChatViewController: StoreSubscriber { self.nextSeq = "" CHUserChat.get(userChatId: self.userChatId ?? "") - .subscribe(onNext: { [weak self] (response) in mainStore.dispatch(GetUserChat(payload: response)) self?.fetchMessages() @@ -468,9 +478,6 @@ extension UserChatViewController: StoreSubscriber { } self.requestReadAll() - //let diff = UIScreen.main.bounds.height - self.tableView.contentSize.height - 80 - //self.tableView.contentInset.top = diff > 0 ? diff : 10.f - //self.tableView.contentInset.bottom = 10.f } func configureInputField(_ userChat: CHUserChat?) { @@ -681,11 +688,12 @@ extension UserChatViewController { private func sendMessage(userChatId: String, text: String) { let me = mainStore.state.guest var message = CHMessage(chatId: userChatId, guest: me, message: text) - self.scrollToBottom(false) + mainStore.dispatch(CreateMessage(payload: message)) self.scrollToBottom(false) message.send().subscribe(onNext: { [weak self] (updated) in + self?.sendTyping(isStop: true) mainStore.dispatch(CreateMessage(payload: updated)) self?.showUserInfoGuideIfNeeded() }, onError: { (error) in @@ -702,7 +710,7 @@ extension UserChatViewController { self?.userChatId = userChat.id mainStore.dispatch(CreateUserChat(payload: userChat)) mainStore.dispatch(CreateSession(payload: session)) - WsService.sharedService.join(chatId: userChat.id) + WsService.shared.join(chatId: userChat.id) completion(userChat.id) }, onError: { [weak self] (error) in self?.errorToastView.show(animated: true) @@ -717,12 +725,15 @@ extension UserChatViewController { self.photoBrowser?.reloadData() } + + override func textViewDidChange(_ textView: UITextView) { + self.sendTyping(isStop: textView.text == "") + } } // MARK: - UIScrollViewDelegate extension UserChatViewController { - override func scrollViewDidScroll(_ scrollView: UIScrollView) { let yOffset = scrollView.contentOffset.y if yOffset + UIScreen.main.bounds.height > scrollView.contentHeight && @@ -737,20 +748,74 @@ extension UserChatViewController { self.newMessageView.hide(animated: false) } } - } -// MARK: - UITableViewDataSource +// MARK: - UITableView extension UserChatViewController { - + override func numberOfSections(in tableView: UITableView) -> Int { + return 2 + } + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return self.messages.count + if section == 0 { + return 1 + } else if section == 1 { + return self.messages.count + } + return 0 + } + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + if indexPath.section == 0 { + return 40 + } + + let message = self.messages[indexPath.row] + let previousMessage: CHMessage? = + indexPath.row == self.messages.count - 1 ? + self.messages[indexPath.row] : + self.messages[indexPath.row + 1] + let viewModel = MessageCellModel(message: message, previous: previousMessage) + switch message.messageType { + case .DateDivider: + return 40 + case .NewAlertMessage: + return 54 + case .SatisfactionFeedback: + return 158 + 16 + case .SatisfactionCompleted: + return 104 + 16 + case .Log: + return 46 + case .UserInfoDialog: + let model = DialogViewModel.model(type: message.userGuideDialogType) + return UserInfoDialogCell.measureHeight(fits: Constant.messageCellMaxWidth, viewModel: model) + default: + let calSize = MessageCell.measureHeight(fits: Constant.messageCellMaxWidth, viewModel: viewModel) + return calSize + } } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let section = indexPath.section + if section == 0 { + return self.cellForTyping(tableView, cellForRowAt: indexPath) + } else { + return self.cellForMessage(tableView, cellForRowAt: indexPath) + } + } + + func cellForTyping(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell: TypingIndicatorCell = tableView.dequeueReusableCell(for: indexPath) + cell.transform = tableView.transform + cell.configure(typingUsers: self.typingManagers) + return cell + } + + func cellForMessage(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let message = self.messages[indexPath.row] - + switch message.messageType { case .ChannelClosed: let cell: MessageCell = tableView.dequeueReusableCell(for: indexPath) @@ -798,18 +863,18 @@ extension UserChatViewController { cell.configure(viewModel: model) cell.dialogView.signalForCountryCode() .subscribe(onNext: { [weak self] (code) in - self?.dismissKeyboard(true) - - let pickerView = CountryCodePickerView(frame: (self?.view.frame)!) - pickerView.pickedCode = code - pickerView.showPicker(onView: (self?.navigationController?.view)!,animated: true) + self?.dismissKeyboard(true) - pickerView.signalForSubmit() - .subscribe(onNext: { (code) in - cell.dialogView.setCountryCodeText(code: code) - cell.dialogView.phoneFieldView.phoneField.becomeFirstResponder() - }).disposed(by: (self?.disposeBag)!) - }).disposed(by: self.disposeBag) + let pickerView = CountryCodePickerView(frame: (self?.view.frame)!) + pickerView.pickedCode = code + pickerView.showPicker(onView: (self?.navigationController?.view)!,animated: true) + + pickerView.signalForSubmit() + .subscribe(onNext: { (code) in + cell.dialogView.setCountryCodeText(code: code) + cell.dialogView.phoneFieldView.phoneField.becomeFirstResponder() + }).disposed(by: (self?.disposeBag)!) + }).disposed(by: self.disposeBag) cell.transform = self.tableView.transform return cell case .SatisfactionFeedback: @@ -823,7 +888,7 @@ extension UserChatViewController { mainStore.dispatch(GetUserChat(payload: response)) }).disposed(by: (self?.disposeBag)!) } - }).disposed(by: self.disposeBag) + }).disposed(by: self.disposeBag) cell.transform = self.tableView.transform return cell case .SatisfactionCompleted: @@ -855,16 +920,16 @@ extension UserChatViewController { cell.clipImageView.signalForClick() .subscribe { [weak self] _ in - self?.didImageTapped(message: message) - }.disposed(by: self.disposeBag) + self?.didImageTapped(message: message) + }.disposed(by: self.disposeBag) cell.clipWebpageView.signalForClick() .subscribe{ [weak self] _ in - self?.didWebPageTapped(message: message) - }.disposed(by: self.disposeBag) + self?.didWebPageTapped(message: message) + }.disposed(by: self.disposeBag) cell.clipFileView.signalForClick() .subscribe { [weak self] _ in - self?.didFileTapped(message: message) - }.disposed(by: self.disposeBag) + self?.didFileTapped(message: message) + }.disposed(by: self.disposeBag) cell.transform = self.tableView.transform return cell @@ -875,7 +940,6 @@ extension UserChatViewController { // MARK: MWPhotoBrowser extension UserChatViewController: MWPhotoBrowserDelegate { - func numberOfPhotos(in photoBrowser: MWPhotoBrowser!) -> UInt { return UInt(self.photoUrls.count) } @@ -883,45 +947,8 @@ extension UserChatViewController: MWPhotoBrowserDelegate { func photoBrowser(_ photoBrowser: MWPhotoBrowser!, photoAt index: UInt) -> MWPhotoProtocol! { return MWPhoto(url: URL(string: self.photoUrls[Int(index)])) } - } -// MARK: - UITableViewDelegate - -extension UserChatViewController { - - override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - let message = self.messages[indexPath.row] - let previousMessage: CHMessage? = - indexPath.row == self.messages.count - 1 ? - self.messages[indexPath.row] : - self.messages[indexPath.row + 1] - let viewModel = MessageCellModel(message: message, previous: previousMessage) - switch message.messageType { - case .DateDivider: - return 40 - case .NewAlertMessage: - return 54 - case .SatisfactionFeedback: - return 158 + 16 - case .SatisfactionCompleted: - return 104 + 16 - case .Log: - return 46 - case .UserInfoDialog: - let model = DialogViewModel.model(type: message.userGuideDialogType) - return UserInfoDialogCell.measureHeight(fits: Constant.messageCellMaxWidth, viewModel: model) - default: - let calSize = MessageCell.measureHeight(fits: Constant.messageCellMaxWidth, viewModel: viewModel) - return calSize - } - } - - override func tableView(_ tableView: UITableView, - didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - } -} // MARK: Clip handlers @@ -1004,14 +1031,12 @@ extension UserChatViewController { // MARK: UIDocumentInteractionControllerDelegate methods extension UserChatViewController : UIDocumentInteractionControllerDelegate { - func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController { if let controller = CHUtils.getTopController() { return controller } return UIViewController() } - } extension UserChatViewController : CHNavigationDelegate { @@ -1020,6 +1045,7 @@ extension UserChatViewController : CHNavigationDelegate { self.requestReadAll() } if !willShow.isKind(of: UserChatViewController.self) { + self.resetTypingInfo() mainStore.dispatch(RemoveMessages(payload: self.userChatId)) } } @@ -1045,3 +1071,100 @@ extension UserChatViewController : SLKInputBarViewDelegate { } } } + +extension UserChatViewController { + func initLiveTyping() { + WsService.shared.typingSubject + .observeOn(MainScheduler.instance) + .subscribe(onNext: { [weak self] (typingEntity) in + guard let s = self else { return } + if typingEntity.action == "stop" { + if let index = s.getTypingIndex(of: typingEntity) { + let person = s.typingManagers.remove(at: index) + s.removeTimer(with: person) + } + } else if typingEntity.action == "start" { + if s.getTypingIndex(of: typingEntity) == nil, + let manager = personSelector( + state: mainStore.state, + personType: typingEntity.personType ?? "", + personId: typingEntity.personId) as? CHManager { + s.typingManagers.append(manager) + s.addTimer(with: manager, delay: 15) + } + } + + s.tableView.reloadSections(IndexSet(integer: 0), with: UITableViewRowAnimation.none) + }).disposed(by: self.disposeBag) + + WsService.shared.mOnCreate() + .observeOn(MainScheduler.instance) + .subscribe(onNext: { [weak self] (message) in + guard let s = self else { return } + + let typing = CHTypingEntity.transform(from: message) + if let index = s.getTypingIndex(of: typing) { + let person = s.typingManagers.remove(at: index) + s.removeTimer(with: person) + s.tableView.reloadSections(IndexSet(integer: 0), with: UITableViewRowAnimation.none) + } + }).disposed(by: self.disposeBag) + } + + func sendTyping(isStop: Bool) { + WsService.shared.sendTyping( + chat: self.userChat, isStop: isStop + ) + } + + func addTimer(with manager: CHManager, delay: TimeInterval) { + let timer = Timer.scheduledTimer( + timeInterval: delay, + target: self, + selector: #selector(self.expired(_:)), + userInfo: [manager], + repeats: false + ) + + if let t = self.timeStorage[manager.key] { + t.invalidate() + } + + self.timeStorage[manager.key] = timer + } + + func removeTimer(with manager: CHManager?) { + guard let manager = manager else { return } + if let t = self.timeStorage.removeValue(forKey: manager.key) { + t.invalidate() + } + } + + func resetTypingInfo() { + self.timeStorage.forEach { (k, t) in + t.invalidate() + } + self.typingManagers = [] + self.timeStorage = [:] + } + + @objc func expired(_ timer: Timer) { + guard let params = timer.userInfo as? [Any] else { return } + guard let manager = params[0] as? CHManager else { return } + + timer.invalidate() + if let index = self.typingManagers.index(where: { (m) in + return m.id == manager.id + }) { + self.typingManagers.remove(at: index) + self.timeStorage.removeValue(forKey: manager.key) + self.tableView.reloadSections(IndexSet(integer: 0), with: UITableViewRowAnimation.none) + } + } + + func getTypingIndex(of typingEntity: CHTypingEntity) -> Int? { + return self.typingManagers.index(where: { + $0.id == typingEntity.personId + }) + } +} diff --git a/CHPlugin/Source/Controllers/UserChatsViewController.swift b/CHPlugin/Source/Controllers/UserChatsViewController.swift index d757485a..6da33f35 100644 --- a/CHPlugin/Source/Controllers/UserChatsViewController.swift +++ b/CHPlugin/Source/Controllers/UserChatsViewController.swift @@ -84,7 +84,7 @@ class UserChatsViewController: BaseViewController { .subscribe(onNext: { [weak self] _ in self?.nextSeq = nil self?.fetchUserChats() - WsService.sharedService.connect() + WsService.shared.connect() }).disposed(by: self.disposeBag) self.plusButton.signalForClick() diff --git a/CHPlugin/Source/Models/CHAssets.swift b/CHPlugin/Source/Models/CHAssets.swift index 39ab8550..071d99a8 100644 --- a/CHPlugin/Source/Models/CHAssets.swift +++ b/CHPlugin/Source/Models/CHAssets.swift @@ -19,6 +19,22 @@ class CHAssets { return UIImage(named: named, in: bundle, compatibleWith: nil) } + class func getData(named: String) -> Data? { + let bundle = Bundle(for: self) + if #available(iOS 9.0, *) { + return NSDataAsset(name: named, bundle: bundle)?.data + } else { + do { + guard let url = try bundle.path(forResource: "typing", ofType: "gif")?.asURL() else { + return nil + } + return try Data(contentsOf: url) + } catch { + return nil + } + } + } + class func localized(_ key: String) -> String { let bundle = Bundle(for: self) return NSLocalizedString(key, tableName: nil, bundle: bundle, value: "", comment: "") diff --git a/CHPlugin/Source/Models/CHManager.swift b/CHPlugin/Source/Models/CHManager.swift index d060a40e..503b7799 100644 --- a/CHPlugin/Source/Models/CHManager.swift +++ b/CHPlugin/Source/Models/CHManager.swift @@ -20,6 +20,12 @@ struct CHManager: CHEntity { var color = "" // Manager var username = "" + + var key: String { + get { + return "Manager:\(self.id)" + } + } } extension CHManager: Mappable { diff --git a/CHPlugin/Source/Models/CHTypingEntity.swift b/CHPlugin/Source/Models/CHTypingEntity.swift new file mode 100644 index 00000000..afb0aa4d --- /dev/null +++ b/CHPlugin/Source/Models/CHTypingEntity.swift @@ -0,0 +1,58 @@ +// +// CHTypingEntity.swift +// CHPlugin +// +// Created by R3alFr3e on 11/14/17. +// Copyright © 2017 ZOYI. All rights reserved. +// + +import Foundation +import SocketIO +import ObjectMapper + +struct CHTypingEntity: SocketData { + var action = "" + var chatId = "" + var chatType = "" + var personId: String? = nil + var personType: String? = nil + + init(action: String, chatId: String, chatType: String, + personId: String? = nil, personType: String? = nil) { + self.action = action + self.chatId = chatId + self.chatType = chatType + self.personId = personId + self.personType = personType + } + + func socketRepresentation() -> SocketData { + return [ + "action": self.action, + "chatId": self.chatId, + "chatType": self.chatType, + ] + } + + static func transform(from message: CHMessage) -> CHTypingEntity { + return CHTypingEntity( + action: "stop", + chatId: message.chatId, + chatType: message.chatType, + personId: message.personId, + personType: message.personType + ) + } +} + +extension CHTypingEntity: Mappable { + init?(map: Map) { } + + mutating func mapping(map: Map) { + action <- map["action"] + chatId <- map["channelId"] + chatType <- map["chatType"] + personId <- map["personId"] + personType <- map["personType"] + } +} diff --git a/CHPlugin/Source/Models/CHi18n.swift b/CHPlugin/Source/Models/CHi18n.swift index f05e76fb..1f4a1e0d 100644 --- a/CHPlugin/Source/Models/CHi18n.swift +++ b/CHPlugin/Source/Models/CHi18n.swift @@ -18,8 +18,7 @@ struct CHi18n { guard let str = NSLocale.preferredLanguages.get(index: 0) else { return nil } let start = str.startIndex let end = str.index(str.startIndex, offsetBy: 2) - let range = start.. CHEntity? { +func personSelector(state: AppState, personType: String?, personId: String?) -> CHEntity? { + guard let personType = personType else { return nil } + guard let personId = personId else { return nil } + if personType == "Manager" { return state.managersState.findBy(id: personId) } else if personType == "User" || personType == "Veil" { diff --git a/CHPlugin/Source/Services/WsService.swift b/CHPlugin/Source/Services/WsService.swift index 4320cdde..43911b04 100644 --- a/CHPlugin/Source/Services/WsService.swift +++ b/CHPlugin/Source/Services/WsService.swift @@ -44,6 +44,7 @@ enum CHSocketResponse : String { case disconnect = "disconnect" case push = "push" case error = "error" + case typing = "typing" var value: String { return self.rawValue @@ -90,9 +91,11 @@ struct WsServiceType: OptionSet { class WsService { //MARK: Share Singleton Instance - static let sharedService = WsService() + static let shared = WsService() let eventSubject = PublishSubject() let readySubject = PublishSubject() + let typingSubject = PublishSubject() + let messageOnCreateSubject = PublishSubject() //MARK: Private properties fileprivate var socket: SocketIOClient! @@ -105,7 +108,10 @@ class WsService { //move these properties into state fileprivate var currentChatId: String? fileprivate var currentChat: CHUserChat? - fileprivate var heartbeatTimer: Foundation.Timer? + fileprivate var heartbeatTimer: Timer? + + private var stopTypingThrottleFnc: ((CHUserChat?) -> Void)? + private var startTypingThrottleFnc: ((CHUserChat?) -> Void)? init() { if let staging = CHUtils.getCurrentStage() { @@ -119,6 +125,16 @@ class WsService { // error } } + + self.stopTypingThrottleFnc = throttle( + delay: 1.0, + queue: DispatchQueue.global(qos: .background), + action: self.stopTyping) + + self.startTypingThrottleFnc = throttle( + delay: 1.0, + queue: DispatchQueue.global(qos: .background), + action: self.startTyping) } //MARK: Signals @@ -132,6 +148,13 @@ class WsService { return self.readySubject } + func typing() -> PublishSubject { + return self.typingSubject + } + + func mOnCreate() -> PublishSubject { + return self.messageOnCreateSubject + } //MARK: Socket functionalities func connect() { @@ -190,6 +213,40 @@ class WsService { } } + func sendTyping(chat: CHUserChat?, isStop: Bool) { + guard let socket = self.socket, socket.status == .connected else { return } + guard let chat = chat else { return } + + if isStop { + self.stopTypingThrottleFnc?(chat) + } else { + self.startTypingThrottleFnc?(chat) + } + } + + func startTyping(chat: CHUserChat?) { + guard let socket = self.socket, socket.status == .connected else { return } + guard let chat = chat else { return } + socket.emit("typing", CHTypingEntity( + action: "start", + chatId: chat.id, + chatType: "UserChat") + ) + } + + func stopTyping(chat: CHUserChat?) { + guard let socket = self.socket, socket.status == .connected else { return } + guard let chat = chat else { return } + + let entity = CHTypingEntity( + action: "stop", + chatId: chat.id, + chatType: "UserChat") + + socket.emit("typing", entity) + self.typingSubject.onNext(entity) + } + @objc func heartbeat() { dlog("heartbeat") if self.socket != nil { @@ -218,6 +275,7 @@ fileprivate extension WsService { self.onJoined() self.onLeaved() self.onPush() + self.onTyping() self.onAuthenticated() self.onUnauthorized() self.onReconnectAttempt() @@ -274,6 +332,7 @@ fileprivate extension WsService { case WsServiceType.Message: guard let message = Mapper() .map(JSONObject: json["entity"].object) else { return } + self?.messageOnCreateSubject.onNext(message) mainStore.dispatch(CreateMessage(payload: message)) break default: @@ -395,11 +454,19 @@ fileprivate extension WsService { } } + fileprivate func onTyping() { + self.socket.on(CHSocketResponse.typing.value) { [weak self] (data, ack) in + guard let entity = data.get(index: 0) else { return } + guard let json = JSON(rawValue: entity) else { return } + guard let typing = Mapper().map(JSONObject: json.object) else { return } + self?.typingSubject.onNext(typing) + } + } + fileprivate func onPush() { self.socket.on(CHSocketResponse.push.value) { [weak self] (data, ack) in self?.eventSubject.onNext(CHSocketResponse.push.value) //dlog("socket pushed: \(data)") - guard let entity = data.get(index: 0) else { return } guard let json = JSON(rawValue: entity) else { return } guard let push = Mapper().map(JSONObject: json.object) else { return } @@ -412,7 +479,6 @@ fileprivate extension WsService { } } - fileprivate func onAuthenticated() { self.socket.on(CHSocketResponse.authenticated.value) { [weak self] (data, ack) in self?.eventSubject.onNext(CHSocketResponse.authenticated.value) @@ -423,11 +489,13 @@ fileprivate extension WsService { } if let s = self { - self?.heartbeatTimer = Foundation.Timer.scheduledTimer( - timeInterval: 30, - target: s, - selector: #selector(WsService.heartbeat), - userInfo: nil, repeats: true) + dispatch { + self?.heartbeatTimer = Foundation.Timer.scheduledTimer( + timeInterval: 30, + target: s, + selector: #selector(WsService.heartbeat), + userInfo: nil, repeats: true) + } } } } diff --git a/CHPlugin/Source/Utils/CHAnimations.swift b/CHPlugin/Source/Utils/CHAnimations.swift new file mode 100644 index 00000000..0ba7d38b --- /dev/null +++ b/CHPlugin/Source/Utils/CHAnimations.swift @@ -0,0 +1,76 @@ +// +// CHAnimations.swift +// CHPlugin +// +// Created by R3alFr3e on 11/14/17. +// Copyright © 2017 ZOYI. All rights reserved. +// + +import Foundation +import UIKit + +final private class ViewAnimationStep { + fileprivate var completed: (() -> Void) = { } + fileprivate let animations: (() -> Void) + fileprivate let duration: TimeInterval + + init(withAnimations animations: @escaping (() -> Void), duration: TimeInterval = 0.0) { + self.animations = animations + self.duration = duration + } + + func onCompleted(completed: @escaping (() -> Void)) -> Self { + self.completed = completed + + return self + } + + func execute() { + UIView.animate(withDuration: duration, animations: animations) { (_) in + self.completed() + } + } +} + +class AnimationSequence { + fileprivate var completion: (() -> Void) = { } + fileprivate var sequence = [ViewAnimationStep]() + fileprivate var stepDuration: TimeInterval + + init(withStepDuration stepDuration: TimeInterval = 0.0) { + self.stepDuration = stepDuration + } + + @discardableResult + func doStep(_ animations: @escaping (() -> Void)) -> Self { + let step = ViewAnimationStep(withAnimations: animations, duration: stepDuration) + sequence.append(step) + + return self + } + + @discardableResult + func onCompletion(_ sequenceCompletion: @escaping (() -> Void)) -> Self { + completion = sequenceCompletion + + return self + } + + func execute() { + executeSteps() + } + + fileprivate func executeSteps() { + if sequence.isEmpty == false { + let step = sequence.removeFirst() + step + .onCompleted { + self.executeSteps() + } + .execute() + } + else { + completion() + } + } +} diff --git a/CHPlugin/Source/Utils/CHUtils.swift b/CHPlugin/Source/Utils/CHUtils.swift index cc0a402e..341ecc42 100644 --- a/CHPlugin/Source/Utils/CHUtils.swift +++ b/CHPlugin/Source/Utils/CHUtils.swift @@ -91,8 +91,7 @@ class CHUtils { guard let str = NSLocale.preferredLanguages.get(index: 0) else { return nil } let start = str.startIndex let end = str.index(str.startIndex, offsetBy: 2) - let range = start.. Bool { + return Date().timeIntervalSinceReferenceDate - self > since + } + +} +/** + Wraps a function in a new function that will throttle the execution to once in every `delay` seconds. + + - Parameter delay: A `TimeInterval` specifying the number of seconds that needst to pass between each execution of `action`. + - Parameter queue: The queue to perform the action on. Defaults to the main queue. + - Parameter action: A function to throttle. + + - Returns: A new function that will only call `action` once every `delay` seconds, regardless of how often it is called. + */ +func throttle(delay: TimeInterval, queue: DispatchQueue = .main, action: @escaping (() -> Void)) -> () -> Void { + var currentWorkItem: DispatchWorkItem? + var lastFire: TimeInterval = 0 + return { + guard currentWorkItem == nil else { return } + currentWorkItem = DispatchWorkItem { + action() + lastFire = Date().timeIntervalSinceReferenceDate + currentWorkItem = nil + } + if delay.hasPassed(since: lastFire) { + queue.async(execute: currentWorkItem!) + } else { + currentWorkItem = nil + } + } +} + +func throttle(delay: TimeInterval, queue: DispatchQueue = .main, action: @escaping ((T) -> Void)) -> (T) -> Void { + var currentWorkItem: DispatchWorkItem? + var lastFire: TimeInterval = 0 + return { (p1: T) in + guard currentWorkItem == nil else { return } + currentWorkItem = DispatchWorkItem { + action(p1) + lastFire = Date().timeIntervalSinceReferenceDate + currentWorkItem = nil + } + //if time has passed, execute workitem. Otherwise, abandon work + if delay.hasPassed(since: lastFire) { + queue.async(execute: currentWorkItem!) + } else { + currentWorkItem = nil + } + } +} + diff --git a/CHPlugin/Source/Views/CHMultiAvatarView.swift b/CHPlugin/Source/Views/CHMultiAvatarView.swift new file mode 100644 index 00000000..4ea90cdd --- /dev/null +++ b/CHPlugin/Source/Views/CHMultiAvatarView.swift @@ -0,0 +1,137 @@ +// +// CHMultiAvatarView.swift +// CHPlugin +// +// Created by R3alFr3e on 11/14/17. +// Copyright © 2017 ZOYI. All rights reserved. +// + +import Foundation +import UIKit + +class CHMultiAvatarView: BaseView { + let firstAvatarView = AvatarView().then { + $0.showBorder = false + } + let secondAvatarView = AvatarView().then { + $0.showBorder = false + } + let thirdAvatarView = AvatarView().then { + $0.showBorder = false + } + + var persons = [CHEntity]() + + override func initialize() { + super.initialize() + + self.addSubview(self.thirdAvatarView) + self.addSubview(self.secondAvatarView) + self.addSubview(self.firstAvatarView) + } + + override func setLayouts() { + super.setLayouts() + + self.firstAvatarView.snp.remakeConstraints { (make) in + make.size.equalTo(CGSize(width:22, height:22)) + make.top.equalToSuperview() + make.bottom.equalToSuperview() + make.leading.equalToSuperview() + } + + self.secondAvatarView.snp.remakeConstraints { (make) in + make.size.equalTo(CGSize(width:22, height:22)) + make.top.equalToSuperview() + make.bottom.equalToSuperview() + make.leading.equalToSuperview().inset(18) + } + + self.thirdAvatarView.snp.remakeConstraints { (make) in + make.size.equalTo(CGSize(width:22, height:22)) + make.top.equalToSuperview() + make.bottom.equalToSuperview() + make.leading.equalToSuperview().inset(36) + } + } + + func configure(persons: [CHEntity]) { + guard self.isIdentical(persons: persons) == false else { return } + + if persons.count == 1 { + self.firstAvatarView.configure(persons[0]) + self.layoutOneAvatar() + } else if persons.count == 2 { + self.firstAvatarView.configure(persons[0]) + self.secondAvatarView.configure(persons[1]) + self.layoutTwoAvatars() + } else if persons.count == 3 { + self.firstAvatarView.configure(persons[0]) + self.secondAvatarView.configure(persons[1]) + self.thirdAvatarView.configure(persons[2]) + self.layoutThreeAvatars() + } else { + self.firstAvatarView.configure(persons[0]) + self.secondAvatarView.configure(persons[1]) + self.layoutTwoAvatars() + } + } + + func isIdentical(persons: [CHEntity]) -> Bool { + for person in persons { + if self.persons.index(where: { (p) in + return p.avatarUrl == person.avatarUrl && p.name == person.name + }) != nil { + continue + } else { + return false + } + } + + return true + } + + func layoutOneAvatar() { + AnimationSequence(withStepDuration: 0.2).doStep { [weak self] in + self?.firstAvatarView.alpha = 1 + }.execute() + + if self.secondAvatarView.alpha == 1 { + AnimationSequence(withStepDuration: 0.2).doStep { [weak self] in + self?.secondAvatarView.alpha = 0 + }.execute() + } + + if self.thirdAvatarView.alpha == 1 { + AnimationSequence(withStepDuration: 0.2).doStep { [weak self] in + self?.thirdAvatarView.alpha = 0 + }.execute() + } + } + + func layoutTwoAvatars() { + if self.secondAvatarView.alpha == 0 { + AnimationSequence(withStepDuration: 0.2).doStep { [weak self] in + self?.secondAvatarView.alpha = 1 + }.execute() + } + + if self.thirdAvatarView.alpha == 1 { + AnimationSequence(withStepDuration: 0.2).doStep { [weak self] in + self?.thirdAvatarView.alpha = 0 + }.execute() + } + } + + func layoutThreeAvatars() { + if self.secondAvatarView.alpha == 0 { + let seq = AnimationSequence(withStepDuration: 0.4) + seq.doStep { [weak self] in + self?.secondAvatarView.alpha = 1 + } + .doStep { [weak self] in + self?.thirdAvatarView.alpha = 1 + }.execute() + } + } +} diff --git a/CHPlugin/Source/Views/Cells/TypingIndicatorCell.swift b/CHPlugin/Source/Views/Cells/TypingIndicatorCell.swift new file mode 100644 index 00000000..4e06fef8 --- /dev/null +++ b/CHPlugin/Source/Views/Cells/TypingIndicatorCell.swift @@ -0,0 +1,92 @@ +// +// TypingIndicatorCell.swift +// +// +// Created by R3alFr3e on 11/14/17. +// + +import Foundation +import UIKit +import SnapKit +import Reusable + +final class TypingIndicatorCell: BaseTableViewCell, Reusable { + let multiAvatarView = CHMultiAvatarView() + let personCountLabel = UILabel().then { + $0.font = UIFont.systemFont(ofSize: 12) + $0.textColor = CHColors.blueyGrey + } + let typingImageView = UIImageView().then { + $0.contentMode = .scaleAspectFit + } + + var avatarViewWidthConstraint: Constraint? = nil + + override func initialize() { + super.initialize() + + if let data = CHAssets.getData(named: "typing") { + self.typingImageView.image = UIImage.sd_animatedGIF(with: data) + } + + self.contentView.addSubview(self.multiAvatarView) + self.contentView.addSubview(self.personCountLabel) + self.contentView.addSubview(self.typingImageView) + } + + override func prepareForReuse() { + self.typingImageView.layoutIfNeeded() + } + + override func setLayouts() { + super.setLayouts() + + self.multiAvatarView.snp.remakeConstraints { [weak self] (make) in + make.leading.equalToSuperview().inset(10) + make.height.equalTo(22) + make.centerY.equalToSuperview() + self?.avatarViewWidthConstraint = make.width.equalTo(22).constraint + } + + self.personCountLabel.snp.remakeConstraints { [weak self] (make) in + make.leading.equalTo((self?.multiAvatarView.snp.trailing)!).offset(2) + make.centerY.equalToSuperview() + } + + self.typingImageView.snp.remakeConstraints { [weak self] (make) in + make.centerY.equalToSuperview() + make.height.equalTo(6) + make.width.equalTo(22) + make.leading.equalTo((self?.personCountLabel.snp.trailing)!).offset(6) + } + } + + func configure(typingUsers: [CHEntity]) { + guard typingUsers.count > 0 else { + self.multiAvatarView.isHidden = true + self.typingImageView.isHidden = true + self.personCountLabel.isHidden = true + return + } + + self.typingImageView.isHidden = false + self.multiAvatarView.isHidden = false + UIView.animate(withDuration: 0.2) { + self.personCountLabel.isHidden = typingUsers.count < 4 + self.personCountLabel.text = typingUsers.count < 4 ? "" : "+\(typingUsers.count)" + + if typingUsers.count == 1 { + self.avatarViewWidthConstraint?.update(offset: 22) + } else if typingUsers.count == 2 { + self.avatarViewWidthConstraint?.update(offset: 40) + } else if typingUsers.count == 3 { + self.avatarViewWidthConstraint?.update(offset: 58) + } else { + self.avatarViewWidthConstraint?.update(offset: 40) + } + } + + self.multiAvatarView.configure(persons: typingUsers) + } +} + diff --git a/CHPlugin/Source/Views/ChatNotificationView.swift b/CHPlugin/Source/Views/ChatNotificationView/ChatNotificationView.swift similarity index 100% rename from CHPlugin/Source/Views/ChatNotificationView.swift rename to CHPlugin/Source/Views/ChatNotificationView/ChatNotificationView.swift diff --git a/CHPlugin/Source/Views/DialogActionView.swift b/CHPlugin/Source/Views/DialogView/DialogActionView.swift similarity index 100% rename from CHPlugin/Source/Views/DialogActionView.swift rename to CHPlugin/Source/Views/DialogView/DialogActionView.swift diff --git a/Example/Swift Example/Channel Plugin Sample.xcodeproj/project.pbxproj b/Example/Swift Example/Channel Plugin Sample.xcodeproj/project.pbxproj index fd4759fb..9861258c 100644 --- a/Example/Swift Example/Channel Plugin Sample.xcodeproj/project.pbxproj +++ b/Example/Swift Example/Channel Plugin Sample.xcodeproj/project.pbxproj @@ -185,16 +185,16 @@ "${SRCROOT}/Pods/Target Support Files/Pods-Channel Plugin Sample/Pods-Channel Plugin Sample-frameworks.sh", "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", "${BUILT_PRODUCTS_DIR}/CGFloatLiteral/CGFloatLiteral.framework", + "${BUILT_PRODUCTS_DIR}/CHDwifft/CHDwifft.framework", + "${BUILT_PRODUCTS_DIR}/CHPhotoBrowser/CHPhotoBrowser.framework", "${BUILT_PRODUCTS_DIR}/CHPlugin/CHPlugin.framework", "${BUILT_PRODUCTS_DIR}/CHSlackTextViewController/CHSlackTextViewController.framework", "${BUILT_PRODUCTS_DIR}/CRToast/CRToast.framework", "${BUILT_PRODUCTS_DIR}/DACircularProgress/DACircularProgress.framework", "${BUILT_PRODUCTS_DIR}/DKImagePickerController/DKImagePickerController.framework", - "${BUILT_PRODUCTS_DIR}/Dwifft/Dwifft.framework", "${BUILT_PRODUCTS_DIR}/M13ProgressSuite/M13ProgressSuite.framework", "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework", "${BUILT_PRODUCTS_DIR}/MGSwipeTableCell/MGSwipeTableCell.framework", - "${BUILT_PRODUCTS_DIR}/MWPhotoBrowser/MWPhotoBrowser.framework", "${BUILT_PRODUCTS_DIR}/ManualLayout/ManualLayout.framework", "${BUILT_PRODUCTS_DIR}/NVActivityIndicatorView/NVActivityIndicatorView.framework", "${BUILT_PRODUCTS_DIR}/ObjectMapper/ObjectMapper.framework", @@ -216,16 +216,16 @@ outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CGFloatLiteral.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CHDwifft.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CHPhotoBrowser.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CHPlugin.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CHSlackTextViewController.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CRToast.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DACircularProgress.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKImagePickerController.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Dwifft.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/M13ProgressSuite.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MBProgressHUD.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MGSwipeTableCell.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MWPhotoBrowser.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ManualLayout.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NVActivityIndicatorView.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ObjectMapper.framework", From c9d92e6d1fbe702ead830612f32ab1aa59051e66 Mon Sep 17 00:00:00 2001 From: intoxicated Date: Wed, 15 Nov 2017 22:13:02 +0900 Subject: [PATCH 2/2] Updated docs --- CHANGELOG.md | 6 ++++++ CHPlugin/Source/Extensions/String+Utils.swift | 3 +++ README.md | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 051c69b3..aa59b4dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ ### 2.3.0 #### Update * Added live typing indicator +* Raised min os version to 9.0 + +### 2.2.6 +#### Bug fixes +* Fixed scopes for objective-c +* Fixed symbol error for iOS 8 ### 2.2.4 * SwiftyJSON 4.0 migration diff --git a/CHPlugin/Source/Extensions/String+Utils.swift b/CHPlugin/Source/Extensions/String+Utils.swift index a8aaf4bb..515a8726 100644 --- a/CHPlugin/Source/Extensions/String+Utils.swift +++ b/CHPlugin/Source/Extensions/String+Utils.swift @@ -46,6 +46,9 @@ extension String { data: data, options: [ .documentType: NSAttributedString.DocumentType.html, + //iOS 8 symbol error + //https://stackoverflow.com/questions/46484650/documentreadingoptionkey-key-corrupt-after-swift4-migration + //NSAttributedString.DocumentReadingOptionKey("CharacterEncodi‌​ng"): String.Encoding.utf8.rawValue .characterEncoding: String.Encoding.utf8.rawValue ], documentAttributes: nil).string diff --git a/README.md b/README.md index 0cfe6142..1f851fe8 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ## Prerequisite -* iOS 8 or above +* iOS 9 or above ## Documentation