WFCUChatInputBar.m 90 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115
  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 "UIView+Toast.h"
  18. #import <WFChatClient/WFCChatClient.h>
  19. #import "WFCUContactListViewController.h"
  20. #import "MBProgressHUD.h"
  21. #import "UIColor+YH.h"
  22. #import "WFCUPublicMenuButton.h""
  23. #if WFCU_SUPPORT_VOIP
  24. #import <WFAVEngineKit/WFAVEngineKit.h>
  25. #endif
  26. #import <Photos/Photos.h>
  27. #import "WFCUShareMessageView.h"
  28. #import "TYAlertController.h"
  29. #import "UIView+TYAlertView.h"
  30. #import <ZLPhotoBrowser/ZLPhotoBrowser-Swift.h>
  31. #import "WFCUConfigManager.h"
  32. #ifdef WFC_PTT
  33. #import <PttClient/WFPttClient.h>
  34. #import "WFPttViewController.h"
  35. #endif
  36. #import "WFCUImage.h"
  37. #define CHAT_INPUT_BAR_PADDING 8
  38. #define CHAT_INPUT_BAR_ICON_SIZE (CHAT_INPUT_BAR_HEIGHT - CHAT_INPUT_BAR_PADDING - CHAT_INPUT_BAR_PADDING)
  39. #define CHAT_INPUT_QUOTE_PADDING 5
  40. @implementation WFCUMetionInfo
  41. - (instancetype)initWithType:(int)type target:(NSString *)target range:(NSRange)range {
  42. self = [super init];
  43. if (self) {
  44. self.mentionType = type;
  45. self.target = target;
  46. self.range = range;
  47. }
  48. return self;
  49. }
  50. -(void)setRange:(NSRange)range {
  51. _range = range;
  52. }
  53. @end
  54. //@implementation TextInfo
  55. //
  56. //@end
  57. @interface WFCUChatInputBar () <UITextViewDelegate, WFCUFaceBoardDelegate, UIImagePickerControllerDelegate, AVAudioRecorderDelegate, AVAudioPlayerDelegate, WFCUPluginBoardViewDelegate, UIImagePickerControllerDelegate, LocationViewControllerDelegate, UIActionSheetDelegate, UIDocumentPickerDelegate, WFCUPublicMenuButtonDelegate>
  58. @property (nonatomic, assign)BOOL textInput;
  59. @property (nonatomic, assign)BOOL voiceInput;
  60. @property (nonatomic, assign)BOOL emojInput;
  61. @property (nonatomic, assign)BOOL pluginInput;
  62. @property (nonatomic, assign)BOOL publicInput;
  63. @property (nonatomic, strong)UIButton *publicSwitchBtn;
  64. @property (nonatomic, strong)UIButton *voiceSwitchBtn;
  65. #ifdef WFC_PTT
  66. @property (nonatomic, strong)UIButton *pttSwitchBtn;
  67. #endif
  68. @property (nonatomic, strong)UIButton *emojSwitchBtn;
  69. @property (nonatomic, strong)UIButton *pluginSwitchBtn;
  70. @property (nonatomic, strong)UITextView *textInputView;
  71. @property (nonatomic, strong)UIView *inputCoverView;
  72. @property (nonatomic, strong)UIButton *voiceInputBtn;
  73. @property (nonatomic, strong)UIView *inputContainer;
  74. @property (nonatomic, strong)UIView *publicContainer;
  75. @property (nonatomic, strong)UIView *emojInputView;
  76. @property (nonatomic, strong)UIView *pluginInputView;
  77. @property (nonatomic, strong)UIView *quoteContainerView;
  78. @property (nonatomic, strong)UILabel *quoteLabel;
  79. @property (nonatomic, strong)UIButton *quoteDeleteBtn;
  80. @property(nonatomic, weak)id<WFCUChatInputBarDelegate> delegate;
  81. @property (nonatomic, strong)WFCUVoiceRecordView *recordView;
  82. @property(nonatomic) AVAudioRecorder *recorder;
  83. @property(nonatomic) NSTimer *recordingTimer;
  84. @property(nonatomic) NSTimer *updateMeterTimer;
  85. @property(nonatomic, assign) int seconds;
  86. @property(nonatomic) BOOL recordCanceled;
  87. @property(nonatomic, weak)UIView *parentView;
  88. @property (nonatomic, strong)NSMutableArray<WFCUMetionInfo *> *mentionInfos;
  89. @property (nonatomic, strong)WFCCConversation *conversation;
  90. @property (nonatomic, assign)double lastTypingTime;
  91. @property (nonatomic, strong)UIColor *textInputViewTintColor;
  92. @property (nonatomic, assign)CGRect backupFrame;
  93. @property (nonatomic, strong)WFCCQuoteInfo *quoteInfo;
  94. @property(nonatomic, strong)NSTimer *saveDraftTimer;
  95. @property(nonatomic, strong)NSMutableArray<WFCUPublicMenuButton *> *menuButtons;
  96. @end
  97. @implementation WFCUChatInputBar
  98. - (instancetype)initWithSuperView:(UIView *)parentView conversation:(WFCCConversation *)conversation delegate:(id<WFCUChatInputBarDelegate>)delegate {
  99. self = [super initWithFrame:CGRectMake(0, parentView.bounds.size.height - CHAT_INPUT_BAR_HEIGHT, parentView.bounds.size.width, CHAT_INPUT_BAR_HEIGHT)];
  100. if (self) {
  101. [parentView addSubview:self];
  102. self.delegate = delegate;
  103. self.parentView = parentView;
  104. self.mentionInfos = [[NSMutableArray alloc] init];
  105. self.conversation = conversation;
  106. self.lastTypingTime = 0;
  107. self.backupFrame = CGRectZero;
  108. [self initSubViews];
  109. }
  110. return self;
  111. }
  112. - (void)initSubViews {
  113. [[NSNotificationCenter defaultCenter] addObserver:self
  114. selector:@selector(keyboardWillShow:)
  115. name:UIKeyboardWillShowNotification
  116. object:nil];
  117. [[NSNotificationCenter defaultCenter] addObserver:self
  118. selector:@selector(keyboardWillHide:)
  119. name:UIKeyboardWillHideNotification
  120. object:nil];
  121. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidHide:) name:UIKeyboardDidHideNotification object:nil];
  122. [[NSNotificationCenter defaultCenter] addObserver:self
  123. selector:@selector(onAppResume)
  124. name:UIApplicationWillEnterForegroundNotification
  125. object:nil];
  126. self.backgroundColor = [UIColor colorWithHexString:@"0xf7f7f7"];
  127. NSArray<WFCCChannelMenu *> *menus = nil;
  128. if (self.conversation.type == Channel_Type) {
  129. WFCCChannelInfo *channelInfo = [[WFCCIMService sharedWFCIMService] getChannelInfo:self.conversation.target refresh:NO];
  130. menus = channelInfo.menus;
  131. }
  132. CGRect parentRect = self.bounds;
  133. if (menus.count) {
  134. self.publicSwitchBtn = [[UIButton alloc] initWithFrame:CGRectMake(CHAT_INPUT_BAR_PADDING, CHAT_INPUT_BAR_PADDING, CHAT_INPUT_BAR_ICON_SIZE, CHAT_INPUT_BAR_ICON_SIZE)];
  135. [self.publicSwitchBtn setImage:[WFCUImage imageNamed:@"chat_input_bar_keyboard"] forState:UIControlStateNormal];
  136. [self.publicSwitchBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
  137. [self.publicSwitchBtn addTarget:self action:@selector(onSwitchBtn:) forControlEvents:UIControlEventTouchDown];
  138. [self addSubview:self.publicSwitchBtn];
  139. UIView *split = [[UIView alloc] initWithFrame:CGRectMake(CHAT_INPUT_BAR_PADDING + CHAT_INPUT_BAR_ICON_SIZE + CHAT_INPUT_BAR_PADDING/2, parentRect.size.height/2 - CHAT_INPUT_BAR_ICON_SIZE/2, 1, CHAT_INPUT_BAR_ICON_SIZE)];
  140. split.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1];
  141. [self addSubview:split];
  142. self.publicContainer = [[UIView alloc] initWithFrame:CGRectMake(CHAT_INPUT_BAR_PADDING + CHAT_INPUT_BAR_ICON_SIZE + CHAT_INPUT_BAR_PADDING/2+1, 0, parentRect.size.width - (CHAT_INPUT_BAR_PADDING + CHAT_INPUT_BAR_ICON_SIZE + CHAT_INPUT_BAR_PADDING/2+1), parentRect.size.height)];
  143. self.inputContainer = [[UIView alloc] initWithFrame:CGRectMake(CHAT_INPUT_BAR_PADDING + CHAT_INPUT_BAR_ICON_SIZE + CHAT_INPUT_BAR_PADDING/2+1, 0, parentRect.size.width - (CHAT_INPUT_BAR_PADDING + CHAT_INPUT_BAR_ICON_SIZE + CHAT_INPUT_BAR_PADDING/2+1), parentRect.size.height)];
  144. [self addSubview:self.publicContainer];
  145. [self addSubview:self.inputContainer];
  146. self.inputContainer.hidden = YES;
  147. [self setupInputContainer:YES];
  148. [self setupPublicContainer:menus];
  149. } else {
  150. self.inputContainer = [[UIView alloc] initWithFrame:CGRectMake(0, 0, parentRect.size.width, parentRect.size.height)];
  151. [self addSubview:self.inputContainer];
  152. [self setupInputContainer:NO];
  153. }
  154. }
  155. - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
  156. UIView *view = [super hitTest:point withEvent:event];
  157. if (view == nil) {
  158. for (WFCUPublicMenuButton *menuButton in self.menuButtons) {
  159. CGPoint tempPoint = [menuButton convertPoint:point fromView:self];
  160. view = [menuButton hitTest:tempPoint withEvent:event];
  161. if (view ) {
  162. return view;
  163. }
  164. }
  165. }
  166. return view;
  167. }
  168. - (void)onAppResume {
  169. if((self.inputBarStatus == ChatInputBarKeyboardStatus || self.inputBarStatus == ChatInputBarEmojiStatus || self.inputBarStatus == ChatInputBarPluginStatus) && ![self.textInputView isFirstResponder]) {
  170. [self.textInputView becomeFirstResponder];
  171. }
  172. }
  173. - (void)setupPublicContainer:(NSArray<WFCCChannelMenu *> *)menus {
  174. CGRect parentRect = self.publicContainer.bounds;
  175. CGFloat butWidth = (parentRect.size.width - (menus.count - 1) * 1)/menus.count;
  176. self.menuButtons = [[NSMutableArray alloc] init];
  177. for (int i = 0; i < menus.count; ++i) {
  178. WFCUPublicMenuButton *menuButton = [[WFCUPublicMenuButton alloc] initWithFrame:CGRectMake(butWidth * i + (i > 0 ? (i-1):0), 0, butWidth, parentRect.size.height)];
  179. [menuButton setChannelMenu:[menus objectAtIndex:i] isSubMenu:NO];
  180. menuButton.delegate = self;
  181. [self.menuButtons addObject:menuButton];
  182. [self.publicContainer addSubview:menuButton];
  183. if (i > 0) {
  184. UIView *split = [[UIView alloc] initWithFrame:CGRectMake(i * butWidth, parentRect.size.height/2 - CHAT_INPUT_BAR_ICON_SIZE/2, 1, CHAT_INPUT_BAR_ICON_SIZE)];
  185. split.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1];
  186. [self.publicContainer addSubview:split];
  187. }
  188. }
  189. _inputBarStatus = ChatInputBarPublicStatus;
  190. }
  191. - (void)setupInputContainer:(BOOL)hasPublic {
  192. CGRect parentRect = self.inputContainer.bounds;
  193. CGFloat voiceBtnPaddingLeft = hasPublic ? CHAT_INPUT_BAR_PADDING/2 : CHAT_INPUT_BAR_PADDING;
  194. CGFloat voiceAndPttOffset;
  195. self.voiceSwitchBtn = [[UIButton alloc] initWithFrame:CGRectMake(voiceBtnPaddingLeft, CHAT_INPUT_BAR_PADDING, CHAT_INPUT_BAR_ICON_SIZE, CHAT_INPUT_BAR_ICON_SIZE)];
  196. [self.voiceSwitchBtn setImage:[WFCUImage imageNamed:@"chat_input_bar_voice"] forState:UIControlStateNormal];
  197. [self.voiceSwitchBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
  198. [self.voiceSwitchBtn addTarget:self action:@selector(onSwitchBtn:) forControlEvents:UIControlEventTouchDown];
  199. [self.inputContainer addSubview:self.voiceSwitchBtn];
  200. voiceAndPttOffset = voiceBtnPaddingLeft + CHAT_INPUT_BAR_ICON_SIZE;
  201. #ifdef WFC_PTT
  202. if([self isPttEnabled]) {
  203. self.pttSwitchBtn = [[UIButton alloc] initWithFrame:CGRectMake(voiceAndPttOffset + CHAT_INPUT_BAR_PADDING, CHAT_INPUT_BAR_PADDING, CHAT_INPUT_BAR_ICON_SIZE, CHAT_INPUT_BAR_ICON_SIZE)];
  204. [self.pttSwitchBtn setImage:[WFCUImage imageNamed:@"chat_input_bar_ptt"] forState:UIControlStateNormal];
  205. [self.pttSwitchBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
  206. [self.pttSwitchBtn addTarget:self action:@selector(onSwitchBtn:) forControlEvents:UIControlEventTouchDown];
  207. [self.inputContainer addSubview:self.pttSwitchBtn];
  208. voiceAndPttOffset += CHAT_INPUT_BAR_ICON_SIZE;
  209. }
  210. #endif
  211. 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)];
  212. [self.pluginSwitchBtn setImage:[WFCUImage imageNamed:@"chat_input_bar_plugin"] forState:UIControlStateNormal];
  213. [self.pluginSwitchBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
  214. [self.pluginSwitchBtn addTarget:self action:@selector(onSwitchBtn:) forControlEvents:UIControlEventTouchDown];
  215. [self.inputContainer addSubview:self.pluginSwitchBtn];
  216. 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)];
  217. [self.emojSwitchBtn setImage:[WFCUImage imageNamed:@"chat_input_bar_emoj"] forState:UIControlStateNormal];
  218. [self.emojSwitchBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
  219. [self.emojSwitchBtn addTarget:self action:@selector(onSwitchBtn:) forControlEvents:UIControlEventTouchDown];
  220. [self.inputContainer addSubview:self.emojSwitchBtn];
  221. self.textInputView = [[UITextView alloc] initWithFrame:CGRectMake(voiceAndPttOffset + CHAT_INPUT_BAR_PADDING, CHAT_INPUT_BAR_PADDING, self.inputContainer.bounds.size.width - voiceAndPttOffset - CHAT_INPUT_BAR_PADDING - CHAT_INPUT_BAR_HEIGHT - CHAT_INPUT_BAR_HEIGHT + CHAT_INPUT_BAR_PADDING, CHAT_INPUT_BAR_ICON_SIZE)];
  222. self.textInputView.delegate = self;
  223. self.textInputView.layoutManager.allowsNonContiguousLayout = NO;
  224. [self.textInputView setExclusiveTouch:YES];
  225. [self.textInputView setTextColor:[UIColor blackColor]];
  226. [self.textInputView setFont:[UIFont systemFontOfSize:16]];
  227. [self.textInputView setReturnKeyType:UIReturnKeySend];
  228. self.textInputView.backgroundColor = [UIColor whiteColor];
  229. self.textInputView.enablesReturnKeyAutomatically = YES;
  230. self.textInputView.userInteractionEnabled = YES;
  231. [self.inputContainer addSubview:self.textInputView];
  232. self.inputCoverView = [[UIView alloc] initWithFrame:self.textInputView.bounds];
  233. self.inputCoverView.backgroundColor = [UIColor clearColor];
  234. [self.textInputView addSubview:self.inputCoverView];
  235. UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapInputView:)];
  236. tap.numberOfTapsRequired = 1;
  237. [self.inputCoverView addGestureRecognizer:tap];
  238. self.voiceInputBtn = [[UIButton alloc] initWithFrame:self.textInputView.frame];
  239. [self.voiceInputBtn setTitle:WFCString(@"HoldToTalk") forState:UIControlStateNormal];
  240. [self.voiceInputBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
  241. self.voiceInputBtn.layer.cornerRadius = 4;
  242. self.voiceInputBtn.layer.masksToBounds = YES;
  243. self.voiceInputBtn.layer.borderWidth = 0.5f;
  244. self.voiceInputBtn.layer.borderColor = HEXCOLOR(0xdbdbdd).CGColor;
  245. [self.inputContainer addSubview:self.voiceInputBtn];
  246. self.layer.borderWidth = 0.5f;
  247. self.layer.borderColor = HEXCOLOR(0xdbdbdd).CGColor;
  248. self.inputBarStatus = ChatInputBarDefaultStatus;
  249. [self.voiceInputBtn addTarget:self action:@selector(onTouchDown:) forControlEvents:UIControlEventTouchDown];
  250. [self.voiceInputBtn addTarget:self action:@selector(onTouchDragExit:) forControlEvents:UIControlEventTouchDragExit];
  251. [self.voiceInputBtn addTarget:self action:@selector(onTouchDragEnter:) forControlEvents:UIControlEventTouchDragEnter];
  252. [self.voiceInputBtn addTarget:self action:@selector(onTouchUpInside:) forControlEvents:UIControlEventTouchUpInside];
  253. [self.voiceInputBtn addTarget:self action:@selector(onTouchUpOutside:) forControlEvents:UIControlEventTouchUpOutside];
  254. [self.voiceInputBtn addTarget:self action:@selector(onTouchUpOutside:) forControlEvents:UIControlEventTouchCancel];
  255. self.voiceInputBtn.hidden = YES;
  256. self.textInputView.returnKeyType = UIReturnKeySend;
  257. self.textInputView.delegate = self;
  258. }
  259. - (void)onTapInputView:(id)sender {
  260. NSLog(@"on tap input view");
  261. self.inputBarStatus = ChatInputBarKeyboardStatus;
  262. }
  263. - (void)onTouchDown:(id)sender {
  264. if ([self canRecordNow]) {
  265. _recordView = [[WFCUVoiceRecordView alloc] initWithFrame:CGRectMake(self.parentView.bounds.size.width/2 - 70, self.parentView.bounds.size.height/2 - 70, 140, 140)];
  266. _recordView.center = self.parentView.center;
  267. [self.parentView addSubview:_recordView];
  268. [self.parentView bringSubviewToFront:_recordView];
  269. #ifdef WFC_PTT
  270. _recordView.isPtt = self.inputBarStatus == ChatInputBarPttStatus;
  271. #endif
  272. [self recordStart];
  273. }
  274. }
  275. - (void)willAppear {
  276. if (self.backupFrame.size.height) {
  277. [self.delegate willChangeFrame:self.backupFrame withDuration:0.5 keyboardShowing:NO];
  278. }
  279. }
  280. #ifdef WFC_PTT
  281. - (void)playPttRing:(NSString *)ring {
  282. if([[UIApplication sharedApplication].delegate respondsToSelector:@selector(playPttRing:)]) {
  283. [[UIApplication sharedApplication].delegate performSelector:@selector(playPttRing:) withObject:ring];
  284. }
  285. }
  286. #endif
  287. - (void)recordStart {
  288. #ifdef WFC_PTT
  289. if(self.inputBarStatus == ChatInputBarPttStatus) {
  290. if([[[WFPttClient sharedClient] getTalkingConversation] isEqual:self.conversation]) {
  291. return;
  292. }
  293. } else
  294. #endif
  295. if (self.recorder.recording) {
  296. return;
  297. }
  298. __weak typeof(self)ws = self;
  299. [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) {
  300. if (granted) {
  301. __block BOOL isViewExist = YES;
  302. if([NSThread isMainThread]) {
  303. if (!ws.recordView.superview) {
  304. isViewExist = NO;
  305. }
  306. } else {
  307. dispatch_sync(dispatch_get_main_queue(), ^{
  308. if (!ws.recordView.superview) {
  309. isViewExist = NO;
  310. }
  311. });
  312. }
  313. if(!isViewExist) {
  314. return;
  315. }
  316. #ifdef WFC_PTT
  317. if(self.inputBarStatus == ChatInputBarPttStatus) {
  318. __weak typeof(self)ws = self;
  319. [[WFPttClient sharedClient] requestTalk:self.conversation startTalking:^(void) {
  320. NSLog(@"talking now...");
  321. [ws playPttRing:@"ptt_begin"];
  322. } onAmplitude:^(int averageAmp){
  323. float level = 10*log10(averageAmp)/48.f;
  324. [ws.recordView setVoiceImage:level];
  325. } requestFailure:^(int errorCode) {
  326. NSLog(@"request talking failure");
  327. [ws.recordView removeFromSuperview];
  328. [ws recordEnd];
  329. } talkingEnd:^(PttEndReason reason) {
  330. NSLog(@"talking ended");
  331. [ws playPttRing:@"ptt_end"];
  332. [ws.recordView removeFromSuperview];
  333. [ws recordEnd];
  334. }];
  335. }
  336. else
  337. #endif
  338. {
  339. AVAudioSession *session = [AVAudioSession sharedInstance];
  340. [session setCategory:AVAudioSessionCategoryRecord error:nil];
  341. BOOL r = [session setActive:YES error:nil];
  342. if (!r) {
  343. NSLog(@"activate audio session fail");
  344. return;
  345. }
  346. NSLog(@"start record...");
  347. NSArray *pathComponents = [NSArray arrayWithObjects:
  348. [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject],
  349. @"voice.wav",
  350. nil];
  351. NSURL *outputFileURL = [NSURL fileURLWithPathComponents:pathComponents];
  352. // Define the recorder setting
  353. NSMutableDictionary *recordSetting = [[NSMutableDictionary alloc] init];
  354. [recordSetting setValue:[NSNumber numberWithInt:kAudioFormatLinearPCM] forKey:AVFormatIDKey];
  355. [recordSetting setValue:[NSNumber numberWithFloat:8000] forKey:AVSampleRateKey];
  356. [recordSetting setValue:[NSNumber numberWithInt:2] forKey:AVNumberOfChannelsKey];
  357. self.recorder = [[AVAudioRecorder alloc] initWithURL:outputFileURL settings:recordSetting error:NULL];
  358. self.recorder.delegate = self;
  359. self.recorder.meteringEnabled = YES;
  360. if (![self.recorder prepareToRecord]) {
  361. NSLog(@"prepare record fail");
  362. return;
  363. }
  364. if (![self.recorder record]) {
  365. NSLog(@"start record fail");
  366. return;
  367. }
  368. self.recordCanceled = NO;
  369. self.seconds = 0;
  370. self.recordingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES];
  371. self.updateMeterTimer = [NSTimer scheduledTimerWithTimeInterval:0.05
  372. target:self
  373. selector:@selector(updateMeter:)
  374. userInfo:nil
  375. repeats:YES];
  376. }
  377. } else {
  378. dispatch_async(dispatch_get_main_queue(), ^{
  379. [[[UIAlertView alloc] initWithTitle:@"警告" message:@"无法录音,请到设置-隐私-麦克风,允许程序访问" delegate:nil cancelButtonTitle:WFCString(@"Ok") otherButtonTitles:nil, nil] show];
  380. });
  381. }
  382. }];
  383. }
  384. - (void)recordCancel {
  385. NSLog(@"touch cancel");
  386. #ifdef WFC_PTT
  387. if(self.inputBarStatus == ChatInputBarPttStatus && [[[WFPttClient sharedClient] getTalkingConversation] isEqual:self.conversation]) {
  388. self.recordCanceled = YES;
  389. [self stopRecord];
  390. } else
  391. #endif
  392. if (self.recorder.recording) {
  393. NSLog(@"cancel record...");
  394. self.recordCanceled = YES;
  395. [self stopRecord];
  396. }
  397. }
  398. - (void)onTouchDragExit:(id)sender {
  399. [self.recordView recordButtonDragOutside];
  400. }
  401. - (void)onTouchDragEnter:(id)sender {
  402. [self.recordView recordButtonDragInside];
  403. }
  404. - (void)onTouchUpInside:(id)sender {
  405. [self.recordView removeFromSuperview];
  406. [self recordEnd];
  407. }
  408. - (void)onTouchUpOutside:(id)sender {
  409. [self.recordView removeFromSuperview];
  410. [self recordCancel];
  411. }
  412. - (BOOL)canRecordNow {
  413. return [WFCUUtilities checkRecordOrCameraPermission:YES complete:^(BOOL granted) {
  414. } viewController:[self.delegate requireNavi]];
  415. }
  416. - (void)timerFired:(NSTimer*)timer {
  417. self.seconds = self.seconds + 1;
  418. int minute = self.seconds/60;
  419. int s = self.seconds%60;
  420. NSString *str = [NSString stringWithFormat:@"%02d:%02d", minute, s];
  421. NSLog(@"timer:%@", str);
  422. int countdown = 60 - self.seconds;
  423. if (countdown <= 10) {
  424. [self.recordView setCountdown:countdown];
  425. }
  426. if (countdown <= 0) {
  427. [self.recordView removeFromSuperview];
  428. [self recordEnd];
  429. } else {
  430. [self notifyTyping:1];
  431. }
  432. }
  433. - (void)updateMeter:(NSTimer*)timer {
  434. double voiceMeter = 0;
  435. if ([self.recorder isRecording]) {
  436. [self.recorder updateMeters];
  437. //获取音量的平均值 [recorder averagePowerForChannel:0];
  438. //音量的最大值 [recorder peakPowerForChannel:0];
  439. double lowPassResults = pow(10, (0.05 * [self.recorder peakPowerForChannel:0]));
  440. voiceMeter = lowPassResults;
  441. }
  442. [self.recordView setVoiceImage:voiceMeter];
  443. }
  444. -(void)recordEnd {
  445. #ifdef WFC_PTT
  446. if(self.inputBarStatus == ChatInputBarPttStatus && [[[WFPttClient sharedClient] getTalkingConversation] isEqual:self.conversation]) {
  447. self.recordCanceled = YES;
  448. [self stopRecord];
  449. } else
  450. #endif
  451. if (self.recorder.recording) {
  452. NSLog(@"stop record...");
  453. self.recordCanceled = NO;
  454. [self stopRecord];
  455. }
  456. }
  457. -(void)stopRecord {
  458. #ifdef WFC_PTT
  459. if(self.inputBarStatus == ChatInputBarPttStatus) {
  460. [[WFPttClient sharedClient] releaseTalking:self.conversation];
  461. } else {
  462. #endif
  463. [self.recorder stop];
  464. [self.recordingTimer invalidate];
  465. self.recordingTimer = nil;
  466. [self.updateMeterTimer invalidate];
  467. self.updateMeterTimer = nil;
  468. AVAudioSession *audioSession = [AVAudioSession sharedInstance];
  469. BOOL r = [audioSession setActive:NO error:nil];
  470. if (!r) {
  471. NSLog(@"deactivate audio session fail");
  472. }
  473. #ifdef WFC_PTT
  474. }
  475. #endif
  476. }
  477. - (void)resetInputBarStatue {
  478. if (self.inputBarStatus != ChatInputBarRecordStatus && self.inputBarStatus != ChatInputBarMuteStatus
  479. #ifdef WFC_PTT
  480. && self.inputBarStatus != ChatInputBarPttStatus
  481. #endif
  482. && self.inputBarStatus != ChatInputBarPublicStatus
  483. ) {
  484. self.inputBarStatus = ChatInputBarDefaultStatus;
  485. }
  486. for (WFCUPublicMenuButton *menuButton in self.menuButtons) {
  487. menuButton.expended = NO;
  488. }
  489. }
  490. - (void)onSwitchBtn:(id)sender {
  491. #ifdef WFC_PTT
  492. if(sender == self.pttSwitchBtn) {
  493. if (self.inputBarStatus == ChatInputBarPttStatus) {
  494. self.inputBarStatus = ChatInputBarKeyboardStatus;
  495. } else {
  496. self.inputBarStatus = ChatInputBarPttStatus;
  497. }
  498. } else
  499. #endif
  500. if (sender == self.voiceSwitchBtn) {
  501. if (self.inputBarStatus == ChatInputBarRecordStatus) {
  502. self.inputBarStatus = ChatInputBarKeyboardStatus;
  503. } else {
  504. self.inputBarStatus = ChatInputBarRecordStatus;
  505. }
  506. } else if(sender == self.emojSwitchBtn) {
  507. if (self.emojInput && self.inputBarStatus != ChatInputBarDefaultStatus) {
  508. self.inputBarStatus = ChatInputBarKeyboardStatus;
  509. } else {
  510. self.inputBarStatus = ChatInputBarEmojiStatus;
  511. }
  512. } else if (sender == self.pluginSwitchBtn) {
  513. if (self.pluginInput && self.inputBarStatus != ChatInputBarDefaultStatus) {
  514. self.inputBarStatus = ChatInputBarKeyboardStatus;
  515. } else {
  516. self.inputBarStatus = ChatInputBarPluginStatus;
  517. }
  518. } else if (sender == self.publicSwitchBtn) {
  519. if (self.inputBarStatus == ChatInputBarPublicStatus) {
  520. self.inputBarStatus = ChatInputBarDefaultStatus;
  521. } else {
  522. self.inputBarStatus = ChatInputBarPublicStatus;
  523. }
  524. }
  525. }
  526. - (void)setInputBarStatus:(ChatInputBarStatus)inputBarStatus {
  527. if (inputBarStatus == _inputBarStatus) {
  528. return;
  529. }
  530. if (_inputBarStatus == ChatInputBarMuteStatus) {
  531. [self.textInputView setUserInteractionEnabled:YES];
  532. [self.voiceInputBtn setEnabled:YES];
  533. [self.voiceSwitchBtn setEnabled:YES];
  534. [self.emojSwitchBtn setEnabled:YES];
  535. [self.pluginSwitchBtn setEnabled:YES];
  536. }
  537. _inputBarStatus = inputBarStatus;
  538. if (inputBarStatus != ChatInputBarPublicStatus && self.publicContainer) {
  539. self.inputContainer.hidden = NO;
  540. self.publicContainer.hidden = YES;
  541. [self.publicSwitchBtn setImage:[WFCUImage imageNamed:@"chat_input_bar_menu"] forState:UIControlStateNormal];
  542. }
  543. switch (inputBarStatus) {
  544. case ChatInputBarKeyboardStatus:
  545. self.voiceInput = NO;
  546. self.emojInput = NO;
  547. self.pluginInput = NO;
  548. self.textInput = YES;
  549. break;
  550. case ChatInputBarPluginStatus:
  551. self.voiceInput = NO;
  552. self.emojInput = NO;
  553. self.pluginInput = YES;
  554. self.textInput = NO;
  555. break;
  556. case ChatInputBarEmojiStatus:
  557. self.voiceInput = NO;
  558. self.emojInput = YES;
  559. self.pluginInput = NO;
  560. self.textInput = NO;
  561. break;
  562. #ifdef WFC_PTT
  563. case ChatInputBarPttStatus:
  564. self.voiceInput = YES;
  565. self.emojInput = NO;
  566. self.pluginInput = NO;
  567. self.textInput = NO;
  568. break;
  569. #endif
  570. case ChatInputBarRecordStatus:
  571. self.voiceInput = YES;
  572. self.emojInput = NO;
  573. self.pluginInput = NO;
  574. self.textInput = NO;
  575. break;
  576. case ChatInputBarPublicStatus:
  577. self.inputContainer.hidden = YES;
  578. self.publicContainer.hidden = NO;
  579. self.publicInput = YES;
  580. self.textInput = NO;
  581. break;
  582. case ChatInputBarDefaultStatus:
  583. self.voiceInput = NO;
  584. self.emojInput = NO;
  585. self.pluginInput = NO;
  586. self.textInput = YES;
  587. [self.textInputView resignFirstResponder];
  588. break;
  589. case ChatInputBarMuteStatus:
  590. self.voiceInput = NO;
  591. self.emojInput = NO;
  592. self.pluginInput = NO;
  593. self.textInput = YES;
  594. [self.textInputView setUserInteractionEnabled:NO];
  595. [self.voiceInputBtn setEnabled:NO];
  596. [self.voiceSwitchBtn setEnabled:NO];
  597. #ifdef WFC_PTT
  598. [self.pttSwitchBtn setEnabled:NO];
  599. #endif
  600. [self.emojSwitchBtn setEnabled:NO];
  601. [self.pluginSwitchBtn setEnabled:NO];
  602. break;
  603. default:
  604. break;
  605. }
  606. if (inputBarStatus != ChatInputBarKeyboardStatus) {
  607. if (self.textInputView.tintColor != [UIColor clearColor]) {
  608. self.textInputViewTintColor = self.textInputView.tintColor;
  609. }
  610. self.textInputView.tintColor = [UIColor clearColor];
  611. self.inputCoverView.hidden = NO;
  612. } else {
  613. self.textInputView.tintColor = self.textInputViewTintColor;
  614. self.inputCoverView.hidden = YES;
  615. }
  616. }
  617. - (void)setVoiceInput:(BOOL)voiceInput {
  618. _voiceInput = voiceInput;
  619. if (voiceInput) {
  620. [self.textInputView setHidden:YES];
  621. [self.voiceInputBtn setHidden:NO];
  622. if (self.textInputView.isFirstResponder) {
  623. [self.textInputView resignFirstResponder];
  624. }
  625. #ifdef WFC_PTT
  626. if(self.inputBarStatus == ChatInputBarPttStatus) {
  627. [self.pttSwitchBtn setImage:[WFCUImage imageNamed:@"chat_input_bar_keyboard"] forState:UIControlStateNormal];
  628. [self.voiceSwitchBtn setImage:[WFCUImage imageNamed:@"chat_input_bar_voice"] forState:UIControlStateNormal];
  629. } else {
  630. [self.pttSwitchBtn setImage:[WFCUImage imageNamed:@"chat_input_bar_ptt"] forState:UIControlStateNormal];
  631. #endif
  632. [self.voiceSwitchBtn setImage:[WFCUImage imageNamed:@"chat_input_bar_keyboard"] forState:UIControlStateNormal];
  633. #ifdef WFC_PTT
  634. }
  635. if(self.inputBarStatus == ChatInputBarPttStatus) {
  636. [self.voiceInputBtn setTitle:WFCString(@"PushToTalk") forState:UIControlStateNormal];
  637. } else {
  638. [self.voiceInputBtn setTitle:WFCString(@"HoldToTalk") forState:UIControlStateNormal];
  639. }
  640. #endif
  641. CGFloat diff = 0;
  642. if (self.textInputView.frame.size.height != CHAT_INPUT_BAR_ICON_SIZE) {
  643. diff = self.textInputView.frame.size.height - CHAT_INPUT_BAR_ICON_SIZE;
  644. }
  645. if (self.quoteContainerView && !self.quoteContainerView.hidden) {
  646. self.quoteContainerView.hidden = YES;
  647. diff += self.quoteContainerView.frame.size.height + CHAT_INPUT_QUOTE_PADDING;
  648. }
  649. [self extendUp:-diff];
  650. } else {
  651. [self.textInputView setHidden:NO];
  652. self.quoteContainerView.hidden = NO;
  653. [self.voiceInputBtn setHidden:YES];
  654. [self.voiceSwitchBtn setImage:[WFCUImage imageNamed:@"chat_input_bar_voice"] forState:UIControlStateNormal];
  655. #ifdef WFC_PTT
  656. [self.pttSwitchBtn setImage:[WFCUImage imageNamed:@"chat_input_bar_ptt"] forState:UIControlStateNormal];
  657. #endif
  658. }
  659. }
  660. #ifdef WFC_PTT
  661. - (BOOL)isPttEnabled {
  662. return ![[WFCCIMService sharedWFCIMService] isConversationSilent:self.conversation] && [WFPttClient sharedClient].enablePtt && (self.conversation.type == Single_Type || self.conversation.type == Group_Type);
  663. }
  664. #endif
  665. - (void)setEmojInput:(BOOL)emojInput {
  666. _emojInput = emojInput;
  667. if (emojInput) {
  668. [self.textInputView setHidden:NO];
  669. self.quoteContainerView.hidden = NO;
  670. [self.voiceInputBtn setHidden:YES];
  671. self.textInputView.inputView = self.emojInputView;
  672. if (!self.textInputView.isFirstResponder) {
  673. [self.textInputView becomeFirstResponder];
  674. }
  675. [self.textInputView reloadInputViews];
  676. [self.emojSwitchBtn setImage:[WFCUImage imageNamed:@"chat_input_bar_keyboard"] forState:UIControlStateNormal];
  677. if (self.textInputView.frame.size.height+self.quoteContainerView.frame.size.height > self.frame.size.height) {
  678. [self textView:self.textInputView shouldChangeTextInRange:NSMakeRange(self.textInputView.text.length, 0) replacementText:@""];
  679. }
  680. } else {
  681. [self.emojSwitchBtn setImage:[WFCUImage imageNamed:@"chat_input_bar_emoj"] forState:UIControlStateNormal];
  682. }
  683. }
  684. - (void)setPluginInput:(BOOL)pluginInput {
  685. _pluginInput = pluginInput;
  686. if (pluginInput) {
  687. [self.textInputView setHidden:NO];
  688. self.quoteContainerView.hidden = NO;
  689. [self.voiceInputBtn setHidden:YES];
  690. self.textInputView.inputView = self.pluginInputView;
  691. if (!self.textInputView.isFirstResponder) {
  692. [self.textInputView becomeFirstResponder];
  693. }
  694. [self.textInputView reloadInputViews];
  695. self.quoteContainerView.hidden = NO;
  696. if (self.textInputView.frame.size.height+self.quoteContainerView.frame.size.height > self.frame.size.height) {
  697. [self textView:self.textInputView shouldChangeTextInRange:NSMakeRange(self.textInputView.text.length, 0) replacementText:@""];
  698. }
  699. }
  700. }
  701. - (void)setTextInput:(BOOL)textInput {
  702. _textInput = textInput;
  703. if (textInput) {
  704. [self.textInputView setHidden:NO];
  705. self.quoteContainerView.hidden = NO;
  706. [self.voiceInputBtn setHidden:YES];
  707. self.textInputView.inputView = nil;
  708. if (!self.textInputView.isFirstResponder && _inputBarStatus == ChatInputBarKeyboardStatus) {
  709. [self.textInputView becomeFirstResponder];
  710. }
  711. if (_inputBarStatus == ChatInputBarKeyboardStatus) {
  712. [self.textInputView reloadInputViews];
  713. }
  714. if (self.textInputView.frame.size.height+self.quoteContainerView.frame.size.height > self.frame.size.height) {
  715. [self textView:self.textInputView shouldChangeTextInRange:NSMakeRange(self.textInputView.text.length, 0) replacementText:@""];
  716. }
  717. }
  718. }
  719. - (void)setPublicInput:(BOOL)publicInput {
  720. _publicInput = publicInput;
  721. if (publicInput) {
  722. [self.publicSwitchBtn setImage:[WFCUImage imageNamed:@"chat_input_bar_keyboard"] forState:UIControlStateNormal];
  723. [self.textInputView resignFirstResponder];
  724. if (!self.voiceInput) {
  725. CGFloat diff = 0;
  726. if (self.textInputView.frame.size.height != CHAT_INPUT_BAR_ICON_SIZE) {
  727. diff = self.textInputView.frame.size.height - CHAT_INPUT_BAR_ICON_SIZE;
  728. }
  729. if (self.quoteContainerView && !self.quoteContainerView.hidden) {
  730. self.quoteContainerView.hidden = YES;
  731. diff += self.quoteContainerView.frame.size.height + CHAT_INPUT_QUOTE_PADDING;
  732. }
  733. [self extendUp:-diff];
  734. }
  735. }
  736. }
  737. - (void)resetTyping {
  738. self.lastTypingTime = 0;
  739. }
  740. - (void)notifyTyping:(WFCCTypingType)type {
  741. double now = [[NSDate date] timeIntervalSince1970];
  742. if (self.lastTypingTime + TYPING_INTERVAL < now) {
  743. if ([self.delegate respondsToSelector:@selector(onTyping:)]) {
  744. [self.delegate onTyping:type];
  745. }
  746. self.lastTypingTime = now;
  747. }
  748. }
  749. - (void)setDraft:(NSString *)draft {
  750. NSError *__error = nil;
  751. NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:[draft dataUsingEncoding:NSUTF8StringEncoding]
  752. options:kNilOptions
  753. error:&__error];
  754. BOOL textDraft = YES;
  755. NSString *text = draft;
  756. NSMutableArray<WFCUMetionInfo *> *mentionInfos = [[NSMutableArray alloc] init];
  757. WFCCQuoteInfo *quoteInfo = nil;
  758. if (!__error) {
  759. if ([dictionary[@"mentions"] isKindOfClass:[NSArray class]]) {
  760. textDraft = NO;
  761. NSArray *mentions = dictionary[@"mentions"];
  762. [mentions enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  763. NSDictionary *dic = (NSDictionary *)obj;
  764. WFCUMetionInfo *mentionInfo = [[WFCUMetionInfo alloc] init];
  765. if(dic[@"uid"] || dic[@"isMentionAll"]) {
  766. mentionInfo.target = dic[@"uid"];
  767. mentionInfo.mentionType = [dic[@"isMentionAll"] boolValue] ? 2 : 1;
  768. mentionInfo.range = NSMakeRange([dic[@"start"] integerValue], [dic[@"end"] integerValue]-[dic[@"start"] integerValue]);
  769. } else {
  770. mentionInfo.target = dic[@"target"];
  771. mentionInfo.mentionType = [dic[@"type"] intValue];
  772. mentionInfo.range = NSMakeRange([dic[@"loc"] integerValue], [dic[@"len"] integerValue]);
  773. }
  774. [mentionInfos addObject:mentionInfo];
  775. }];
  776. }
  777. if ([dictionary[@"quote"] isKindOfClass:[NSDictionary class]] || [dictionary[@"quoteInfo"] isKindOfClass:[NSDictionary class]]) {
  778. textDraft = NO;
  779. quoteInfo = [[WFCCQuoteInfo alloc] init];
  780. if([dictionary[@"quoteInfo"] isKindOfClass:[NSDictionary class]])
  781. [quoteInfo decode:dictionary[@"quoteInfo"]];
  782. else if([dictionary[@"quote"] isKindOfClass:[NSDictionary class]])
  783. [quoteInfo decode:dictionary[@"quote"]];
  784. }
  785. if([dictionary[@"content"] isKindOfClass:[NSString class]]) {
  786. //兼容android与web端
  787. text = dictionary[@"content"];
  788. } else if([dictionary[@"text"] isKindOfClass:[NSString class]]) {
  789. text = dictionary[@"text"];
  790. }
  791. }
  792. //防止弹出@选项
  793. if ([text isEqualToString:@"@"]) {
  794. text = @"@ ";
  795. }
  796. self.mentionInfos = mentionInfos;
  797. if (quoteInfo) {
  798. self.quoteInfo = quoteInfo;
  799. [self updateQuoteView:NO showKeyboard:NO];
  800. }
  801. [self textView:self.textInputView shouldChangeTextInRange:NSMakeRange(0, 0) replacementText:text];
  802. self.textInputView.text = text;
  803. }
  804. - (NSString *)draft {
  805. if (self.mentionInfos.count || self.quoteInfo) {
  806. NSMutableDictionary *dataDict = [NSMutableDictionary dictionary];
  807. [dataDict setObject:self.textInputView.text forKey:@"content"];
  808. if (self.mentionInfos.count) {
  809. NSMutableArray *mentions = [[NSMutableArray alloc] init];
  810. [self.mentionInfos enumerateObjectsUsingBlock:^(WFCUMetionInfo * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  811. NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];
  812. [dic setObject:obj.target forKey:@"uid"];
  813. [dic setObject:obj.mentionType==2?@(YES):@(NO) forKey:@"isMentionAll"];
  814. [dic setObject:@(obj.range.location) forKey:@"start"];
  815. [dic setObject:@(obj.range.location+obj.range.length) forKey:@"end"];
  816. [mentions addObject:dic];
  817. }];
  818. [dataDict setObject:mentions forKey:@"mentions"];
  819. }
  820. if (self.quoteInfo) {
  821. NSDictionary *quoteDict = [self.quoteInfo encode];
  822. [dataDict setObject:quoteDict forKey:@"quoteInfo"];
  823. }
  824. NSData *data = [NSJSONSerialization dataWithJSONObject:dataDict
  825. options:kNilOptions
  826. error:nil];
  827. return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  828. } else {
  829. return self.textInputView.text;
  830. }
  831. }
  832. - (void)appendText:(NSString *)text {
  833. [self textView:self.textInputView shouldChangeTextInRange:NSMakeRange(self.textInputView.text.length, 0) replacementText:text];
  834. self.textInputView.text = [self.textInputView.text stringByAppendingString:text];
  835. }
  836. - (NSString *)getDraftText:(NSString *)draft {
  837. if(!draft) {
  838. return nil;
  839. }
  840. NSError *__error = nil;
  841. NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:[draft dataUsingEncoding:NSUTF8StringEncoding]
  842. options:kNilOptions
  843. error:&__error];
  844. NSString *text = draft;
  845. if (!__error) {
  846. if([dictionary[@"content"] isKindOfClass:[NSString class]]) {
  847. //兼容android与web端
  848. text = dictionary[@"content"];
  849. } else if ([dictionary[@"text"] isKindOfClass:[NSString class]]) {
  850. text = dictionary[@"text"];
  851. }
  852. }
  853. return text;
  854. }
  855. - (UIView *)emojInputView {
  856. if (!_emojInputView) {
  857. _emojInputView = [[WFCUFaceBoard alloc] init];
  858. ((WFCUFaceBoard*)_emojInputView).delegate = self;
  859. }
  860. return _emojInputView;
  861. }
  862. - (UIView *)pluginInputView {
  863. if (!_pluginInputView) {
  864. #if WFCU_SUPPORT_VOIP
  865. BOOL hasVoip = self.conversation.type == Single_Type || self.conversation.type == SecretChat_Type || (self.conversation.type == Group_Type && [WFAVEngineKit sharedEngineKit].supportMultiCall);
  866. #else
  867. BOOL hasVoip = NO;
  868. #endif
  869. #ifdef WFC_PTT
  870. BOOL hasPtt = [WFPttClient sharedClient].enablePtt && (self.conversation.type == Single_Type || self.conversation.type == Group_Type);
  871. #else
  872. BOOL hasPtt = NO;
  873. #endif
  874. _pluginInputView = [[WFCUPluginBoardView alloc] initWithDelegate:self withVoip:hasVoip withPtt:hasPtt];
  875. }
  876. return _pluginInputView;
  877. }
  878. - (void)keyboardWillShow:(NSNotification *)notification {
  879. if (![self.textInputView isFirstResponder]) {
  880. return;
  881. }
  882. NSDictionary *userInfo = [notification userInfo];
  883. NSValue *value = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
  884. CGRect keyboardRect = [value CGRectValue];
  885. int height = keyboardRect.size.height - [WFCUUtilities wf_safeDistanceBottom];
  886. CGFloat duration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
  887. CGRect frame = CGRectMake(0, self.superview.bounds.size.height - self.bounds.size.height - height, self.superview.bounds.size.width, self.bounds.size.height);
  888. [self.delegate willChangeFrame:frame withDuration:duration keyboardShowing:YES];
  889. self.backupFrame = frame;
  890. [UIView animateWithDuration:duration animations:^{
  891. self.frame = frame;
  892. CGRect inputFrame = self.inputContainer.frame;
  893. inputFrame.size.height = frame.size.height;
  894. self.inputContainer.frame = inputFrame;
  895. inputFrame = self.publicContainer.frame;
  896. inputFrame.size.height = frame.size.height;
  897. self.publicContainer.frame = inputFrame;
  898. }];
  899. }
  900. - (void)keyboardWillHide:(NSNotification *)notification {
  901. NSDictionary *userInfo = [notification userInfo];
  902. CGFloat duration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
  903. CGRect frame = CGRectMake(0, self.superview.bounds.size.height - self.bounds.size.height, self.superview.bounds.size.width, self.bounds.size.height);
  904. [self.delegate willChangeFrame:frame withDuration:duration keyboardShowing:NO];
  905. self.backupFrame = frame;
  906. [UIView animateWithDuration:duration animations:^{
  907. self.frame = frame;
  908. CGRect inputFrame = self.inputContainer.frame;
  909. inputFrame.size.height = frame.size.height;
  910. self.inputContainer.frame = inputFrame;
  911. inputFrame = self.publicContainer.frame;
  912. inputFrame.size.height = frame.size.height;
  913. self.publicContainer.frame = inputFrame;
  914. }];
  915. if(self.inputBarStatus == ChatInputBarKeyboardStatus || self.inputBarStatus == ChatInputBarPluginStatus || self.inputBarStatus == ChatInputBarEmojiStatus) {
  916. _inputBarStatus = ChatInputBarDefaultStatus;
  917. }
  918. }
  919. -(void)keyboardDidHide:(NSNotification *)notification{
  920. if ((self.emojInput || self.pluginInput || self.textInput) && self.inputBarStatus != ChatInputBarDefaultStatus && self.inputBarStatus != ChatInputBarPublicStatus) {
  921. [self.textInputView becomeFirstResponder];
  922. }
  923. }
  924. - (BOOL)appendMention:(NSString *)userId name:(NSString *)userName {
  925. if (self.conversation.type == Group_Type) {
  926. NSString *mentionText = [NSString stringWithFormat:@"@%@ ", userName];
  927. BOOL needDelay = NO;
  928. if(self.inputBarStatus == ChatInputBarDefaultStatus || self.inputBarStatus == ChatInputBarPluginStatus ||
  929. #ifdef WFC_PTT
  930. self.inputBarStatus == ChatInputBarPttStatus ||
  931. #endif
  932. self.inputBarStatus == ChatInputBarRecordStatus) {
  933. self.inputBarStatus = ChatInputBarKeyboardStatus;
  934. needDelay = YES;
  935. }
  936. if (needDelay) {
  937. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  938. [self didMentionType:1 user:userId range:NSMakeRange(self.textInputView.selectedRange.location, mentionText.length) text:mentionText];
  939. });
  940. } else {
  941. [self didMentionType:1 user:userId range:NSMakeRange(self.textInputView.selectedRange.location, mentionText.length) text:mentionText];
  942. }
  943. return YES;
  944. } else {
  945. return NO;
  946. }
  947. }
  948. - (void)clearQuoteInfo {
  949. self.quoteInfo = nil;
  950. }
  951. - (void)onQuoteDelBtn:(id)sender {
  952. if (self.quoteInfo.messageUid) {
  953. [self clearQuoteInfo];
  954. [self updateQuoteView:YES showKeyboard:YES];
  955. }
  956. }
  957. - (void)updateQuoteView:(BOOL)updateFrame showKeyboard:(BOOL)showKeyboard {
  958. if (self.inputBarStatus == ChatInputBarMuteStatus) {
  959. return;
  960. }
  961. if (showKeyboard && (self.inputBarStatus == ChatInputBarDefaultStatus || self.inputBarStatus == ChatInputBarRecordStatus
  962. #ifdef WFC_PTT
  963. || self.inputBarStatus == ChatInputBarPttStatus
  964. #endif
  965. )) {
  966. self.inputBarStatus = ChatInputBarKeyboardStatus;
  967. }
  968. if (self.quoteInfo.messageUid) {
  969. NSString *textContent = [NSString stringWithFormat:@"%@:%@", self.quoteInfo.userDisplayName, self.quoteInfo.messageDigest];
  970. CGFloat deleteBtnWidth = 10;
  971. CGRect textViewFrame = self.textInputView.frame;
  972. CGSize size = [WFCUUtilities getTextDrawingSize:textContent font:[UIFont systemFontOfSize:12] constrainedSize:CGSizeMake(textViewFrame.size.width-CHAT_INPUT_QUOTE_PADDING-CHAT_INPUT_QUOTE_PADDING-deleteBtnWidth-CHAT_INPUT_QUOTE_PADDING, 30)];
  973. size.height += 4;
  974. self.quoteLabel = [[UILabel alloc] initWithFrame:CGRectMake(CHAT_INPUT_QUOTE_PADDING, 0, textViewFrame.size.width-CHAT_INPUT_QUOTE_PADDING-CHAT_INPUT_QUOTE_PADDING-deleteBtnWidth, size.height)];
  975. self.quoteLabel.font = [UIFont systemFontOfSize:12];
  976. self.quoteLabel.textColor = [UIColor grayColor];
  977. self.quoteLabel.text = textContent;
  978. self.quoteLabel.numberOfLines = 0;
  979. self.quoteDeleteBtn = [[UIButton alloc] initWithFrame:CGRectMake(textViewFrame.size.width-deleteBtnWidth-CHAT_INPUT_QUOTE_PADDING, (size.height-deleteBtnWidth)/2, deleteBtnWidth, deleteBtnWidth)];
  980. [self.quoteDeleteBtn setTitle:@"x" forState:UIControlStateNormal];
  981. [self.quoteDeleteBtn addTarget:self action:@selector(onQuoteDelBtn:) forControlEvents:UIControlEventTouchUpInside];
  982. self.quoteContainerView = [[UIView alloc] initWithFrame:CGRectMake(textViewFrame.origin.x, textViewFrame.origin.y+textViewFrame.size.height+CHAT_INPUT_QUOTE_PADDING, textViewFrame.size.width, size.height)];
  983. self.quoteContainerView.backgroundColor = [UIColor colorWithRed:0.85 green:0.85 blue:0.85 alpha:1.f];
  984. [self.quoteContainerView addSubview:self.quoteLabel];
  985. [self.quoteContainerView addSubview:self.quoteDeleteBtn];
  986. [self addSubview:self.quoteContainerView];
  987. if (updateFrame) {
  988. [self extendUp:(size.height + CHAT_INPUT_QUOTE_PADDING)];
  989. }
  990. } else {
  991. CGFloat quoteHeight = self.quoteContainerView.frame.size.height;
  992. [self.quoteLabel removeFromSuperview];
  993. self.quoteLabel = nil;
  994. [self.quoteDeleteBtn removeFromSuperview];
  995. self.quoteDeleteBtn = nil;
  996. [self.quoteContainerView removeFromSuperview];
  997. self.quoteContainerView = nil;
  998. if (updateFrame) {
  999. [self extendUp: -quoteHeight - CHAT_INPUT_QUOTE_PADDING];
  1000. }
  1001. }
  1002. }
  1003. - (BOOL)appendQuote:(WFCCMessage *)message {
  1004. if (self.quoteInfo) {
  1005. [self clearQuoteInfo];
  1006. [self updateQuoteView:YES showKeyboard:YES];
  1007. }
  1008. self.quoteInfo = [[WFCCQuoteInfo alloc] initWithMessage:message];
  1009. [self updateQuoteView:YES showKeyboard:YES];
  1010. return self.quoteInfo != nil;
  1011. }
  1012. - (void)extendUp:(CGFloat)diff {
  1013. CGRect baseFrame = self.frame;
  1014. CGRect voiceFrame = self.voiceSwitchBtn.frame;
  1015. CGRect emojFrame = self.emojSwitchBtn.frame;
  1016. CGRect extendFrame = self.pluginSwitchBtn.frame;
  1017. CGRect publicFrame = self.publicSwitchBtn.frame;
  1018. #ifdef WFC_PTT
  1019. CGRect pttFrame = self.pttSwitchBtn.frame;
  1020. #endif
  1021. baseFrame.size.height += diff;
  1022. baseFrame.origin.y -= diff;
  1023. voiceFrame.origin.y += diff;
  1024. emojFrame.origin.y += diff;
  1025. extendFrame.origin.y += diff;
  1026. publicFrame.origin.y += diff;
  1027. #ifdef WFC_PTT
  1028. pttFrame.origin.y += diff;
  1029. #endif
  1030. [UIView animateWithDuration:0.5 animations:^{
  1031. self.frame = baseFrame;
  1032. CGRect inputFrame = self.inputContainer.frame;
  1033. inputFrame.size.height = baseFrame.size.height;
  1034. self.inputContainer.frame = inputFrame;
  1035. inputFrame = self.publicContainer.frame;
  1036. inputFrame.size.height = baseFrame.size.height;
  1037. self.publicContainer.frame = inputFrame;
  1038. self.voiceSwitchBtn.frame = voiceFrame;
  1039. self.emojSwitchBtn.frame = emojFrame;
  1040. self.pluginSwitchBtn.frame = extendFrame;
  1041. self.publicSwitchBtn.frame = publicFrame;
  1042. #ifdef WFC_PTT
  1043. self.pttSwitchBtn.frame = pttFrame;
  1044. #endif
  1045. }];
  1046. [self.delegate willChangeFrame:baseFrame withDuration:0.5 keyboardShowing:YES];
  1047. }
  1048. - (void)paste:(id)sender {
  1049. [self.textInputView paste:sender];
  1050. }
  1051. #pragma mark - AVAudioRecorderDelegate
  1052. - (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag {
  1053. NSLog(@"record finish:%d", flag);
  1054. if (!flag) {
  1055. return;
  1056. }
  1057. if (self.recordCanceled) {
  1058. return;
  1059. }
  1060. if (self.seconds < 1) {
  1061. NSLog(@"record time too short");
  1062. [[[UIAlertView alloc] initWithTitle:@"警告" message:@"录音时间太短了" delegate:nil cancelButtonTitle:WFCString(@"Ok") otherButtonTitles:nil, nil] show];
  1063. return;
  1064. }
  1065. [self.delegate recordDidEnd:[recorder.url path] duration:self.seconds error:nil];
  1066. [[NSFileManager defaultManager] removeItemAtURL:recorder.url error:nil];
  1067. }
  1068. #pragma mark - FaceBoardDelegate
  1069. - (void)didTouchEmoj:(NSString *)emojString {
  1070. NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc]initWithString:emojString];
  1071. UIFont *font = [UIFont fontWithName:@"Heiti SC-Bold" size:16];
  1072. [attStr addAttribute:(__bridge NSString*)kCTFontAttributeName value:(id)CFBridgingRelease(CTFontCreateWithName((CFStringRef)font.fontName,
  1073. 16,
  1074. NULL)) range:NSMakeRange(0, emojString.length)];
  1075. NSInteger cursorPosition;
  1076. if (self.textInputView.selectedTextRange) {
  1077. cursorPosition = self.textInputView.selectedRange.location ;
  1078. } else {
  1079. cursorPosition = 0;
  1080. }
  1081. //获取光标位置
  1082. if(cursorPosition> self.textInputView.textStorage.length)
  1083. cursorPosition = self.textInputView.textStorage.length;
  1084. [self.textInputView.textStorage
  1085. insertAttributedString:attStr atIndex:cursorPosition];
  1086. NSRange range;
  1087. range.location = self.textInputView.selectedRange.location + emojString.length;
  1088. range.length = 1;
  1089. self.textInputView.selectedRange = range;
  1090. }
  1091. - (void)didTouchBackEmoj {
  1092. [self.textInputView deleteBackward];
  1093. }
  1094. - (void)didTouchSendEmoj {
  1095. [self sendAndCleanTextView];
  1096. }
  1097. - (void)sendAndCleanTextView {
  1098. if(self.saveDraftTimer) {
  1099. [self.saveDraftTimer invalidate];
  1100. self.saveDraftTimer = nil;
  1101. }
  1102. [self.delegate didTouchSend:self.textInputView.text withMentionInfos:self.mentionInfos withQuoteInfo:self.quoteInfo];
  1103. self.textInputView.text = nil;
  1104. [self clearQuoteInfo];
  1105. [self updateQuoteView:NO showKeyboard:YES];
  1106. [self.mentionInfos removeAllObjects];
  1107. [self changeTextViewHeight:32 needUpdateText:NO updateRange:NSMakeRange(0, 0)];
  1108. }
  1109. - (void)didSelectedSticker:(NSString *)stickerPath {
  1110. if ([self.delegate respondsToSelector:@selector(didSelectSticker:)]) {
  1111. [self.delegate didSelectSticker:stickerPath];
  1112. }
  1113. }
  1114. #pragma mark - UITextViewDelegate
  1115. - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text{
  1116. if ([text isEqualToString:@"\n"]){ //判断输入的字是否是回车,即按下return
  1117. [self sendAndCleanTextView];
  1118. return NO;
  1119. }
  1120. BOOL needUpdateText = NO;
  1121. if(self.conversation.type == Group_Type) {
  1122. if ([text isEqualToString:@"@"]) {
  1123. WFCUContactListViewController *pvc = [[WFCUContactListViewController alloc] init];
  1124. pvc.selectContact = YES;
  1125. pvc.multiSelect = NO;
  1126. NSMutableArray *disabledUser = [[NSMutableArray alloc] init];
  1127. [disabledUser addObject:[WFCCNetworkService sharedInstance].userId];
  1128. pvc.disableUsers = disabledUser;
  1129. NSMutableArray *candidateUser = [[NSMutableArray alloc] init];
  1130. NSArray<WFCCGroupMember *> *members = [[WFCCIMService sharedWFCIMService] getGroupMembers:self.conversation.target forceUpdate:NO];
  1131. for (WFCCGroupMember *member in members) {
  1132. [candidateUser addObject:member.memberId];
  1133. }
  1134. pvc.candidateUsers = candidateUser;
  1135. pvc.withoutCheckBox = YES;
  1136. pvc.groupId = self.conversation.target;
  1137. __weak typeof(self)ws = self;
  1138. WFCCGroupInfo *groupInfo = [[WFCCIMService sharedWFCIMService] getGroupInfo:self.conversation.target refresh:NO];
  1139. WFCCGroupMember *member = [[WFCCIMService sharedWFCIMService] getGroupMember:self.conversation.target memberId:[WFCCNetworkService sharedInstance].userId];
  1140. if ([groupInfo.owner isEqualToString:[WFCCNetworkService sharedInstance].userId] || member.type == Member_Type_Manager) {
  1141. pvc.showMentionAll = YES;
  1142. pvc.mentionAll = ^{
  1143. NSString *text = WFCString(@"@All");
  1144. [ws didMentionType:2 user:@"" range:NSMakeRange(range.location, text.length) text:text];
  1145. };
  1146. }
  1147. pvc.selectResult = ^(NSArray<NSString *> *contacts) {
  1148. if (contacts.count == 1) {
  1149. WFCCUserInfo *userInfo = [[WFCCIMService sharedWFCIMService] getUserInfo:[contacts objectAtIndex:0] inGroup:self.conversation.target refresh:NO];
  1150. NSString *name = userInfo.displayName;
  1151. if (userInfo.groupAlias.length) {
  1152. name = userInfo.groupAlias;
  1153. }
  1154. NSString *text = [NSString stringWithFormat:@"@%@ ", name];
  1155. [ws didMentionType:1 user:[contacts objectAtIndex:0] range:NSMakeRange(range.location, text.length) text:text];
  1156. } else {
  1157. [ws didCancelMentionAtRange:range];
  1158. }
  1159. };
  1160. pvc.cancelSelect = ^(void) {
  1161. [ws didCancelMentionAtRange:range];
  1162. };
  1163. pvc.disableUsersSelected = YES;
  1164. UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController:pvc];
  1165. [[self.delegate requireNavi] presentViewController:navi animated:YES completion:nil];
  1166. return NO;
  1167. }
  1168. if (text.length == 0) {
  1169. WFCUMetionInfo *deletedMention;
  1170. for (WFCUMetionInfo *mentionInfo in self.mentionInfos) {
  1171. if ((mentionInfo.range.location >= range.location && mentionInfo.range.location < range.location + range.length) ||
  1172. (range.location >= mentionInfo.range.location && range.location < mentionInfo.range.location + mentionInfo.range.length)) {
  1173. deletedMention = mentionInfo;
  1174. }
  1175. }
  1176. if (deletedMention) {
  1177. range = deletedMention.range;
  1178. [self.mentionInfos removeObject:deletedMention];
  1179. needUpdateText = YES;
  1180. }
  1181. } else {
  1182. if(self.textInputView.text.length) {
  1183. for (WFCUMetionInfo *mentionInfo in self.mentionInfos) {
  1184. if (range.location <= mentionInfo.range.location) {
  1185. mentionInfo.range = NSMakeRange(mentionInfo.range.location - range.length + text.length, mentionInfo.range.length);
  1186. }
  1187. }
  1188. }
  1189. }
  1190. }
  1191. NSString *oldStr = textView.text;
  1192. NSString *newStr = [oldStr stringByReplacingCharactersInRange:range withString:text];
  1193. CGFloat textAreaWidth = textView.frame.size.width - 2 * textView.textContainer.lineFragmentPadding;
  1194. CGSize size = [WFCUUtilities getTextDrawingSize:newStr font:[UIFont systemFontOfSize:16] constrainedSize:CGSizeMake(textAreaWidth, 1000)];
  1195. [self changeTextViewHeight:size.height needUpdateText:needUpdateText updateRange:range];
  1196. return YES;
  1197. }
  1198. - (void)changeTextViewHeight:(CGFloat)height needUpdateText:(BOOL)needUpdateText updateRange:(NSRange)range {
  1199. CGRect tvFrame = self.textInputView.frame;
  1200. CGRect baseFrame = self.frame;
  1201. CGRect voiceFrame = self.voiceSwitchBtn.frame;
  1202. CGRect emojFrame = self.emojSwitchBtn.frame;
  1203. CGRect extendFrame = self.pluginSwitchBtn.frame;
  1204. CGRect publicFrame = self.publicSwitchBtn.frame;
  1205. #ifdef WFC_PTT
  1206. CGRect pttFrame = self.pttSwitchBtn.frame;
  1207. #endif
  1208. CGFloat diff = 0;
  1209. CGFloat quoteHeight = 0;
  1210. if (self.quoteContainerView) {
  1211. quoteHeight = self.quoteContainerView.frame.size.height + CHAT_INPUT_QUOTE_PADDING;
  1212. }
  1213. if (height <= 32.f) {
  1214. tvFrame.size.height = 32.f;
  1215. diff = (48.f - baseFrame.size.height + quoteHeight);
  1216. baseFrame.size.height = 48.f;
  1217. } else if (height > 32.f && height < 50.f) {
  1218. tvFrame.size.height = 50.f;
  1219. diff = (66.f - baseFrame.size.height + quoteHeight);
  1220. baseFrame.size.height = 66.f;
  1221. } else {
  1222. tvFrame.size.height = 65.f;
  1223. diff = (81.f - baseFrame.size.height + quoteHeight);
  1224. baseFrame.size.height = 81.f;
  1225. }
  1226. if (self.quoteContainerView) {
  1227. baseFrame.size.height += quoteHeight;
  1228. CGRect quoteFrame = self.quoteContainerView.frame;
  1229. quoteFrame.origin.y = tvFrame.origin.y + tvFrame.size.height + CHAT_INPUT_QUOTE_PADDING;
  1230. self.quoteContainerView.frame = quoteFrame;
  1231. }
  1232. baseFrame.origin.y -= diff;
  1233. voiceFrame.origin.y += diff;
  1234. emojFrame.origin.y += diff;
  1235. extendFrame.origin.y += diff;
  1236. publicFrame.origin.y += diff;
  1237. #ifdef WFC_PTT
  1238. pttFrame.origin.y += diff;
  1239. #endif
  1240. float duration = 0.5f;
  1241. [self.delegate willChangeFrame:baseFrame withDuration:duration keyboardShowing:YES];
  1242. self.backupFrame = baseFrame;
  1243. __weak typeof(self)ws = self;
  1244. [UIView animateWithDuration:duration animations:^{
  1245. ws.textInputView.frame = tvFrame;
  1246. ws.inputCoverView.frame = ws.textInputView.bounds;
  1247. self.frame = baseFrame;
  1248. CGRect inputFrame = self.inputContainer.frame;
  1249. inputFrame.size.height = baseFrame.size.height;
  1250. self.inputContainer.frame = inputFrame;
  1251. inputFrame = self.publicContainer.frame;
  1252. inputFrame.size.height = baseFrame.size.height;
  1253. self.publicContainer.frame = inputFrame;
  1254. self.voiceSwitchBtn.frame = voiceFrame;
  1255. self.emojSwitchBtn.frame = emojFrame;
  1256. self.pluginSwitchBtn.frame = extendFrame;
  1257. self.publicSwitchBtn.frame = publicFrame;
  1258. #ifdef WFC_PTT
  1259. self.pttSwitchBtn.frame = pttFrame;
  1260. #endif
  1261. if(needUpdateText) {
  1262. [ws.textInputView.textStorage replaceCharactersInRange:range withString:@" "];
  1263. }
  1264. }];
  1265. }
  1266. - (void)textViewDidChangeSelection:(UITextView *)textView {
  1267. if (self.textInputView == textView && self.conversation.type == Group_Type) {
  1268. NSRange range = textView.selectedRange;
  1269. for (WFCUMetionInfo *mention in self.mentionInfos) {
  1270. if (range.location > mention.range.location && range.location < mention.range.location + mention.range.length) {
  1271. if (range.length == 0) {
  1272. if(range.location == mention.range.location + mention.range.length - 1) {
  1273. range.location = mention.range.location;
  1274. } else {
  1275. range = NSMakeRange(mention.range.location + mention.range.length, 0);
  1276. }
  1277. } else {
  1278. long length = range.length - (mention.range.location + mention.range.length) + range.location;
  1279. if (length < 0) {
  1280. length = 0;
  1281. }
  1282. range = NSMakeRange(mention.range.location + mention.range.length, length);
  1283. }
  1284. textView.selectedRange = range;
  1285. break;
  1286. }
  1287. }
  1288. }
  1289. }
  1290. - (void)textViewDidChange:(UITextView *)textView {
  1291. if (textView.text.length > 0) {
  1292. [self notifyTyping:0];
  1293. }
  1294. if(self.saveDraftTimer) {
  1295. [self.saveDraftTimer invalidate];
  1296. self.saveDraftTimer = nil;
  1297. }
  1298. if([self.delegate respondsToSelector:@selector(needSaveDraft)]) {
  1299. __weak typeof(self)ws = self;
  1300. if (@available(iOS 10.0, *)) {
  1301. self.saveDraftTimer = [NSTimer scheduledTimerWithTimeInterval:3 repeats:NO block:^(NSTimer * _Nonnull timer) {
  1302. [ws.delegate needSaveDraft];
  1303. }];
  1304. } else {
  1305. // Fallback on earlier versions
  1306. }
  1307. }
  1308. }
  1309. - (BOOL)textViewShouldBeginEditing:(UITextView *)textView {
  1310. if (self.inputBarStatus == ChatInputBarDefaultStatus) {
  1311. self.inputBarStatus = ChatInputBarKeyboardStatus;
  1312. }
  1313. return YES;
  1314. }
  1315. - (BOOL)textViewShouldEndEditing:(UITextView *)textView {
  1316. return YES;
  1317. }
  1318. #pragma mark - PluginBoardViewDelegate
  1319. - (void)onItemClicked:(NSUInteger)itemTag {
  1320. UINavigationController *navi = [self.delegate requireNavi];
  1321. __weak typeof(self)weakself = self;
  1322. self.inputBarStatus = ChatInputBarDefaultStatus;
  1323. if (itemTag == 1) {
  1324. [ZLPhotoConfiguration default].allowSelectImage = YES;
  1325. [ZLPhotoConfiguration default].allowSelectVideo = YES;
  1326. [ZLPhotoConfiguration default].maxSelectCount = 9;
  1327. [ZLPhotoConfiguration default].allowMixSelect = NO;
  1328. [ZLPhotoConfiguration default].allowTakePhotoInLibrary = NO;
  1329. [ZLPhotoConfiguration default].allowEditImage = YES;
  1330. [ZLPhotoConfiguration default].allowEditVideo = YES;
  1331. //视频最大时长,默认是5分钟,可以更改为更大
  1332. [ZLPhotoConfiguration default].maxSelectVideoDuration = 300;
  1333. [ZLPhotoConfiguration default].maxEditVideoTime = 300;
  1334. [ZLPhotoConfiguration default].cameraConfiguration.sessionPreset = CaptureSessionPresetVga640x480;
  1335. [ZLPhotoConfiguration default].cameraConfiguration.videoExportType = VideoExportTypeMp4;
  1336. ZLPhotoPreviewSheet *ps = [[ZLPhotoPreviewSheet alloc] initWithSelectedAssets:@[]];
  1337. ps.selectImageBlock = ^(NSArray<ZLResultModel *> *models, BOOL isOriginal) {
  1338. NSMutableArray *photos = [[NSMutableArray alloc] init];
  1339. [models enumerateObjectsUsingBlock:^(ZLResultModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  1340. [photos addObject:obj.asset];
  1341. }];
  1342. [weakself recursiveHandle:photos isFullImage:isOriginal];
  1343. };
  1344. [ps showPhotoLibraryWithSender:[self.delegate requireNavi]];
  1345. } else if(itemTag == 2) {
  1346. #if TARGET_IPHONE_SIMULATOR
  1347. [self makeToast:@"模拟器不支持相机" duration:1 position:CSToastPositionCenter];
  1348. UIImagePickerController *picker = [[UIImagePickerController alloc] init];
  1349. picker.delegate = self;
  1350. picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
  1351. [navi presentViewController:picker animated:YES completion:nil];
  1352. #else
  1353. [self checkAndAlertCameraAccessRight];
  1354. [ZLPhotoConfiguration default].allowEditVideo = YES;
  1355. ZLCustomCamera *cc = [[ZLCustomCamera alloc] init];
  1356. cc.takeDoneBlock = ^(UIImage * _Nullable image, NSURL * _Nullable url) {
  1357. NSLog(@"select the image");
  1358. if (image) {
  1359. [self.delegate imageDidCapture:image fullImage:NO];
  1360. } else {
  1361. NSData *data = [[NSData alloc] initWithContentsOfURL:url];
  1362. NSString *cacheDir = [[WFCUConfigManager globalManager] cachePathOf:self.conversation mediaType:Media_Type_VIDEO];
  1363. NSString *desFileName = [cacheDir stringByAppendingPathComponent:[url lastPathComponent]];
  1364. [data writeToFile:desFileName atomically:YES];
  1365. UIImage *thumb = [self getVideoThumbnailWithUrl:url second:1];
  1366. AVURLAsset * asset = [AVURLAsset assetWithURL:url];
  1367. CMTime time = [asset duration];
  1368. int seconds = ceil(time.value/time.timescale);
  1369. [self.delegate videoDidCapture:desFileName thumbnail:thumb duration:seconds];
  1370. }
  1371. };
  1372. [[self.delegate requireNavi] showDetailViewController:cc sender:nil];
  1373. [self notifyTyping:2];
  1374. #endif
  1375. } else if(itemTag == 3){
  1376. WFCULocationViewController *vc = [[WFCULocationViewController alloc] initWithDelegate:self];
  1377. UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
  1378. [navi presentViewController:nav animated:YES completion:nil];
  1379. [self notifyTyping:3];
  1380. return;
  1381. } else if(itemTag == 4) {
  1382. #if WFCU_SUPPORT_VOIP
  1383. UIActionSheet *actionSheet =
  1384. [[UIActionSheet alloc] initWithTitle:nil
  1385. delegate:self
  1386. cancelButtonTitle:WFCString(@"Cancel")
  1387. destructiveButtonTitle:@"视频"
  1388. otherButtonTitles:@"音频", nil];
  1389. [actionSheet showInView:self.parentView];
  1390. #endif
  1391. } else if(itemTag == 5) {
  1392. NSArray*documentTypes =@[
  1393. @"public.content",
  1394. @"public.data",
  1395. @"com.microsoft.powerpoint.ppt",
  1396. @"com.microsoft.word.doc",
  1397. @"com.microsoft.excel.xls",
  1398. @"com.microsoft.powerpoint.pptx",
  1399. @"com.microsoft.word.docx",
  1400. @"com.microsoft.excel.xlsx",
  1401. @"public.avi",
  1402. @"public.3gpp",
  1403. @"public.mpeg-4",
  1404. @"com.compuserve.gif",
  1405. @"public.jpeg",
  1406. @"public.png",
  1407. @"public.plain-text",
  1408. @"com.adobe.pdf"
  1409. ];
  1410. UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:documentTypes inMode:UIDocumentPickerModeOpen];
  1411. picker.delegate = self;
  1412. if (@available(iOS 11.0, *)) {
  1413. picker.allowsMultipleSelection = YES;
  1414. }
  1415. picker.modalPresentationStyle = UIModalPresentationFullScreen;
  1416. [navi presentViewController:picker animated:YES completion:nil];
  1417. [self notifyTyping:4];
  1418. } else if(itemTag == 6) {
  1419. WFCUContactListViewController *pvc = [[WFCUContactListViewController alloc] init];
  1420. pvc.selectContact = YES;
  1421. pvc.multiSelect = NO;
  1422. pvc.withoutCheckBox = YES;
  1423. __weak typeof(self)ws = self;
  1424. pvc.selectResult = ^(NSArray<NSString *> *contacts) {
  1425. if (contacts.count == 1) {
  1426. WFCCCardMessageContent *card = [WFCCCardMessageContent cardWithTarget:contacts[0] type:CardType_User from:[WFCCNetworkService sharedInstance].userId];
  1427. WFCCMessage *message = [[WFCCMessage alloc] init];
  1428. message.content = card;
  1429. WFCUShareMessageView *shareView = [WFCUShareMessageView createViewFromNib];
  1430. shareView.conversation = ws.conversation;
  1431. shareView.message = message;
  1432. shareView.forwardDone = ^(BOOL success) {
  1433. if (success) {
  1434. [[ws.delegate requireNavi] dismissViewControllerAnimated:YES completion:nil];
  1435. } else {
  1436. [ws makeToast:WFCString(@"SendFailure") duration:1 position:CSToastPositionCenter];
  1437. }
  1438. };
  1439. TYAlertController *alertController = [TYAlertController alertControllerWithAlertView:shareView preferredStyle:TYAlertControllerStyleAlert];
  1440. dispatch_async(dispatch_get_main_queue(), ^{
  1441. [[ws.delegate requireNavi] presentViewController:alertController animated:YES completion:nil];
  1442. });
  1443. }
  1444. };
  1445. pvc.cancelSelect = ^(void) {
  1446. NSLog(@"canceled");
  1447. };
  1448. UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController:pvc];
  1449. [[self.delegate requireNavi] presentViewController:navi animated:YES completion:nil];
  1450. } else if(itemTag == 7) {
  1451. #ifdef WFC_PTT
  1452. WFPttViewController *vc = [[WFPttViewController alloc] init];
  1453. vc.conversation = self.conversation;
  1454. UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController:vc];
  1455. navi.modalPresentationStyle = UIModalPresentationFullScreen;
  1456. [[self.delegate requireNavi] presentViewController:navi animated:YES completion:nil];
  1457. #endif
  1458. }
  1459. }
  1460. - (void)checkAndAlertCameraAccessRight {
  1461. AVAuthorizationStatus authStatus =
  1462. [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
  1463. if (authStatus == AVAuthorizationStatusDenied ||
  1464. authStatus == AVAuthorizationStatusRestricted) {
  1465. UIAlertView *alertView = [[UIAlertView alloc]
  1466. initWithTitle:@"拍照权限"
  1467. message:@"需要拍照权限,请在设置里打开"
  1468. delegate:nil
  1469. cancelButtonTitle:@"确认"
  1470. otherButtonTitles:nil, nil];
  1471. [alertView show];
  1472. }
  1473. }
  1474. #define k_THUMBNAIL_IMG_WIDTH 120//缩略图及cell大小
  1475. #define k_FPS 1//一秒想取多少帧
  1476. //这本来是个异步调用,但写成这种方便大家看和复制来直接测试
  1477. - (UIImage*)getVideoThumbnailWithUrl:(NSURL*)videoUrl second:(CGFloat)second
  1478. {
  1479. if (!videoUrl)
  1480. {
  1481. NSLog(@"WARNING:videoUrl为空");
  1482. return nil;
  1483. }
  1484. AVURLAsset *urlSet = [AVURLAsset assetWithURL:videoUrl];
  1485. AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:urlSet];
  1486. imageGenerator.appliesPreferredTrackTransform = YES;
  1487. imageGenerator.apertureMode = AVAssetImageGeneratorApertureModeEncodedPixels;
  1488. /*
  1489. 如果不需要获取缩略图,就设置为NO,如果需要获取缩略图,则maximumSize为获取的最大尺寸。
  1490. 以BBC为例,getThumbnail = NO时,打印宽高数据为:1920*1072。
  1491. getThumbnail = YES时,maximumSize为100*100。打印宽高数据为:100*55.
  1492. 注:不乘[UIScreen mainScreen].scale,会发现缩略图在100*100很虚。
  1493. */
  1494. BOOL getThumbnail = YES;
  1495. if (getThumbnail)
  1496. {
  1497. CGFloat width = [UIScreen mainScreen].scale * k_THUMBNAIL_IMG_WIDTH;
  1498. imageGenerator.maximumSize = CGSizeMake(width, width);
  1499. }
  1500. NSError *error = nil;
  1501. CMTime time = CMTimeMake(second,k_FPS);
  1502. CMTime actucalTime;
  1503. CGImageRef cgImage = [imageGenerator copyCGImageAtTime:time actualTime:&actucalTime error:&error];
  1504. if (error) {
  1505. NSLog(@"ERROR:获取视频图片失败,%@",error.domain);
  1506. }
  1507. CMTimeShow(actucalTime);
  1508. UIImage *image = [UIImage imageWithCGImage:cgImage];
  1509. NSLog(@"imageWidth=%f,imageHeight=%f",image.size.width,image.size.height);
  1510. CGImageRelease(cgImage);
  1511. return image;
  1512. }
  1513. #pragma mark UIDocumentDelegate 文件选择回调
  1514. - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
  1515. [controller dismissViewControllerAnimated:NO completion:nil];
  1516. __block NSMutableArray *arr = [NSMutableArray array];
  1517. [MBProgressHUD showHUDAddedTo:self.parentView animated:YES];
  1518. [MBProgressHUD HUDForView:self.parentView].mode = MBProgressHUDModeDeterminate;
  1519. [MBProgressHUD HUDForView:self.parentView].label.text = WFCString(@"Processing");
  1520. for (NSURL *url in urls) {
  1521. //获取授权
  1522. BOOL fileUrlAuthozied = [url startAccessingSecurityScopedResource];
  1523. if(fileUrlAuthozied){
  1524. //通过文件协调工具来得到新的文件地址,以此得到文件保护功能
  1525. NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] init];
  1526. NSError *error;
  1527. [fileCoordinator coordinateReadingItemAtURL:url options:0 error:&error byAccessor:^(NSURL *newURL) {
  1528. if (!error) {
  1529. NSData *fileData = [NSData dataWithContentsOfURL:newURL];
  1530. NSString *cacheDir = [[WFCUConfigManager globalManager] cachePathOf:self.conversation mediaType:Media_Type_FILE];
  1531. NSString *desFileName = [cacheDir stringByAppendingPathComponent:[newURL lastPathComponent]];
  1532. [fileData writeToFile:desFileName atomically:YES];
  1533. [arr addObject:desFileName];
  1534. }
  1535. }];
  1536. [url stopAccessingSecurityScopedResource];
  1537. }else{
  1538. NSLog(@"授权失败");
  1539. }
  1540. }
  1541. [MBProgressHUD hideHUDForView:self.parentView animated:YES];
  1542. [self.delegate didSelectFiles:arr];
  1543. }
  1544. #pragma mark - UIImagePickerControllerDelegate<NSObject>
  1545. - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey, id> *)info {
  1546. NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType];
  1547. if([mediaType isEqualToString:@"public.movie"]) {
  1548. NSURL *videoURL = [info objectForKey:UIImagePickerControllerMediaURL];
  1549. NSString *url = [videoURL absoluteString];
  1550. url = [url stringByReplacingOccurrencesOfString:@"file:///private" withString:@""];
  1551. //获取视频的thumbnail
  1552. AVURLAsset *asset1 = [[AVURLAsset alloc] initWithURL:videoURL options:nil];
  1553. AVAssetImageGenerator *generate1 = [[AVAssetImageGenerator alloc] initWithAsset:asset1];
  1554. generate1.appliesPreferredTrackTransform = YES;
  1555. NSError *err = NULL;
  1556. CMTime time = CMTimeMake(1, 2);
  1557. CGImageRef oneRef = [generate1 copyCGImageAtTime:time actualTime:NULL error:&err];
  1558. UIImage *thumbnail = [[UIImage alloc] initWithCGImage:oneRef];
  1559. thumbnail = [WFCCUtilities generateThumbnail:thumbnail withWidth:120 withHeight:120];
  1560. AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:videoURL options:nil];
  1561. NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];
  1562. NSString *CompressionVideoPaht = [WFCCUtilities getDocumentPathWithComponent:@"/VIDEO"];
  1563. AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:@"AVAssetExportPresetMediumQuality"];
  1564. NSDateFormatter *formater = [[NSDateFormatter alloc] init];// 用时间, 给文件重新命名, 防止视频存储覆盖,
  1565. [formater setDateFormat:@"yyyy-MM-dd_HH-mm-ss"];
  1566. NSFileManager *manager = [NSFileManager defaultManager];
  1567. BOOL isExists = [manager fileExistsAtPath:CompressionVideoPaht];
  1568. if (!isExists) {
  1569. [manager createDirectoryAtPath:CompressionVideoPaht withIntermediateDirectories:YES attributes:nil error:nil];
  1570. }
  1571. //
  1572. NSString *resultPath = [CompressionVideoPaht stringByAppendingPathComponent:[NSString stringWithFormat:@"outputJFVideo-%@.mov", [formater stringFromDate:[NSDate date]]]];
  1573. NSLog(@"resultPath = %@",resultPath);
  1574. exportSession.outputURL = [NSURL fileURLWithPath:resultPath];
  1575. exportSession.outputFileType = AVFileTypeMPEG4;
  1576. exportSession.shouldOptimizeForNetworkUse = YES;
  1577. MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:picker.view animated:YES];
  1578. hud.label.text = WFCString(@"Processing");
  1579. [hud showAnimated:YES];
  1580. __weak typeof(self)ws = self;
  1581. [exportSession exportAsynchronouslyWithCompletionHandler:^(void)
  1582. {
  1583. if (exportSession.status == AVAssetExportSessionStatusCompleted) {
  1584. NSData *data = [NSData dataWithContentsOfFile:resultPath];
  1585. float memorySize = (float)data.length / 1024 / 1024;
  1586. NSLog(@"视频压缩后大小 %f", memorySize);
  1587. dispatch_async(dispatch_get_main_queue(), ^{
  1588. [hud hideAnimated:YES];
  1589. [picker dismissViewControllerAnimated:YES completion:nil];
  1590. [ws.delegate videoDidCapture:resultPath thumbnail:thumbnail duration:10];
  1591. });
  1592. } else {
  1593. dispatch_async(dispatch_get_main_queue(), ^{
  1594. [hud hideAnimated:YES];
  1595. [picker dismissViewControllerAnimated:YES completion:nil];
  1596. });
  1597. NSLog(@"压缩失败");
  1598. }
  1599. }];
  1600. } else if ([mediaType isEqualToString:@"public.image"]) {
  1601. [picker dismissViewControllerAnimated:YES completion:nil];
  1602. UIImage* image = [info objectForKey:UIImagePickerControllerEditedImage];
  1603. if (!image)
  1604. image = [info objectForKey:UIImagePickerControllerOriginalImage];
  1605. [self.delegate imageDidCapture:image fullImage:NO];
  1606. }
  1607. }
  1608. - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
  1609. [picker dismissViewControllerAnimated:YES completion:nil];
  1610. }
  1611. #pragma mark - LocationViewControllerDelegate <NSObject>
  1612. - (void)onSendLocation:(WFCULocationPoint *)locationPoint {
  1613. [self.delegate locationDidSelect:locationPoint.coordinate locationName:locationPoint.title mapScreenShot:locationPoint.thumbnail];
  1614. }
  1615. #pragma mark - UIActionSheetDelegate
  1616. - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
  1617. if (buttonIndex == 0) {
  1618. #if WFCU_SUPPORT_VOIP
  1619. [self.delegate didTouchVideoBtn:NO];
  1620. #endif
  1621. } else if(buttonIndex == 1) {
  1622. #if WFCU_SUPPORT_VOIP
  1623. [self.delegate didTouchVideoBtn:YES];
  1624. #endif
  1625. }
  1626. }
  1627. #pragma mark - WFCUPublicMenuButtonDelegate
  1628. - (void)didTapButton:(WFCUPublicMenuButton *)button menu:(WFCCChannelMenu *)channelMenu {
  1629. for (WFCUPublicMenuButton *menuButton in self.menuButtons) {
  1630. if (button != menuButton) {
  1631. menuButton.expended = NO;
  1632. }
  1633. }
  1634. WFCCChannelMenuEventMessageContent *content = [[WFCCChannelMenuEventMessageContent alloc] init];
  1635. content.menu = channelMenu;
  1636. [[WFCCIMService sharedWFCIMService] send:self.conversation content:content success:nil error:nil];
  1637. if ([self.delegate respondsToSelector:@selector(didTapChannelMenu:)]) {
  1638. [self.delegate didTapChannelMenu:channelMenu];
  1639. }
  1640. }
  1641. #pragma mark - WFCUMentionUserDelegate
  1642. - (void)didMentionType:(int)type user:(NSString *)userId range:(NSRange)range text:(NSString *)text {
  1643. [self textView:self.textInputView shouldChangeTextInRange:NSMakeRange(range.location, 0) replacementText:text];
  1644. [self.mentionInfos addObject:[[WFCUMetionInfo alloc] initWithType:type target:userId range:NSMakeRange(range.location, range.length)]];
  1645. NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc]initWithString:text];
  1646. UIFont *font = [UIFont fontWithName:@"Heiti SC-Bold" size:16];
  1647. [attStr addAttribute:(__bridge NSString*)kCTFontAttributeName value:(id)CFBridgingRelease(CTFontCreateWithName((CFStringRef)font.fontName,
  1648. 16,
  1649. NULL)) range:NSMakeRange(0, text.length)];
  1650. [self.textInputView.textStorage
  1651. insertAttributedString:attStr atIndex:range.location];
  1652. range.location += range.length;
  1653. range.length = 0;
  1654. self.textInputView.selectedRange = range;
  1655. dispatch_async(dispatch_get_main_queue(), ^{
  1656. if (!self.textInputView.isFirstResponder) {
  1657. [self.textInputView becomeFirstResponder];
  1658. }
  1659. });
  1660. }
  1661. - (void)didCancelMentionAtRange:(NSRange)range {
  1662. NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc]initWithString:@"@"];
  1663. UIFont *font = [UIFont fontWithName:@"Heiti SC-Bold" size:16];
  1664. [attStr addAttribute:(__bridge NSString*)kCTFontAttributeName value:(id)CFBridgingRelease(CTFontCreateWithName((CFStringRef)font.fontName,
  1665. 16,
  1666. NULL)) range:NSMakeRange(0, 1)];
  1667. [self.textInputView.textStorage
  1668. insertAttributedString:attStr atIndex:range.location];
  1669. range.location += 1;
  1670. range.length = 0;
  1671. self.textInputView.selectedRange = range;
  1672. dispatch_async(dispatch_get_main_queue(), ^{
  1673. if (!self.textInputView.isFirstResponder) {
  1674. [self.textInputView becomeFirstResponder];
  1675. }
  1676. });
  1677. }
  1678. - (void)convertAvcompositionToAvasset:(AVComposition *)composition completion:(void (^)(AVAsset *asset))completion {
  1679. // 导出视频
  1680. AVAssetExportSession *exporter = [AVAssetExportSession exportSessionWithAsset:composition presetName:AVAssetExportPresetHighestQuality];
  1681. // 生成一个文件路径
  1682. NSInteger randNumber = arc4random();
  1683. NSString *exportPath = [NSTemporaryDirectory() stringByAppendingString:[NSString stringWithFormat:@"%ldvideo.mov", randNumber]];
  1684. NSURL *exportURL = [NSURL fileURLWithPath:exportPath];
  1685. // 导出
  1686. if (exporter) {
  1687. exporter.outputURL = exportURL; // 设置路径
  1688. exporter.outputFileType = AVFileTypeQuickTimeMovie;
  1689. exporter.shouldOptimizeForNetworkUse = YES;
  1690. [exporter exportAsynchronouslyWithCompletionHandler:^{
  1691. dispatch_async(dispatch_get_main_queue(), ^{
  1692. if (AVAssetExportSessionStatusCompleted == exporter.status) { // 导出完成
  1693. NSURL *URL = exporter.outputURL;
  1694. AVAsset *avAsset = [AVAsset assetWithURL:URL];
  1695. if (completion) {
  1696. completion(avAsset);
  1697. }
  1698. } else {
  1699. if (completion) {
  1700. completion(nil);
  1701. }
  1702. }
  1703. });
  1704. }];
  1705. } else {
  1706. dispatch_async(dispatch_get_main_queue(), ^{
  1707. if (completion) {
  1708. completion(nil);
  1709. }
  1710. });
  1711. }
  1712. }
  1713. - (void)handleVideo:(NSURL *)url photos:(NSMutableArray<PHAsset *> *)photos isFullImage:(BOOL)isFullImage {
  1714. AVURLAsset *asset1 = [[AVURLAsset alloc] initWithURL:url options:nil];
  1715. AVAssetImageGenerator *generate1 = [[AVAssetImageGenerator alloc] initWithAsset:asset1];
  1716. generate1.appliesPreferredTrackTransform = YES;
  1717. NSError *err = NULL;
  1718. CMTime time = CMTimeMake(1, 2);
  1719. CGImageRef oneRef = [generate1 copyCGImageAtTime:time actualTime:NULL error:&err];
  1720. UIImage *thumbnail = [[UIImage alloc] initWithCGImage:oneRef];
  1721. thumbnail = [WFCCUtilities generateThumbnail:thumbnail withWidth:120 withHeight:120];
  1722. AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:url options:nil];
  1723. NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];
  1724. NSString *CompressionVideoPaht = [WFCCUtilities getDocumentPathWithComponent:@"/VIDEO"];
  1725. AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:@"AVAssetExportPresetMediumQuality"];
  1726. NSDateFormatter *formater = [[NSDateFormatter alloc] init];// 用时间, 给文件重新命名, 防止视频存储覆盖,
  1727. [formater setDateFormat:@"yyyy-MM-dd_HH-mm-ss"];
  1728. NSFileManager *manager = [NSFileManager defaultManager];
  1729. BOOL isExists = [manager fileExistsAtPath:CompressionVideoPaht];
  1730. if (!isExists) {
  1731. [manager createDirectoryAtPath:CompressionVideoPaht withIntermediateDirectories:YES attributes:nil error:nil];
  1732. }
  1733. //
  1734. NSString *resultPath = [CompressionVideoPaht stringByAppendingPathComponent:[NSString stringWithFormat:@"outputJFVideo-%@.mov", [formater stringFromDate:[NSDate date]]]];
  1735. NSLog(@"resultPath = %@",resultPath);
  1736. exportSession.outputURL = [NSURL fileURLWithPath:resultPath];
  1737. exportSession.outputFileType = AVFileTypeMPEG4;
  1738. exportSession.shouldOptimizeForNetworkUse = YES;
  1739. CMTime time2 = [asset1 duration];
  1740. int seconds = ceil(time2.value/time2.timescale);
  1741. dispatch_async(dispatch_get_main_queue(), ^{
  1742. [MBProgressHUD showHUDAddedTo:self.parentView animated:YES];
  1743. [MBProgressHUD HUDForView:self.parentView].mode = MBProgressHUDModeDeterminate;
  1744. [MBProgressHUD HUDForView:self.parentView].label.text = WFCString(@"Processing");
  1745. });
  1746. __weak typeof(self)ws = self;
  1747. [exportSession exportAsynchronouslyWithCompletionHandler:^(void)
  1748. {
  1749. if (exportSession.status == AVAssetExportSessionStatusCompleted) {
  1750. NSData *data = [NSData dataWithContentsOfFile:resultPath];
  1751. float memorySize = (float)data.length / 1024 / 1024;
  1752. NSLog(@"视频压缩后大小 %f", memorySize);
  1753. dispatch_async(dispatch_get_main_queue(), ^{
  1754. [MBProgressHUD hideHUDForView:self.parentView animated:YES];
  1755. [ws.delegate videoDidCapture:resultPath thumbnail:thumbnail duration:seconds];
  1756. });
  1757. [ws recursiveHandle:photos isFullImage:isFullImage];
  1758. } else {
  1759. dispatch_async(dispatch_get_main_queue(), ^{
  1760. [MBProgressHUD hideHUDForView:self.parentView animated:YES];
  1761. [self.parentView makeToast:@"视频处理失败" duration:1 position:CSToastPositionCenter];
  1762. });
  1763. NSLog(@"压缩失败");
  1764. }
  1765. }];
  1766. }
  1767. - (void)recursiveHandle:(NSMutableArray<PHAsset *> *)photos isFullImage:(BOOL)isFullImage {
  1768. if (photos.count == 0) {
  1769. dispatch_async(dispatch_get_main_queue(), ^{
  1770. [MBProgressHUD hideHUDForView:self.parentView animated:YES];
  1771. });
  1772. }else{
  1773. PHAsset *phAsset = photos[0];
  1774. [photos removeObjectAtIndex:0];
  1775. __weak typeof(self) weakself = self;
  1776. if (phAsset.mediaType == PHAssetMediaTypeVideo) {
  1777. PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init];
  1778. options.networkAccessAllowed = YES;
  1779. options.version = PHImageRequestOptionsVersionCurrent;
  1780. options.deliveryMode = PHVideoRequestOptionsDeliveryModeAutomatic;
  1781. PHImageManager *manager = [PHImageManager defaultManager];
  1782. [manager requestAVAssetForVideo:phAsset options:options resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
  1783. if ([asset isKindOfClass:[AVComposition class]]) {
  1784. [weakself convertAvcompositionToAvasset:(AVComposition *)asset completion:^(AVAsset *asset) {
  1785. AVURLAsset *urlAsset = (AVURLAsset *)asset;
  1786. [weakself handleVideo:urlAsset.URL photos:photos isFullImage:isFullImage];
  1787. }];
  1788. } else {
  1789. AVURLAsset *urlAsset = (AVURLAsset *)asset;
  1790. [weakself handleVideo:urlAsset.URL photos:photos isFullImage:isFullImage];
  1791. }
  1792. }];
  1793. } else if(phAsset.mediaType == PHAssetMediaTypeImage) {
  1794. PHImageRequestOptions *imageRequestOption = [[PHImageRequestOptions alloc] init];
  1795. imageRequestOption.networkAccessAllowed = YES;
  1796. PHCachingImageManager *cachingImageManager = [[PHCachingImageManager alloc] init];
  1797. cachingImageManager.allowsCachingHighQualityImages = NO;
  1798. [cachingImageManager
  1799. requestImageDataForAsset:phAsset
  1800. options:imageRequestOption
  1801. resultHandler:^(NSData *_Nullable imageData, NSString *_Nullable dataUTI,
  1802. UIImageOrientation orientation, NSDictionary *_Nullable info) {
  1803. BOOL downloadFinined = (![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey] && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue]);
  1804. if (downloadFinined) {
  1805. if ([weakself isGifWithImageData:imageData] && [weakself.delegate respondsToSelector:@selector(gifDidCapture:)]) {
  1806. [weakself.delegate gifDidCapture:imageData];
  1807. } else if ([weakself.delegate respondsToSelector:@selector(imageDidCapture:fullImage:)]) {
  1808. [weakself.delegate imageDidCapture:[UIImage imageWithData:imageData] fullImage:isFullImage];
  1809. }
  1810. [weakself recursiveHandle:photos isFullImage:isFullImage];
  1811. }
  1812. if ([info objectForKey:PHImageErrorKey]) {
  1813. [weakself.parentView makeToast:@"下载图片失败"];
  1814. [weakself recursiveHandle:photos isFullImage:isFullImage];
  1815. }
  1816. }];
  1817. }
  1818. }
  1819. }
  1820. - (BOOL)isGifWithImageData: (NSData *)data {
  1821. if ([[self contentTypeWithImageData:data] isEqualToString:@"gif"]) {
  1822. return YES;
  1823. }
  1824. return NO;
  1825. }
  1826. - (NSString *)contentTypeWithImageData: (NSData *)data {
  1827. uint8_t c;
  1828. [data getBytes:&c length:1];
  1829. switch (c) {
  1830. case 0xFF:
  1831. return @"jpeg";
  1832. case 0x89:
  1833. return @"png";
  1834. case 0x47:
  1835. return @"gif";
  1836. case 0x49:
  1837. case 0x4D:
  1838. return @"tiff";
  1839. case 0x52:
  1840. if ([data length] < 12) {
  1841. return nil;
  1842. }
  1843. NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
  1844. if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
  1845. return @"webp";
  1846. }
  1847. return nil;
  1848. }
  1849. return nil;
  1850. }
  1851. - (void)dealloc {
  1852. [[NSNotificationCenter defaultCenter] removeObserver:self];
  1853. }
  1854. @end