WFCUChatInputBar.m 61 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445
  1. //
  2. // ChatInputBar.m
  3. // WFChat UIKit
  4. //
  5. // Created by WF Chat on 2017/10/28.
  6. // Copyright © 2017年 WildFireChat. All rights reserved.
  7. //
  8. #import <AVFoundation/AVFoundation.h>
  9. #import <CoreText/CoreText.h>
  10. #import "WFCUChatInputBar.h"
  11. #import "WFCUFaceBoard.h"
  12. #import "WFCUVoiceRecordView.h"
  13. #import "WFCUPluginBoardView.h"
  14. #import "WFCUUtilities.h"
  15. #import "WFCULocationViewController.h"
  16. #import "WFCULocationPoint.h"
  17. #import "WFCUSelectFileViewController.h"
  18. #import "KZVideoViewController.h"
  19. #import "UIView+Toast.h"
  20. #import <WFChatClient/WFCChatClient.h>
  21. #import "WFCUContactListViewController.h"
  22. #import "MBProgressHUD.h"
  23. #import "UIColor+YH.h"
  24. #if WFCU_SUPPORT_VOIP
  25. #import <WFAVEngineKit/WFAVEngineKit.h>
  26. #endif
  27. #import "DNImagePickerController.h"
  28. #import "DNAsset.h"
  29. #import <Photos/Photos.h>
  30. #define CHAT_INPUT_BAR_PADDING 8
  31. #define CHAT_INPUT_BAR_ICON_SIZE (CHAT_INPUT_BAR_HEIGHT - CHAT_INPUT_BAR_PADDING - CHAT_INPUT_BAR_PADDING)
  32. @implementation WFCUMetionInfo
  33. - (instancetype)initWithType:(int)type target:(NSString *)target range:(NSRange)range {
  34. self = [super init];
  35. if (self) {
  36. self.mentionType = type;
  37. self.target = target;
  38. self.range = range;
  39. }
  40. return self;
  41. }
  42. @end
  43. //@implementation TextInfo
  44. //
  45. //@end
  46. @interface WFCUChatInputBar () <UITextViewDelegate, WFCUFaceBoardDelegate, UIImagePickerControllerDelegate, AVAudioRecorderDelegate, AVAudioPlayerDelegate, WFCUPluginBoardViewDelegate, UIImagePickerControllerDelegate, LocationViewControllerDelegate, UIActionSheetDelegate, KZVideoViewControllerDelegate, DNImagePickerControllerDelegate, UIDocumentPickerDelegate>
  47. @property (nonatomic, assign)BOOL textInput;
  48. @property (nonatomic, assign)BOOL voiceInput;
  49. @property (nonatomic, assign)BOOL emojInput;
  50. @property (nonatomic, assign)BOOL pluginInput;
  51. @property (nonatomic, strong)UIButton *voiceSwitchBtn;
  52. @property (nonatomic, strong)UIButton *emojSwitchBtn;
  53. @property (nonatomic, strong)UIButton *pluginSwitchBtn;
  54. @property (nonatomic, strong)UITextView *textInputView;
  55. @property (nonatomic, strong)UIView *inputCoverView;
  56. @property (nonatomic, strong)UIButton *voiceInputBtn;
  57. @property (nonatomic, strong)UIView *emojInputView;
  58. @property (nonatomic, strong)UIView *pluginInputView;
  59. @property(nonatomic, weak)id<WFCUChatInputBarDelegate> delegate;
  60. @property (nonatomic, strong)WFCUVoiceRecordView *recordView;
  61. @property(nonatomic) AVAudioRecorder *recorder;
  62. @property(nonatomic) NSTimer *recordingTimer;
  63. @property(nonatomic) NSTimer *updateMeterTimer;
  64. @property(nonatomic, assign) int seconds;
  65. @property(nonatomic) BOOL recordCanceled;
  66. @property(nonatomic, weak)UIView *parentView;
  67. @property (nonatomic, strong)NSMutableArray<WFCUMetionInfo *> *mentionInfos;
  68. @property (nonatomic, strong)WFCCConversation *conversation;
  69. @property (nonatomic, assign)double lastTypingTime;
  70. @property (nonatomic, strong)UIColor *textInputViewTintColor;
  71. @property (nonatomic, assign)CGRect backupFrame;
  72. @end
  73. @implementation WFCUChatInputBar
  74. - (instancetype)initWithSuperView:(UIView *)parentView conversation:(WFCCConversation *)conversation delegate:(id<WFCUChatInputBarDelegate>)delegate {
  75. self = [super initWithFrame:CGRectMake(0, parentView.bounds.size.height - CHAT_INPUT_BAR_HEIGHT, parentView.bounds.size.width, CHAT_INPUT_BAR_HEIGHT)];
  76. if (self) {
  77. [parentView addSubview:self];
  78. [self initSubViews];
  79. self.delegate = delegate;
  80. self.parentView = parentView;
  81. self.mentionInfos = [[NSMutableArray alloc] init];
  82. self.conversation = conversation;
  83. self.lastTypingTime = 0;
  84. self.backupFrame = CGRectZero;
  85. }
  86. return self;
  87. }
  88. - (void)initSubViews {
  89. [[NSNotificationCenter defaultCenter] addObserver:self
  90. selector:@selector(keyboardWillShow:)
  91. name:UIKeyboardWillShowNotification
  92. object:nil];
  93. [[NSNotificationCenter defaultCenter] addObserver:self
  94. selector:@selector(keyboardWillHide:)
  95. name:UIKeyboardWillHideNotification
  96. object:nil];
  97. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidHide:) name:UIKeyboardDidHideNotification object:nil];
  98. self.backgroundColor = [UIColor colorWithHexString:@"0xf7f7f7"];
  99. CGRect parentRect = self.bounds;
  100. self.voiceSwitchBtn = [[UIButton alloc] initWithFrame:CGRectMake(CHAT_INPUT_BAR_PADDING, CHAT_INPUT_BAR_PADDING, CHAT_INPUT_BAR_ICON_SIZE, CHAT_INPUT_BAR_ICON_SIZE)];
  101. [self.voiceSwitchBtn setImage:[UIImage imageNamed:@"chat_input_bar_voice"] forState:UIControlStateNormal];
  102. [self.voiceSwitchBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
  103. [self.voiceSwitchBtn addTarget:self action:@selector(onSwitchBtn:) forControlEvents:UIControlEventTouchDown];
  104. [self addSubview:self.voiceSwitchBtn];
  105. self.pluginSwitchBtn = [[UIButton alloc] initWithFrame:CGRectMake(parentRect.size.width - CHAT_INPUT_BAR_HEIGHT + CHAT_INPUT_BAR_PADDING, CHAT_INPUT_BAR_PADDING, CHAT_INPUT_BAR_ICON_SIZE, CHAT_INPUT_BAR_ICON_SIZE)];
  106. [self.pluginSwitchBtn setImage:[UIImage imageNamed:@"chat_input_bar_plugin"] forState:UIControlStateNormal];
  107. [self.pluginSwitchBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
  108. [self.pluginSwitchBtn addTarget:self action:@selector(onSwitchBtn:) forControlEvents:UIControlEventTouchDown];
  109. [self addSubview:self.pluginSwitchBtn];
  110. self.emojSwitchBtn = [[UIButton alloc] initWithFrame:CGRectMake(parentRect.size.width - CHAT_INPUT_BAR_HEIGHT - CHAT_INPUT_BAR_ICON_SIZE, CHAT_INPUT_BAR_PADDING, CHAT_INPUT_BAR_ICON_SIZE, CHAT_INPUT_BAR_ICON_SIZE)];
  111. [self.emojSwitchBtn setImage:[UIImage imageNamed:@"chat_input_bar_emoj"] forState:UIControlStateNormal];
  112. [self.emojSwitchBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
  113. [self.emojSwitchBtn addTarget:self action:@selector(onSwitchBtn:) forControlEvents:UIControlEventTouchDown];
  114. [self addSubview:self.emojSwitchBtn];
  115. self.textInputView = [[UITextView alloc] initWithFrame:CGRectMake(CHAT_INPUT_BAR_HEIGHT, CHAT_INPUT_BAR_PADDING, parentRect.size.width - CHAT_INPUT_BAR_HEIGHT - CHAT_INPUT_BAR_HEIGHT - CHAT_INPUT_BAR_HEIGHT + CHAT_INPUT_BAR_PADDING, CHAT_INPUT_BAR_ICON_SIZE)];
  116. self.textInputView.delegate = self;
  117. self.textInputView.layoutManager.allowsNonContiguousLayout = NO;
  118. [self.textInputView setExclusiveTouch:YES];
  119. [self.textInputView setTextColor:[UIColor blackColor]];
  120. [self.textInputView setFont:[UIFont systemFontOfSize:16]];
  121. [self.textInputView setReturnKeyType:UIReturnKeySend];
  122. self.textInputView.backgroundColor = [UIColor whiteColor];
  123. self.textInputView.enablesReturnKeyAutomatically = YES;
  124. self.textInputView.userInteractionEnabled = YES;
  125. [self addSubview:self.textInputView];
  126. self.inputCoverView = [[UIView alloc] initWithFrame:self.textInputView.bounds];
  127. self.inputCoverView.backgroundColor = [UIColor clearColor];
  128. [self.textInputView addSubview:self.inputCoverView];
  129. UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapInputView:)];
  130. tap.numberOfTapsRequired = 1;
  131. [self.inputCoverView addGestureRecognizer:tap];
  132. self.voiceInputBtn = [[UIButton alloc] initWithFrame:CGRectMake(CHAT_INPUT_BAR_HEIGHT, CHAT_INPUT_BAR_PADDING, parentRect.size.width - CHAT_INPUT_BAR_HEIGHT - CHAT_INPUT_BAR_HEIGHT - CHAT_INPUT_BAR_HEIGHT + CHAT_INPUT_BAR_PADDING, CHAT_INPUT_BAR_ICON_SIZE)];
  133. [self.voiceInputBtn setTitle:WFCString(@"HoldToTalk") forState:UIControlStateNormal];
  134. [self.voiceInputBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
  135. self.voiceInputBtn.layer.cornerRadius = 4;
  136. self.voiceInputBtn.layer.masksToBounds = YES;
  137. self.voiceInputBtn.layer.borderWidth = 0.5f;
  138. self.voiceInputBtn.layer.borderColor = HEXCOLOR(0xdbdbdd).CGColor;
  139. [self addSubview:self.voiceInputBtn];
  140. self.layer.borderWidth = 0.5f;
  141. self.layer.borderColor = HEXCOLOR(0xdbdbdd).CGColor;
  142. self.inputBarStatus = ChatInputBarDefaultStatus;
  143. [self.voiceInputBtn addTarget:self action:@selector(onTouchDown:) forControlEvents:UIControlEventTouchDown];
  144. [self.voiceInputBtn addTarget:self action:@selector(onTouchDragExit:) forControlEvents:UIControlEventTouchDragExit];
  145. [self.voiceInputBtn addTarget:self action:@selector(onTouchDragEnter:) forControlEvents:UIControlEventTouchDragEnter];
  146. [self.voiceInputBtn addTarget:self action:@selector(onTouchUpInside:) forControlEvents:UIControlEventTouchUpInside];
  147. [self.voiceInputBtn addTarget:self action:@selector(onTouchUpOutside:) forControlEvents:UIControlEventTouchUpOutside];
  148. [self.voiceInputBtn addTarget:self action:@selector(onTouchUpOutside:) forControlEvents:UIControlEventTouchCancel];
  149. self.voiceInputBtn.hidden = YES;
  150. self.textInputView.returnKeyType = UIReturnKeySend;
  151. self.textInputView.delegate = self;
  152. }
  153. - (void)onTapInputView:(id)sender {
  154. NSLog(@"on tap input view");
  155. self.inputBarStatus = ChatInputBarKeyboardStatus;
  156. }
  157. - (void)onTouchDown:(id)sender {
  158. if ([self canRecord]) {
  159. _recordView = [[WFCUVoiceRecordView alloc] initWithFrame:CGRectMake(self.parentView.bounds.size.width/2 - 70, self.parentView.bounds.size.height/2 - 70, 140, 140)];
  160. _recordView.center = self.parentView.center;
  161. [self.parentView addSubview:_recordView];
  162. [self.parentView bringSubviewToFront:_recordView];
  163. [self recordStart];
  164. }
  165. }
  166. - (void)willAppear {
  167. if (self.backupFrame.size.height) {
  168. [self.delegate willChangeFrame:self.backupFrame withDuration:0.5 keyboardShowing:NO];
  169. }
  170. }
  171. - (void)recordStart {
  172. if (self.recorder.recording) {
  173. return;
  174. }
  175. //[self stopPlayer];
  176. [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) {
  177. if (granted) {
  178. AVAudioSession *session = [AVAudioSession sharedInstance];
  179. [session setCategory:AVAudioSessionCategoryRecord error:nil];
  180. BOOL r = [session setActive:YES error:nil];
  181. if (!r) {
  182. NSLog(@"activate audio session fail");
  183. return;
  184. }
  185. NSLog(@"start record...");
  186. NSArray *pathComponents = [NSArray arrayWithObjects:
  187. [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject],
  188. @"voice.wav",
  189. nil];
  190. NSURL *outputFileURL = [NSURL fileURLWithPathComponents:pathComponents];
  191. // Define the recorder setting
  192. NSMutableDictionary *recordSetting = [[NSMutableDictionary alloc] init];
  193. [recordSetting setValue:[NSNumber numberWithInt:kAudioFormatLinearPCM] forKey:AVFormatIDKey];
  194. [recordSetting setValue:[NSNumber numberWithFloat:8000] forKey:AVSampleRateKey];
  195. [recordSetting setValue:[NSNumber numberWithInt:2] forKey:AVNumberOfChannelsKey];
  196. self.recorder = [[AVAudioRecorder alloc] initWithURL:outputFileURL settings:recordSetting error:NULL];
  197. self.recorder.delegate = self;
  198. self.recorder.meteringEnabled = YES;
  199. if (![self.recorder prepareToRecord]) {
  200. NSLog(@"prepare record fail");
  201. return;
  202. }
  203. if (![self.recorder record]) {
  204. NSLog(@"start record fail");
  205. return;
  206. }
  207. self.recordCanceled = NO;
  208. self.seconds = 0;
  209. self.recordingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES];
  210. self.updateMeterTimer = [NSTimer scheduledTimerWithTimeInterval:0.05
  211. target:self
  212. selector:@selector(updateMeter:)
  213. userInfo:nil
  214. repeats:YES];
  215. } else {
  216. [[[UIAlertView alloc] initWithTitle:@"警告" message:@"无法录音,请到设置-隐私-麦克风,允许程序访问" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil] show];
  217. }
  218. }];
  219. }
  220. - (void)recordCancel {
  221. NSLog(@"touch cancel");
  222. if (self.recorder.recording) {
  223. NSLog(@"cancel record...");
  224. self.recordCanceled = YES;
  225. [self stopRecord];
  226. }
  227. }
  228. - (void)onTouchDragExit:(id)sender {
  229. [self.recordView recordButtonDragOutside];
  230. }
  231. - (void)onTouchDragEnter:(id)sender {
  232. [self.recordView recordButtonDragInside];
  233. }
  234. - (void)onTouchUpInside:(id)sender {
  235. [self.recordView removeFromSuperview];
  236. [self recordEnd];
  237. }
  238. - (void)onTouchUpOutside:(id)sender {
  239. [self.recordView removeFromSuperview];
  240. [self recordCancel];
  241. }
  242. - (BOOL)canRecord {
  243. __block BOOL bCanRecord = YES;
  244. if ([[AVAudioSession sharedInstance]
  245. respondsToSelector:@selector(requestRecordPermission:)]) {
  246. [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) {
  247. bCanRecord = granted;
  248. dispatch_async(dispatch_get_main_queue(), ^{
  249. bCanRecord = granted;
  250. if (granted) {
  251. bCanRecord = YES;
  252. } else {
  253. }
  254. });
  255. }];
  256. }
  257. return bCanRecord;
  258. }
  259. - (void)timerFired:(NSTimer*)timer {
  260. self.seconds = self.seconds + 1;
  261. int minute = self.seconds/60;
  262. int s = self.seconds%60;
  263. NSString *str = [NSString stringWithFormat:@"%02d:%02d", minute, s];
  264. NSLog(@"timer:%@", str);
  265. int countdown = 60 - self.seconds;
  266. if (countdown <= 10) {
  267. [self.recordView setCountdown:countdown];
  268. }
  269. if (countdown <= 0) {
  270. [self.recordView removeFromSuperview];
  271. [self recordEnd];
  272. } else {
  273. [self notifyTyping:1];
  274. }
  275. }
  276. - (void)updateMeter:(NSTimer*)timer {
  277. double voiceMeter = 0;
  278. if ([self.recorder isRecording]) {
  279. [self.recorder updateMeters];
  280. //获取音量的平均值 [recorder averagePowerForChannel:0];
  281. //音量的最大值 [recorder peakPowerForChannel:0];
  282. double lowPassResults = pow(10, (0.05 * [self.recorder peakPowerForChannel:0]));
  283. voiceMeter = lowPassResults;
  284. }
  285. [self.recordView setVoiceImage:voiceMeter];
  286. }
  287. -(void)recordEnd {
  288. if (self.recorder.recording) {
  289. NSLog(@"stop record...");
  290. self.recordCanceled = NO;
  291. [self stopRecord];
  292. }
  293. }
  294. -(void)stopRecord {
  295. [self.recorder stop];
  296. [self.recordingTimer invalidate];
  297. self.recordingTimer = nil;
  298. [self.updateMeterTimer invalidate];
  299. self.updateMeterTimer = nil;
  300. AVAudioSession *audioSession = [AVAudioSession sharedInstance];
  301. BOOL r = [audioSession setActive:NO error:nil];
  302. if (!r) {
  303. NSLog(@"deactivate audio session fail");
  304. }
  305. }
  306. - (void)resetInputBarStatue {
  307. if (self.inputBarStatus != ChatInputBarRecordStatus && self.inputBarStatus != ChatInputBarMuteStatus) {
  308. self.inputBarStatus = ChatInputBarDefaultStatus;
  309. }
  310. }
  311. - (void)onSwitchBtn:(id)sender {
  312. if (sender == self.voiceSwitchBtn) {
  313. if (self.voiceInput && self.inputBarStatus != ChatInputBarDefaultStatus) {
  314. self.inputBarStatus = ChatInputBarKeyboardStatus;
  315. } else {
  316. self.inputBarStatus = ChatInputBarRecordStatus;
  317. }
  318. } else if(sender == self.emojSwitchBtn) {
  319. if (self.emojInput && self.inputBarStatus != ChatInputBarDefaultStatus) {
  320. self.inputBarStatus = ChatInputBarKeyboardStatus;
  321. } else {
  322. self.inputBarStatus = ChatInputBarEmojiStatus;
  323. }
  324. } else if (sender == self.pluginSwitchBtn) {
  325. if (self.pluginInput && self.inputBarStatus != ChatInputBarDefaultStatus) {
  326. self.inputBarStatus = ChatInputBarKeyboardStatus;
  327. } else {
  328. self.inputBarStatus = ChatInputBarPluginStatus;
  329. }
  330. }
  331. }
  332. - (void)setInputBarStatus:(ChatInputBarStatus)inputBarStatus {
  333. if (inputBarStatus == _inputBarStatus) {
  334. return;
  335. }
  336. if (_inputBarStatus == ChatInputBarMuteStatus) {
  337. [self.textInputView setUserInteractionEnabled:YES];
  338. [self.voiceInputBtn setEnabled:YES];
  339. [self.voiceSwitchBtn setEnabled:YES];
  340. [self.emojSwitchBtn setEnabled:YES];
  341. [self.pluginSwitchBtn setEnabled:YES];
  342. }
  343. _inputBarStatus = inputBarStatus;
  344. switch (inputBarStatus) {
  345. case ChatInputBarKeyboardStatus:
  346. self.voiceInput = NO;
  347. self.emojInput = NO;
  348. self.pluginInput = NO;
  349. self.textInput = YES;
  350. break;
  351. case ChatInputBarPluginStatus:
  352. self.voiceInput = NO;
  353. self.emojInput = NO;
  354. self.pluginInput = YES;
  355. self.textInput = NO;
  356. break;
  357. case ChatInputBarEmojiStatus:
  358. self.voiceInput = NO;
  359. self.emojInput = YES;
  360. self.pluginInput = NO;
  361. self.textInput = NO;
  362. break;
  363. case ChatInputBarRecordStatus:
  364. self.voiceInput = YES;
  365. self.emojInput = NO;
  366. self.pluginInput = NO;
  367. self.textInput = NO;
  368. break;
  369. case ChatInputBarPublicStatus:
  370. self.voiceInput = NO;
  371. self.emojInput = NO;
  372. self.pluginInput = NO;
  373. self.textInput = NO;
  374. break;
  375. case ChatInputBarDefaultStatus:
  376. self.voiceInput = NO;
  377. self.emojInput = NO;
  378. self.pluginInput = NO;
  379. self.textInput = YES;
  380. [self.textInputView resignFirstResponder];
  381. break;
  382. case ChatInputBarMuteStatus:
  383. self.voiceInput = NO;
  384. self.emojInput = NO;
  385. self.pluginInput = NO;
  386. self.textInput = YES;
  387. [self.textInputView setUserInteractionEnabled:NO];
  388. [self.voiceInputBtn setEnabled:NO];
  389. [self.voiceSwitchBtn setEnabled:NO];
  390. [self.emojSwitchBtn setEnabled:NO];
  391. [self.pluginSwitchBtn setEnabled:NO];
  392. break;
  393. default:
  394. break;
  395. }
  396. if (inputBarStatus != ChatInputBarKeyboardStatus) {
  397. if (self.textInputView.tintColor != [UIColor clearColor]) {
  398. self.textInputViewTintColor = self.textInputView.tintColor;
  399. }
  400. self.textInputView.tintColor = [UIColor clearColor];
  401. self.inputCoverView.hidden = NO;
  402. } else {
  403. self.textInputView.tintColor = self.textInputViewTintColor;
  404. self.inputCoverView.hidden = YES;
  405. }
  406. }
  407. - (void)setVoiceInput:(BOOL)voiceInput {
  408. _voiceInput = voiceInput;
  409. if (voiceInput) {
  410. [self.textInputView setHidden:YES];
  411. [self.voiceInputBtn setHidden:NO];
  412. if (self.textInputView.isFirstResponder) {
  413. [self.textInputView resignFirstResponder];
  414. }
  415. [self.voiceSwitchBtn setImage:[UIImage imageNamed:@"chat_input_bar_keyboard"] forState:UIControlStateNormal];
  416. } else {
  417. [self.textInputView setHidden:NO];
  418. [self.voiceInputBtn setHidden:YES];
  419. [self.voiceSwitchBtn setImage:[UIImage imageNamed:@"chat_input_bar_voice"] forState:UIControlStateNormal];
  420. }
  421. }
  422. - (void)setEmojInput:(BOOL)emojInput {
  423. _emojInput = emojInput;
  424. if (emojInput) {
  425. [self.textInputView setHidden:NO];
  426. [self.voiceInputBtn setHidden:YES];
  427. self.textInputView.inputView = self.emojInputView;
  428. if (!self.textInputView.isFirstResponder) {
  429. [self.textInputView becomeFirstResponder];
  430. }
  431. [self.textInputView reloadInputViews];
  432. [self.emojSwitchBtn setImage:[UIImage imageNamed:@"chat_input_bar_keyboard"] forState:UIControlStateNormal];
  433. } else {
  434. [self.emojSwitchBtn setImage:[UIImage imageNamed:@"chat_input_bar_emoj"] forState:UIControlStateNormal];
  435. }
  436. }
  437. - (void)setPluginInput:(BOOL)pluginInput {
  438. _pluginInput = pluginInput;
  439. if (pluginInput) {
  440. [self.textInputView setHidden:NO];
  441. [self.voiceInputBtn setHidden:YES];
  442. self.textInputView.inputView = self.pluginInputView;
  443. if (!self.textInputView.isFirstResponder) {
  444. [self.textInputView becomeFirstResponder];
  445. }
  446. [self.textInputView reloadInputViews];
  447. }
  448. }
  449. - (void)setTextInput:(BOOL)textInput {
  450. _textInput = textInput;
  451. if (textInput) {
  452. [self.textInputView setHidden:NO];
  453. [self.voiceInputBtn setHidden:YES];
  454. self.textInputView.inputView = nil;
  455. if (!self.textInputView.isFirstResponder && _inputBarStatus == ChatInputBarKeyboardStatus) {
  456. [self.textInputView becomeFirstResponder];
  457. }
  458. if (_inputBarStatus == ChatInputBarKeyboardStatus) {
  459. [self.textInputView reloadInputViews];
  460. }
  461. }
  462. }
  463. - (void)notifyTyping:(WFCCTypingType)type {
  464. double now = [[NSDate date] timeIntervalSince1970];
  465. if (self.lastTypingTime + TYPING_INTERVAL < now) {
  466. if ([self.delegate respondsToSelector:@selector(onTyping:)]) {
  467. [self.delegate onTyping:type];
  468. }
  469. self.lastTypingTime = now;
  470. }
  471. }
  472. - (void)setDraft:(NSString *)draft {
  473. NSError *__error = nil;
  474. NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:[draft dataUsingEncoding:NSUTF8StringEncoding]
  475. options:kNilOptions
  476. error:&__error];
  477. BOOL hasMentionInfo = NO;
  478. NSString *text = nil;
  479. NSMutableArray<WFCUMetionInfo *> *mentionInfos = [[NSMutableArray alloc] init];
  480. if (!__error) {
  481. if (dictionary[@"text"] != nil && [dictionary[@"mentions"] isKindOfClass:[NSArray class]]) {
  482. hasMentionInfo = YES;
  483. text = dictionary[@"text"];
  484. NSArray *mentions = dictionary[@"mentions"];
  485. [mentions enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  486. NSDictionary *dic = (NSDictionary *)obj;
  487. WFCUMetionInfo *mentionInfo = [[WFCUMetionInfo alloc] init];
  488. mentionInfo.target = dic[@"target"];
  489. mentionInfo.mentionType = [dic[@"type"] intValue];
  490. mentionInfo.range = NSMakeRange([dic[@"loc"] integerValue], [dic[@"len"] integerValue]);
  491. [mentionInfos addObject:mentionInfo];
  492. }];
  493. }
  494. }
  495. if (hasMentionInfo) {
  496. draft = text;
  497. }
  498. //防止弹出@选项
  499. if ([draft isEqualToString:@"@"]) {
  500. draft = @"@ ";
  501. }
  502. [self textView:self.textInputView shouldChangeTextInRange:NSMakeRange(0, 0) replacementText:draft];
  503. self.textInputView.text = draft;
  504. self.mentionInfos = mentionInfos;
  505. }
  506. - (NSString *)draft {
  507. if (self.mentionInfos.count) {
  508. NSMutableDictionary *dataDict = [NSMutableDictionary dictionary];
  509. [dataDict setObject:self.textInputView.text forKey:@"text"];
  510. NSMutableArray *mentions = [[NSMutableArray alloc] init];
  511. [self.mentionInfos enumerateObjectsUsingBlock:^(WFCUMetionInfo * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  512. NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];
  513. [dic setObject:obj.target forKey:@"target"];
  514. [dic setObject:@(obj.mentionType) forKey:@"type"];
  515. [dic setObject:@(obj.range.location) forKey:@"loc"];
  516. [dic setObject:@(obj.range.length) forKey:@"len"];
  517. [mentions addObject:dic];
  518. }];
  519. [dataDict setObject:mentions forKey:@"mentions"];
  520. NSData *data = [NSJSONSerialization dataWithJSONObject:dataDict
  521. options:kNilOptions
  522. error:nil];
  523. return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  524. } else {
  525. return self.textInputView.text;
  526. }
  527. }
  528. - (void)appendText:(NSString *)text {
  529. [self textView:self.textInputView shouldChangeTextInRange:NSMakeRange(self.textInputView.text.length, 0) replacementText:text];
  530. self.textInputView.text = [self.textInputView.text stringByAppendingString:text];
  531. }
  532. - (UIView *)emojInputView {
  533. if (!_emojInputView) {
  534. _emojInputView = [[WFCUFaceBoard alloc] init];
  535. ((WFCUFaceBoard*)_emojInputView).delegate = self;
  536. }
  537. return _emojInputView;
  538. }
  539. - (UIView *)pluginInputView {
  540. if (!_pluginInputView) {
  541. #if WFCU_SUPPORT_VOIP
  542. BOOL hasVoip = self.conversation.type == Single_Type || (self.conversation.type == Group_Type && [WFAVEngineKit sharedEngineKit].supportMultiCall);
  543. #else
  544. BOOL hasVoip = NO;
  545. #endif
  546. _pluginInputView = [[WFCUPluginBoardView alloc] initWithDelegate:self withVoip:hasVoip];
  547. }
  548. return _pluginInputView;
  549. }
  550. - (void)keyboardWillShow:(NSNotification *)notification {
  551. if (![self.textInputView isFirstResponder]) {
  552. return;
  553. }
  554. NSDictionary *userInfo = [notification userInfo];
  555. NSValue *value = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
  556. CGRect keyboardRect = [value CGRectValue];
  557. int height = keyboardRect.size.height - kTabbarSafeBottomMargin;
  558. CGFloat duration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
  559. CGRect frame = CGRectMake(0, self.superview.bounds.size.height - self.bounds.size.height - height, self.superview.bounds.size.width, self.bounds.size.height);
  560. [self.delegate willChangeFrame:frame withDuration:duration keyboardShowing:YES];
  561. self.backupFrame = frame;
  562. [UIView animateWithDuration:duration animations:^{
  563. self.frame = frame;
  564. }];
  565. }
  566. - (void)keyboardWillHide:(NSNotification *)notification {
  567. NSDictionary *userInfo = [notification userInfo];
  568. CGFloat duration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
  569. CGRect frame = CGRectMake(0, self.superview.bounds.size.height - self.bounds.size.height, self.superview.bounds.size.width, self.bounds.size.height);
  570. [self.delegate willChangeFrame:frame withDuration:duration keyboardShowing:NO];
  571. self.backupFrame = frame;
  572. [UIView animateWithDuration:duration animations:^{
  573. self.frame = frame;
  574. }];
  575. }
  576. -(void)keyboardDidHide:(NSNotification *)notification{
  577. if ((self.emojInput || self.pluginInput || self.textInput) && self.inputBarStatus != ChatInputBarDefaultStatus) {
  578. [self.textInputView becomeFirstResponder];
  579. }
  580. }
  581. - (BOOL)appendMention:(NSString *)userId name:(NSString *)userName {
  582. if (self.conversation.type == Group_Type) {
  583. NSString *mentionText = [NSString stringWithFormat:@"@%@ ", userName];
  584. BOOL needDelay = NO;
  585. if(self.inputBarStatus == ChatInputBarDefaultStatus || self.inputBarStatus == ChatInputBarPluginStatus || self.inputBarStatus == ChatInputBarRecordStatus) {
  586. self.inputBarStatus = ChatInputBarKeyboardStatus;
  587. needDelay = YES;
  588. }
  589. if (needDelay) {
  590. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  591. [self didMentionType:1 user:userId range:NSMakeRange(self.textInputView.selectedRange.location, mentionText.length) text:mentionText];
  592. });
  593. } else {
  594. [self didMentionType:1 user:userId range:NSMakeRange(self.textInputView.selectedRange.location, mentionText.length) text:mentionText];
  595. }
  596. return YES;
  597. } else {
  598. return NO;
  599. }
  600. }
  601. - (void)paste:(id)sender {
  602. [self.textInputView paste:sender];
  603. }
  604. #pragma mark - AVAudioRecorderDelegate
  605. - (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag {
  606. NSLog(@"record finish:%d", flag);
  607. if (!flag) {
  608. return;
  609. }
  610. if (self.recordCanceled) {
  611. return;
  612. }
  613. if (self.seconds < 1) {
  614. NSLog(@"record time too short");
  615. [[[UIAlertView alloc] initWithTitle:@"警告" message:@"录音时间太短了" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil] show];
  616. return;
  617. }
  618. [self.delegate recordDidEnd:[recorder.url path] duration:self.seconds error:nil];
  619. [[NSFileManager defaultManager] removeItemAtURL:recorder.url error:nil];
  620. }
  621. #pragma mark - FaceBoardDelegate
  622. - (void)didTouchEmoj:(NSString *)emojString {
  623. NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc]initWithString:emojString];
  624. UIFont *font = [UIFont fontWithName:@"Heiti SC-Bold" size:16];
  625. [attStr addAttribute:(__bridge NSString*)kCTFontAttributeName value:(id)CFBridgingRelease(CTFontCreateWithName((CFStringRef)font.fontName,
  626. 16,
  627. NULL)) range:NSMakeRange(0, emojString.length)];
  628. NSInteger cursorPosition;
  629. if (self.textInputView.selectedTextRange) {
  630. cursorPosition = self.textInputView.selectedRange.location ;
  631. } else {
  632. cursorPosition = 0;
  633. }
  634. //获取光标位置
  635. if(cursorPosition> self.textInputView.textStorage.length)
  636. cursorPosition = self.textInputView.textStorage.length;
  637. [self.textInputView.textStorage
  638. insertAttributedString:attStr atIndex:cursorPosition];
  639. NSRange range;
  640. range.location = self.textInputView.selectedRange.location + emojString.length;
  641. range.length = 1;
  642. self.textInputView.selectedRange = range;
  643. }
  644. - (void)didTouchBackEmoj {
  645. [self.textInputView deleteBackward];
  646. }
  647. - (void)didTouchSendEmoj {
  648. [self sendAndCleanTextView];
  649. }
  650. - (void)sendAndCleanTextView {
  651. [self.delegate didTouchSend:self.textInputView.text withMentionInfos:self.mentionInfos];
  652. self.textInputView.text = nil;
  653. [self.mentionInfos removeAllObjects];
  654. [self changeTextViewHeight:32 needUpdateText:NO updateRange:NSMakeRange(0, 0)];
  655. }
  656. - (void)didSelectedSticker:(NSString *)stickerPath {
  657. if ([self.delegate respondsToSelector:@selector(didSelectSticker:)]) {
  658. [self.delegate didSelectSticker:stickerPath];
  659. }
  660. }
  661. #pragma mark - UITextViewDelegate
  662. - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text{
  663. if ([text isEqualToString:@"\n"]){ //判断输入的字是否是回车,即按下return
  664. [self sendAndCleanTextView];
  665. return NO;
  666. }
  667. BOOL needUpdateText = NO;
  668. if(self.conversation.type == Group_Type) {
  669. if ([text isEqualToString:@"@"]) {
  670. WFCUContactListViewController *pvc = [[WFCUContactListViewController alloc] init];
  671. pvc.selectContact = YES;
  672. pvc.multiSelect = NO;
  673. NSMutableArray *disabledUser = [[NSMutableArray alloc] init];
  674. [disabledUser addObject:[WFCCNetworkService sharedInstance].userId];
  675. pvc.disableUsers = disabledUser;
  676. NSMutableArray *candidateUser = [[NSMutableArray alloc] init];
  677. NSArray<WFCCGroupMember *> *members = [[WFCCIMService sharedWFCIMService] getGroupMembers:self.conversation.target forceUpdate:NO];
  678. for (WFCCGroupMember *member in members) {
  679. [candidateUser addObject:member.memberId];
  680. }
  681. pvc.candidateUsers = candidateUser;
  682. pvc.withoutCheckBox = YES;
  683. __weak typeof(self)ws = self;
  684. WFCCGroupInfo *groupInfo = [[WFCCIMService sharedWFCIMService] getGroupInfo:self.conversation.target refresh:NO];
  685. WFCCGroupMember *member = [[WFCCIMService sharedWFCIMService] getGroupMember:self.conversation.target memberId:[WFCCNetworkService sharedInstance].userId];
  686. if ([groupInfo.owner isEqualToString:[WFCCNetworkService sharedInstance].userId] || member.type == Member_Type_Manager) {
  687. pvc.showMentionAll = YES;
  688. pvc.mentionAll = ^{
  689. NSString *text = WFCString(@"@All");
  690. [ws didMentionType:2 user:nil range:NSMakeRange(range.location, text.length) text:text];
  691. };
  692. }
  693. pvc.selectResult = ^(NSArray<NSString *> *contacts) {
  694. if (contacts.count == 1) {
  695. WFCCUserInfo *userInfo = [[WFCCIMService sharedWFCIMService] getUserInfo:[contacts objectAtIndex:0] inGroup:self.conversation.target refresh:NO];
  696. NSString *name = userInfo.displayName;
  697. if (userInfo.groupAlias.length) {
  698. name = userInfo.groupAlias;
  699. }
  700. NSString *text = [NSString stringWithFormat:@"@%@ ", name];
  701. [ws didMentionType:1 user:[contacts objectAtIndex:0] range:NSMakeRange(range.location, text.length) text:text];
  702. } else {
  703. [ws didCancelMentionAtRange:range];
  704. }
  705. };
  706. pvc.cancelSelect = ^(void) {
  707. [ws didCancelMentionAtRange:range];
  708. };
  709. pvc.disableUsersSelected = YES;
  710. UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController:pvc];
  711. [[self.delegate requireNavi] presentViewController:navi animated:YES completion:nil];
  712. return NO;
  713. }
  714. if (text.length == 0) {
  715. WFCUMetionInfo *deletedMention;
  716. for (WFCUMetionInfo *mentionInfo in self.mentionInfos) {
  717. if ((mentionInfo.range.location >= range.location && mentionInfo.range.location < range.location + range.length) ||
  718. (range.location >= mentionInfo.range.location && range.location < mentionInfo.range.location + mentionInfo.range.length)) {
  719. deletedMention = mentionInfo;
  720. }
  721. }
  722. if (deletedMention) {
  723. range = deletedMention.range;
  724. [self.mentionInfos removeObject:deletedMention];
  725. needUpdateText = YES;
  726. }
  727. } else {
  728. for (WFCUMetionInfo *mentionInfo in self.mentionInfos) {
  729. if (range.location <= mentionInfo.range.location) {
  730. mentionInfo.range = NSMakeRange(mentionInfo.range.location - range.length + text.length, mentionInfo.range.length);
  731. }
  732. }
  733. }
  734. }
  735. NSString *oldStr = textView.text;
  736. NSString *newStr = [oldStr stringByReplacingCharactersInRange:range withString:text];
  737. CGFloat textAreaWidth = textView.frame.size.width - 2 * textView.textContainer.lineFragmentPadding;
  738. CGSize size = [WFCUUtilities getTextDrawingSize:newStr font:[UIFont systemFontOfSize:16] constrainedSize:CGSizeMake(textAreaWidth, 1000)];
  739. [self changeTextViewHeight:size.height needUpdateText:needUpdateText updateRange:range];
  740. return YES;
  741. }
  742. - (void)changeTextViewHeight:(CGFloat)height needUpdateText:(BOOL)needUpdateText updateRange:(NSRange)range {
  743. CGRect tvFrame = self.textInputView.frame;
  744. CGRect baseFrame = self.frame;
  745. CGRect voiceFrame = self.voiceSwitchBtn.frame;
  746. CGRect emojFrame = self.emojSwitchBtn.frame;
  747. CGRect extendFrame = self.pluginSwitchBtn.frame;
  748. CGFloat diff = 0;
  749. if (height <= 32.f) {
  750. tvFrame.size.height = 32.f;
  751. diff = (48.f - baseFrame.size.height);
  752. baseFrame.size.height = 48.f;
  753. } else if (height > 32.f && height < 50.f) {
  754. tvFrame.size.height = 50.f;
  755. diff = (66.f - baseFrame.size.height);
  756. baseFrame.size.height = 66.f;
  757. } else {
  758. tvFrame.size.height = 65.f;
  759. diff = (81.f - baseFrame.size.height);
  760. baseFrame.size.height = 81.f;
  761. }
  762. baseFrame.origin.y -= diff;
  763. voiceFrame.origin.y += diff;
  764. emojFrame.origin.y += diff;
  765. extendFrame.origin.y += diff;
  766. float duration = 0.5f;
  767. [self.delegate willChangeFrame:baseFrame withDuration:duration keyboardShowing:YES];
  768. self.backupFrame = baseFrame;
  769. __weak typeof(self)ws = self;
  770. [UIView animateWithDuration:duration animations:^{
  771. ws.textInputView.frame = tvFrame;
  772. ws.inputCoverView.frame = ws.textInputView.bounds;
  773. self.frame = baseFrame;
  774. self.voiceSwitchBtn.frame = voiceFrame;
  775. self.emojSwitchBtn.frame = emojFrame;
  776. self.pluginSwitchBtn.frame = extendFrame;
  777. if(needUpdateText) {
  778. [ws.textInputView.textStorage replaceCharactersInRange:range withString:@" "];
  779. }
  780. }];
  781. }
  782. - (void)textViewDidChangeSelection:(UITextView *)textView {
  783. if (self.textInputView == textView && self.conversation.type == Group_Type) {
  784. NSRange range = textView.selectedRange;
  785. for (WFCUMetionInfo *mention in self.mentionInfos) {
  786. if (range.location > mention.range.location && range.location < mention.range.location + mention.range.length) {
  787. if (range.length == 0) {
  788. if(range.location == mention.range.location + mention.range.length - 1) {
  789. range.location = mention.range.location;
  790. } else {
  791. range = NSMakeRange(mention.range.location + mention.range.length, 0);
  792. }
  793. } else {
  794. long length = range.length - (mention.range.location + mention.range.length) + range.location;
  795. if (length < 0) {
  796. length = 0;
  797. }
  798. range = NSMakeRange(mention.range.location + mention.range.length, length);
  799. }
  800. textView.selectedRange = range;
  801. break;
  802. }
  803. }
  804. }
  805. }
  806. - (void)textViewDidChange:(UITextView *)textView {
  807. if (textView.text.length > 0) {
  808. [self notifyTyping:0];
  809. }
  810. }
  811. - (BOOL)textViewShouldBeginEditing:(UITextView *)textView {
  812. if (self.inputBarStatus == ChatInputBarDefaultStatus) {
  813. self.inputBarStatus = ChatInputBarKeyboardStatus;
  814. }
  815. return YES;
  816. }
  817. - (BOOL)textViewShouldEndEditing:(UITextView *)textView {
  818. return YES;
  819. }
  820. #pragma mark - PluginBoardViewDelegate
  821. - (void)onItemClicked:(NSUInteger)itemTag {
  822. UINavigationController *navi = [self.delegate requireNavi];
  823. self.inputBarStatus = ChatInputBarDefaultStatus;
  824. if (itemTag == 1) {
  825. #if 0
  826. UIImagePickerController *picker = [[UIImagePickerController alloc] init];
  827. picker.delegate = self;
  828. #if TARGET_IPHONE_SIMULATOR
  829. picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
  830. #else
  831. if (itemTag == 1) {
  832. picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
  833. } else if(itemTag == 2){
  834. picker.sourceType = UIImagePickerControllerSourceTypeCamera;
  835. }
  836. #endif
  837. picker.videoExportPreset = AVAssetExportPresetPassthrough;
  838. picker.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:picker.sourceType];
  839. [navi presentViewController:picker animated:YES completion:nil];
  840. [self checkAndAlertCameraAccessRight];
  841. #else
  842. DNImagePickerController *imagePicker = [[DNImagePickerController alloc] init];
  843. imagePicker.imagePickerDelegate = self;
  844. imagePicker.modalPresentationStyle = UIModalPresentationFullScreen;
  845. [navi presentViewController:imagePicker animated:YES completion:nil];
  846. #endif
  847. } else if(itemTag == 2) {
  848. #if TARGET_IPHONE_SIMULATOR
  849. [self makeToast:@"模拟器不支持相机" duration:1 position:CSToastPositionCenter];
  850. UIImagePickerController *picker = [[UIImagePickerController alloc] init];
  851. picker.delegate = self;
  852. picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
  853. [navi presentViewController:picker animated:YES completion:nil];
  854. [self checkAndAlertCameraAccessRight];
  855. #else
  856. KZVideoViewController *videoVC = [[KZVideoViewController alloc] init];
  857. videoVC.delegate = self;
  858. [videoVC startAnimationWithType:KZVideoViewShowTypeSingle selectExist:NO];
  859. double now = [[NSDate date] timeIntervalSince1970];
  860. [self notifyTyping:2];
  861. #endif
  862. } else if(itemTag == 3){
  863. WFCULocationViewController *vc = [[WFCULocationViewController alloc] initWithDelegate:self];
  864. UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
  865. [navi presentViewController:nav animated:YES completion:nil];
  866. [self notifyTyping:3];
  867. return;
  868. } else if(itemTag == 4) {
  869. #if WFCU_SUPPORT_VOIP
  870. UIActionSheet *actionSheet =
  871. [[UIActionSheet alloc] initWithTitle:nil
  872. delegate:self
  873. cancelButtonTitle:WFCString(@"Cancel")
  874. destructiveButtonTitle:@"视频"
  875. otherButtonTitles:@"音频", nil];
  876. [actionSheet showInView:self.parentView];
  877. #endif
  878. } else if(itemTag == 5) {
  879. NSArray*documentTypes =@[
  880. @"public.content",
  881. @"public.data",
  882. @"com.microsoft.powerpoint.ppt",
  883. @"com.microsoft.word.doc",
  884. @"com.microsoft.excel.xls",
  885. @"com.microsoft.powerpoint.pptx",
  886. @"com.microsoft.word.docx",
  887. @"com.microsoft.excel.xlsx",
  888. @"public.avi",
  889. @"public.3gpp",
  890. @"public.mpeg-4",
  891. @"com.compuserve.gif",
  892. @"public.jpeg",
  893. @"public.png",
  894. @"public.plain-text",
  895. @"com.adobe.pdf"
  896. ];
  897. UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:documentTypes inMode:UIDocumentPickerModeOpen];
  898. picker.delegate = self;
  899. if (@available(iOS 11.0, *)) {
  900. picker.allowsMultipleSelection = YES;
  901. }
  902. picker.modalPresentationStyle = UIModalPresentationFullScreen;
  903. [navi presentViewController:picker animated:YES completion:nil];
  904. [self notifyTyping:4];
  905. }
  906. }
  907. - (void)checkAndAlertCameraAccessRight {
  908. AVAuthorizationStatus authStatus =
  909. [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
  910. if (authStatus == AVAuthorizationStatusDenied ||
  911. authStatus == AVAuthorizationStatusRestricted) {
  912. UIAlertView *alertView = [[UIAlertView alloc]
  913. initWithTitle:@"拍照权限"
  914. message:@"需要拍照权限,请在设置里打开"
  915. delegate:nil
  916. cancelButtonTitle:@"确认"
  917. otherButtonTitles:nil, nil];
  918. [alertView show];
  919. }
  920. }
  921. #pragma mark UIDocumentDelegate 文件选择回调
  922. - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
  923. __block NSMutableArray *arr = [NSMutableArray array];
  924. for (NSURL *url in urls) {
  925. //获取授权
  926. BOOL fileUrlAuthozied = [url startAccessingSecurityScopedResource];
  927. if(fileUrlAuthozied){
  928. //通过文件协调工具来得到新的文件地址,以此得到文件保护功能
  929. NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] init];
  930. NSError *error;
  931. [fileCoordinator coordinateReadingItemAtURL:url options:0 error:&error byAccessor:^(NSURL *newURL) {
  932. if (!error) {
  933. NSData *fileData = [NSData dataWithContentsOfURL:newURL];
  934. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  935. NSString *documentPath = [paths lastObject];
  936. NSString *tempDir = [documentPath stringByAppendingPathComponent:@"wf_send_files"];
  937. NSFileManager *fileManager = [NSFileManager defaultManager];
  938. bool isDir = NO;
  939. if (![fileManager fileExistsAtPath:tempDir isDirectory:&isDir]) {
  940. isDir = YES;
  941. NSError *err;
  942. if(![fileManager createDirectoryAtPath:tempDir withIntermediateDirectories:YES attributes:nil error:&err]) {
  943. NSLog(@"Error, create temp folder error");
  944. return;
  945. }
  946. if (err) {
  947. NSLog(@"Error, create temp folder error:%@", err);
  948. return;
  949. }
  950. }
  951. if (!isDir) {
  952. NSLog(@"Error, create temp folder error");
  953. return;
  954. }
  955. NSString *desFileName = [tempDir stringByAppendingPathComponent:[newURL lastPathComponent]];
  956. [fileData writeToFile:desFileName atomically:YES];
  957. [arr addObject:desFileName];
  958. }
  959. }];
  960. [url stopAccessingSecurityScopedResource];
  961. }else{
  962. NSLog(@"授权失败");
  963. }
  964. }
  965. [self.delegate didSelectFiles:arr];
  966. }
  967. #pragma mark - UIImagePickerControllerDelegate<NSObject>
  968. //- (void)imagePickerController:(UIImagePickerController *)picker
  969. // didFinishPickingImage:(UIImage *)image
  970. // editingInfo:(NSDictionary *)editingInfo {
  971. // [picker dismissViewControllerAnimated:YES completion:nil];
  972. // [self.delegate imageDidCapture:image];
  973. //}
  974. - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey, id> *)info {
  975. NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType];
  976. if([mediaType isEqualToString:@"public.movie"]) {
  977. NSURL *videoURL = [info objectForKey:UIImagePickerControllerMediaURL];
  978. NSString *url = [videoURL absoluteString];
  979. url = [url stringByReplacingOccurrencesOfString:@"file:///private" withString:@""];
  980. //获取视频的thumbnail
  981. AVURLAsset *asset1 = [[AVURLAsset alloc] initWithURL:videoURL options:nil];
  982. AVAssetImageGenerator *generate1 = [[AVAssetImageGenerator alloc] initWithAsset:asset1];
  983. generate1.appliesPreferredTrackTransform = YES;
  984. NSError *err = NULL;
  985. CMTime time = CMTimeMake(1, 2);
  986. CGImageRef oneRef = [generate1 copyCGImageAtTime:time actualTime:NULL error:&err];
  987. UIImage *thumbnail = [[UIImage alloc] initWithCGImage:oneRef];
  988. thumbnail = [WFCCUtilities generateThumbnail:thumbnail withWidth:120 withHeight:120];
  989. AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:videoURL options:nil];
  990. NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];
  991. NSString *CompressionVideoPaht = [WFCCUtilities getDocumentPathWithComponent:@"/VIDEO"];
  992. AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:@"AVAssetExportPresetMediumQuality"];
  993. NSDateFormatter *formater = [[NSDateFormatter alloc] init];// 用时间, 给文件重新命名, 防止视频存储覆盖,
  994. [formater setDateFormat:@"yyyy-MM-dd_HH-mm-ss"];
  995. NSFileManager *manager = [NSFileManager defaultManager];
  996. BOOL isExists = [manager fileExistsAtPath:CompressionVideoPaht];
  997. if (!isExists) {
  998. [manager createDirectoryAtPath:CompressionVideoPaht withIntermediateDirectories:YES attributes:nil error:nil];
  999. }
  1000. //
  1001. NSString *resultPath = [CompressionVideoPaht stringByAppendingPathComponent:[NSString stringWithFormat:@"outputJFVideo-%@.mov", [formater stringFromDate:[NSDate date]]]];
  1002. NSLog(@"resultPath = %@",resultPath);
  1003. exportSession.outputURL = [NSURL fileURLWithPath:resultPath];
  1004. exportSession.outputFileType = AVFileTypeMPEG4;
  1005. exportSession.shouldOptimizeForNetworkUse = YES;
  1006. MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:picker.view animated:YES];
  1007. hud.label.text = @"处理中...";
  1008. [hud showAnimated:YES];
  1009. __weak typeof(self)ws = self;
  1010. [exportSession exportAsynchronouslyWithCompletionHandler:^(void)
  1011. {
  1012. if (exportSession.status == AVAssetExportSessionStatusCompleted) {
  1013. NSData *data = [NSData dataWithContentsOfFile:resultPath];
  1014. float memorySize = (float)data.length / 1024 / 1024;
  1015. NSLog(@"视频压缩后大小 %f", memorySize);
  1016. dispatch_async(dispatch_get_main_queue(), ^{
  1017. [hud hideAnimated:YES];
  1018. [picker dismissViewControllerAnimated:YES completion:nil];
  1019. [ws.delegate videoDidCapture:resultPath thumbnail:thumbnail duration:10];
  1020. });
  1021. } else {
  1022. dispatch_async(dispatch_get_main_queue(), ^{
  1023. [hud hideAnimated:YES];
  1024. [picker dismissViewControllerAnimated:YES completion:nil];
  1025. });
  1026. NSLog(@"压缩失败");
  1027. }
  1028. }];
  1029. } else if ([mediaType isEqualToString:@"public.image"]) {
  1030. [picker dismissViewControllerAnimated:YES completion:nil];
  1031. UIImage* image = [info objectForKey:UIImagePickerControllerEditedImage];
  1032. if (!image)
  1033. image = [info objectForKey:UIImagePickerControllerOriginalImage];
  1034. [self.delegate imageDidCapture:image];
  1035. }
  1036. }
  1037. - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
  1038. [picker dismissViewControllerAnimated:YES completion:nil];
  1039. }
  1040. #pragma mark - LocationViewControllerDelegate <NSObject>
  1041. - (void)onSendLocation:(WFCULocationPoint *)locationPoint {
  1042. [self.delegate locationDidSelect:locationPoint.coordinate locationName:locationPoint.title mapScreenShot:locationPoint.thumbnail];
  1043. }
  1044. #pragma mark - UIActionSheetDelegate
  1045. - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
  1046. if (buttonIndex == 0) {
  1047. #if WFCU_SUPPORT_VOIP
  1048. [self.delegate didTouchVideoBtn:NO];
  1049. #endif
  1050. } else if(buttonIndex == 1) {
  1051. #if WFCU_SUPPORT_VOIP
  1052. [self.delegate didTouchVideoBtn:YES];
  1053. #endif
  1054. }
  1055. }
  1056. #pragma mark - KZVideoViewControllerDelegate
  1057. - (void)videoViewController:(KZVideoViewController *)videoController didCaptureImage:(UIImage *)image {
  1058. [self.delegate imageDidCapture:image];
  1059. }
  1060. - (void)videoViewController:(KZVideoViewController *)videoController didRecordVideo:(KZVideoModel *)videoModel {
  1061. [self.delegate videoDidCapture:videoModel.videoAbsolutePath thumbnail:[UIImage imageWithContentsOfFile:videoModel.thumAbsolutePath] duration:10];
  1062. }
  1063. - (void)videoViewControllerDidCancel:(KZVideoViewController *)videoController {
  1064. }
  1065. #pragma mark - WFCUMentionUserDelegate
  1066. - (void)didMentionType:(int)type user:(NSString *)userId range:(NSRange)range text:(NSString *)text {
  1067. [self textView:self.textInputView shouldChangeTextInRange:NSMakeRange(range.location, 0) replacementText:text];
  1068. [self.mentionInfos addObject:[[WFCUMetionInfo alloc] initWithType:type target:userId range:NSMakeRange(range.location, range.length)]];
  1069. NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc]initWithString:text];
  1070. UIFont *font = [UIFont fontWithName:@"Heiti SC-Bold" size:16];
  1071. [attStr addAttribute:(__bridge NSString*)kCTFontAttributeName value:(id)CFBridgingRelease(CTFontCreateWithName((CFStringRef)font.fontName,
  1072. 16,
  1073. NULL)) range:NSMakeRange(0, text.length)];
  1074. [self.textInputView.textStorage
  1075. insertAttributedString:attStr atIndex:range.location];
  1076. range.location += range.length;
  1077. range.length = 0;
  1078. self.textInputView.selectedRange = range;
  1079. dispatch_async(dispatch_get_main_queue(), ^{
  1080. if (!self.textInputView.isFirstResponder) {
  1081. [self.textInputView becomeFirstResponder];
  1082. }
  1083. });
  1084. }
  1085. - (void)didCancelMentionAtRange:(NSRange)range {
  1086. NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc]initWithString:@"@"];
  1087. UIFont *font = [UIFont fontWithName:@"Heiti SC-Bold" size:16];
  1088. [attStr addAttribute:(__bridge NSString*)kCTFontAttributeName value:(id)CFBridgingRelease(CTFontCreateWithName((CFStringRef)font.fontName,
  1089. 16,
  1090. NULL)) range:NSMakeRange(0, 1)];
  1091. [self.textInputView.textStorage
  1092. insertAttributedString:attStr atIndex:range.location];
  1093. range.location += 1;
  1094. range.length = 0;
  1095. self.textInputView.selectedRange = range;
  1096. dispatch_async(dispatch_get_main_queue(), ^{
  1097. if (!self.textInputView.isFirstResponder) {
  1098. [self.textInputView becomeFirstResponder];
  1099. }
  1100. });
  1101. }
  1102. #pragma mark - DNImagePickerControllerDelegate
  1103. - (void)dnImagePickerController:(DNImagePickerController *)imagePicker
  1104. sendImages:(NSArray *)images
  1105. isFullImage:(BOOL)isFullImage {
  1106. MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.parentView animated:YES];
  1107. hud.label.text = @"处理中...";
  1108. [hud showAnimated:YES];
  1109. [self recursiveHandle:images isFullImage:isFullImage];
  1110. }
  1111. - (void)dnImagePickerControllerDidCancel:(DNImagePickerController *)imagePicker {
  1112. [imagePicker dismissViewControllerAnimated:YES completion:nil];
  1113. }
  1114. - (void)convertAvcompositionToAvasset:(AVComposition *)composition completion:(void (^)(AVAsset *asset))completion {
  1115. // 导出视频
  1116. AVAssetExportSession *exporter = [AVAssetExportSession exportSessionWithAsset:composition presetName:AVAssetExportPresetHighestQuality];
  1117. // 生成一个文件路径
  1118. NSInteger randNumber = arc4random();
  1119. NSString *exportPath = [NSTemporaryDirectory() stringByAppendingString:[NSString stringWithFormat:@"%ldvideo.mov", randNumber]];
  1120. NSURL *exportURL = [NSURL fileURLWithPath:exportPath];
  1121. // 导出
  1122. if (exporter) {
  1123. exporter.outputURL = exportURL; // 设置路径
  1124. exporter.outputFileType = AVFileTypeQuickTimeMovie;
  1125. exporter.shouldOptimizeForNetworkUse = YES;
  1126. [exporter exportAsynchronouslyWithCompletionHandler:^{
  1127. dispatch_async(dispatch_get_main_queue(), ^{
  1128. if (AVAssetExportSessionStatusCompleted == exporter.status) { // 导出完成
  1129. NSURL *URL = exporter.outputURL;
  1130. AVAsset *avAsset = [AVAsset assetWithURL:URL];
  1131. if (completion) {
  1132. completion(avAsset);
  1133. }
  1134. } else {
  1135. if (completion) {
  1136. completion(nil);
  1137. }
  1138. }
  1139. });
  1140. }];
  1141. } else {
  1142. dispatch_async(dispatch_get_main_queue(), ^{
  1143. if (completion) {
  1144. completion(nil);
  1145. }
  1146. });
  1147. }
  1148. }
  1149. - (void)handleVideo:(NSURL *)url photos:(NSMutableArray<DNAsset *> *)photos isFullImage:(BOOL)isFullImage {
  1150. AVURLAsset *asset1 = [[AVURLAsset alloc] initWithURL:url options:nil];
  1151. AVAssetImageGenerator *generate1 = [[AVAssetImageGenerator alloc] initWithAsset:asset1];
  1152. generate1.appliesPreferredTrackTransform = YES;
  1153. NSError *err = NULL;
  1154. CMTime time = CMTimeMake(1, 2);
  1155. CGImageRef oneRef = [generate1 copyCGImageAtTime:time actualTime:NULL error:&err];
  1156. UIImage *thumbnail = [[UIImage alloc] initWithCGImage:oneRef];
  1157. thumbnail = [WFCCUtilities generateThumbnail:thumbnail withWidth:120 withHeight:120];
  1158. AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:url options:nil];
  1159. NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];
  1160. NSString *CompressionVideoPaht = [WFCCUtilities getDocumentPathWithComponent:@"/VIDEO"];
  1161. AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:@"AVAssetExportPresetMediumQuality"];
  1162. NSDateFormatter *formater = [[NSDateFormatter alloc] init];// 用时间, 给文件重新命名, 防止视频存储覆盖,
  1163. [formater setDateFormat:@"yyyy-MM-dd_HH-mm-ss"];
  1164. NSFileManager *manager = [NSFileManager defaultManager];
  1165. BOOL isExists = [manager fileExistsAtPath:CompressionVideoPaht];
  1166. if (!isExists) {
  1167. [manager createDirectoryAtPath:CompressionVideoPaht withIntermediateDirectories:YES attributes:nil error:nil];
  1168. }
  1169. //
  1170. NSString *resultPath = [CompressionVideoPaht stringByAppendingPathComponent:[NSString stringWithFormat:@"outputJFVideo-%@.mov", [formater stringFromDate:[NSDate date]]]];
  1171. NSLog(@"resultPath = %@",resultPath);
  1172. exportSession.outputURL = [NSURL fileURLWithPath:resultPath];
  1173. exportSession.outputFileType = AVFileTypeMPEG4;
  1174. exportSession.shouldOptimizeForNetworkUse = YES;
  1175. __weak typeof(self)ws = self;
  1176. [exportSession exportAsynchronouslyWithCompletionHandler:^(void)
  1177. {
  1178. if (exportSession.status == AVAssetExportSessionStatusCompleted) {
  1179. NSData *data = [NSData dataWithContentsOfFile:resultPath];
  1180. float memorySize = (float)data.length / 1024 / 1024;
  1181. NSLog(@"视频压缩后大小 %f", memorySize);
  1182. dispatch_async(dispatch_get_main_queue(), ^{
  1183. [ws.delegate videoDidCapture:resultPath thumbnail:thumbnail duration:10];
  1184. });
  1185. [ws recursiveHandle:photos isFullImage:isFullImage];
  1186. } else {
  1187. dispatch_async(dispatch_get_main_queue(), ^{
  1188. });
  1189. NSLog(@"压缩失败");
  1190. }
  1191. }];
  1192. }
  1193. - (void)recursiveHandle:(NSMutableArray<DNAsset *> *)photos isFullImage:(BOOL)isFullImage {
  1194. if (photos.count == 0) {
  1195. dispatch_async(dispatch_get_main_queue(), ^{
  1196. [MBProgressHUD hideHUDForView:self.parentView animated:YES];
  1197. });
  1198. }else{
  1199. DNAsset *item = photos[0];
  1200. [photos removeObjectAtIndex:0];
  1201. __weak typeof(self) weakself = self;
  1202. if (item.asset.mediaType == PHAssetMediaTypeVideo) {
  1203. PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init];
  1204. options.version = PHImageRequestOptionsVersionCurrent;
  1205. options.deliveryMode = PHVideoRequestOptionsDeliveryModeAutomatic;
  1206. PHAsset *phAsset = item.asset;
  1207. PHImageManager *manager = [PHImageManager defaultManager];
  1208. [manager requestAVAssetForVideo:phAsset options:options resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
  1209. if ([asset isKindOfClass:[AVComposition class]]) {
  1210. [weakself convertAvcompositionToAvasset:(AVComposition *)asset completion:^(AVAsset *asset) {
  1211. AVURLAsset *urlAsset = (AVURLAsset *)asset;
  1212. [weakself handleVideo:urlAsset.URL photos:photos isFullImage:isFullImage];
  1213. }];
  1214. } else {
  1215. AVURLAsset *urlAsset = (AVURLAsset *)asset;
  1216. [weakself handleVideo:urlAsset.URL photos:photos isFullImage:isFullImage];
  1217. }
  1218. }];
  1219. } else if(item.asset.mediaType == PHAssetMediaTypeImage) {
  1220. PHImageRequestOptions *imageRequestOption = [[PHImageRequestOptions alloc] init];
  1221. imageRequestOption.networkAccessAllowed = YES;
  1222. PHCachingImageManager *cachingImageManager = [[PHCachingImageManager alloc] init];
  1223. cachingImageManager.allowsCachingHighQualityImages = NO;
  1224. [cachingImageManager
  1225. requestImageDataForAsset:item.asset
  1226. options:imageRequestOption
  1227. resultHandler:^(NSData *_Nullable imageData, NSString *_Nullable dataUTI,
  1228. UIImageOrientation orientation, NSDictionary *_Nullable info) {
  1229. BOOL downloadFinined = (![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey] && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue]);
  1230. if (downloadFinined) {
  1231. if ([weakself.delegate respondsToSelector:@selector(imageDidCapture:)]) {
  1232. [weakself.delegate imageDidCapture:[UIImage imageWithData:imageData]];
  1233. }
  1234. [weakself recursiveHandle:photos isFullImage:isFullImage];
  1235. }
  1236. if ([info objectForKey:PHImageErrorKey]) {
  1237. [weakself.parentView makeToast:@"下载图片失败"];
  1238. [weakself recursiveHandle:photos isFullImage:isFullImage];
  1239. }
  1240. }];
  1241. }
  1242. }
  1243. }
  1244. - (void)dealloc {
  1245. [[NSNotificationCenter defaultCenter] removeObserver:self];
  1246. }
  1247. @end