From 12c167ba6c47e1e96a9d7ea71334923e7585be5d Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Mon, 18 Oct 2021 16:30:32 +0300 Subject: [PATCH] vector-im/element-ios/issues/5009 - Implemented multi-room forwarding and added various tweaks following code review. --- .../SharedImages.xcassets/Contents.json | 6 +- .../Contents.json | 23 + .../radio-button-default.png | Bin 0 -> 615 bytes .../radio-button-default@2x.png | Bin 0 -> 1153 bytes .../radio-button-default@3x.png | Bin 0 -> 1705 bytes .../Contents.json | 23 + .../radio-button-selected.png | Bin 0 -> 729 bytes .../radio-button-selected@2x.png | Bin 0 -> 1348 bytes .../radio-button-selected@3x.png | Bin 0 -> 2072 bytes Riot/Generated/Images.swift | 2 + Riot/Modules/Room/RoomViewController.m | 62 +- RiotShareExtension/Shared/ShareDataSource.h | 30 +- RiotShareExtension/Shared/ShareDataSource.m | 39 +- .../SimpleShareItemProvider.swift | 20 +- RiotShareExtension/Shared/ShareManager.h | 14 +- RiotShareExtension/Shared/ShareManager.m | 664 +++++++++--------- .../Shared/View/RecentRoomTableViewCell.h | 2 + .../Shared/View/RecentRoomTableViewCell.m | 14 + .../Shared/View/RecentRoomTableViewCell.xib | 31 +- .../Shared/View/RoomsListViewController.m | 17 + .../Shared/View/ShareViewController.h | 7 +- .../Shared/View/ShareViewController.m | 99 ++- .../Shared/View/ShareViewController.xib | 2 +- .../ShareExtensionRootViewController.m | 11 +- .../ShareExtensionShareItemProvider.swift | 38 +- 25 files changed, 620 insertions(+), 484 deletions(-) create mode 100644 Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/Contents.json create mode 100644 Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/radio-button-default.png create mode 100644 Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/radio-button-default@2x.png create mode 100644 Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/radio-button-default@3x.png create mode 100644 Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/Contents.json create mode 100644 Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/radio-button-selected.png create mode 100644 Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/radio-button-selected@2x.png create mode 100644 Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/radio-button-selected@3x.png diff --git a/Riot/Assets/SharedImages.xcassets/Contents.json b/Riot/Assets/SharedImages.xcassets/Contents.json index da4a164c9..73c00596a 100644 --- a/Riot/Assets/SharedImages.xcassets/Contents.json +++ b/Riot/Assets/SharedImages.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/Contents.json b/Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/Contents.json new file mode 100644 index 000000000..35812152a --- /dev/null +++ b/Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "radio-button-default.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "radio-button-default@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "radio-button-default@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/radio-button-default.png b/Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/radio-button-default.png new file mode 100644 index 0000000000000000000000000000000000000000..84e419079478efbbd5b03ef22c5b6c750ec45e79 GIT binary patch literal 615 zcmV-t0+{`YP)p&1avqt&2U+LibR&Zl+xe`GLTtQ3+PywU@k_tix&;g`^MM6ms4<&S9I^Y}R9d9{b zK%EKcQpASQUM8d#4c-ifr*#bxy(_7b6JBTJCKQn zP0NNa2Bsa>x6ZHvx_^J7pgJyFQmx(XpuJQ;_Z|-=8;K>$aOeLB1)zI(HASAbVby>S z!ur3f?A=U>EVln}2g%R&*mhCtVjqw*Hz1Qgf} z+5>H)BoZXulr1k{!{6z&!llW9HeBr$(cioPu{07sQ~P;@+d-U$eqP{JSSiq^*JYCK<?p-hInDV`jh3@1+5=9&tATqJFAHJSOt;^8U+O9hf*1T)yaUqy)<{&lLtOv>002ovPDHLkV1lm0 B5dr`J literal 0 HcmV?d00001 diff --git a/Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/radio-button-default@2x.png b/Riot/Assets/SharedImages.xcassets/radio-button-default.imageset/radio-button-default@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7e6083bc319d633e3955789d9d89f19b8123b096 GIT binary patch literal 1153 zcmV-{1b+L8P)iLCFA}0fvWHP*_3J70|ALu!6!02rDREfddRM!(##~sJnxFbxMk* zUKs{z0X81C7NI? zV30v5#VrKt-pWE91)whizTcRcc!ez?#Fr;yvm;;(_3%$?$MY5g^oX7aD8rp8Y)g>g z1%QaudL@#ZfPZ$i!6n=8VaB^55E47y@?0%Xqg(`z&l=x4%z9HY97=&BAiTl!WZ;#{ zP4&WWG-mMouM0S{U~-*T^u zS2)8q{dJ8lX-Tav&VA0(PbLB>#y5o&d*1Rw6@9=zy{yu#Z|mh*NpvM}a^Bb`L#tY= zK!|YDTmEo^0^pxFTrPzV?Q1ab-PNV?*G_Fa5{N(ZzSbfTXpzI{-St_=N+BU2H@&6# z!?bp(1jtpod`jzZK*m3fX(!PXcl@2_+4mPu>x@r@RT#4|IO7$(#WK0laFhPF8ylTD zX>F_o)a=uAe`+tIeXuh~;~$~+m2wX4Wy*D{!_!vZN+6#7XVB8=*W2VlJXVk>EZtkr zpOhuU*4m$hK<0y`CYf z<#ot~{zw!$6sBw7W&ul7dWdaC1j*{!^SWNevIqFvorUQOuC8Z z)?|QD!5rIgr8X~=sFZdB4g;34GZFZ&U?Y%Vopu5a1D3Ik)zFmyN-C2Y5sa}}pjR;o zNO`N37`}ISa1vuvjPy&dVGs~Z*4v*Mch)c>aLS*PCDL%|<`)Q&sKg{TMz;^5sed3CU&(F zSE&aW5|oetEgO_*Swng&fhZD`(ul|X(~E(7B$W;7DhNfywX|$7{UmNVRcL#GGv~YS zpSu_k+JuB>(ArO%eT7s4aXhXIOEf^q9|Q@j3WA4huv0GHF&0vKjJ}jG0201`F>5tm z2?<@Y!LGraw>(eDv^>y?6^WnmX6p;84_y(T<#`BTEr3ZOEx`GneNV{Jk@_QCN=zvS5-$C^cn-#zGCCCHjTG#Nw3?fuu35(zZF6Btggid{gJ?F4ZG?-7)$ zJb2oZt^d3S6;O#lAC6956X>l1 zQ$~caPbr*&q6F}tAuKPCqTaU%6(1jm9JNA;cBtqHyAemb8!Y6{D2RfBd^|pJ<~JPT_w};sH6@M^t@RRp zu6mLW|b2ke%^Mu96#d*KjS`l;w=#Sd#Jj0 zGnVxxT7S3=b3lCf<&8poVHqB=wf*S39(lW%>ZaRZ5l7r=Uc?ci^#@xHcgwPU@We}? zEdK!9G!F8DOn239FlVIa*)t`kG1&^qei1zI5-7lqb#c=$j_L~Ivmh>{Dc!J*%Vrup z$Q4MGwneRM-C3)@CQ}xPq**RzKz@^t73B(~6q=(FyOtz@Fz<}BXEa*4j}1=_ncafqXvRe5Ud`>Zs^X#sWp*>$ zZv~e?3i_s(wWd>ODZCFmFb)JzTp%?qaoz-J0Ih|LmXD~cbuFtvJn0AHKnksMuW!gn zt>IO|;uge91Xn^L%<*k;Pqr2kg}m7%ae zd3%@t8ZLnn`ERdSwf91O5^R>Y^R#@aeB&;wTj>CA&tNDs`DaI<~+P}Wm)10sCq zn&nOu&pUgTk*w_mT7vWxfX%(sc;K3wdp7TAbt!bleOrj7tFAr)gFsxTKHy_&=W>y| z)kxFS*5}s>E9xwRG%2e}PZUYk{i z6>)S3-p}KLec@WMZJbEI1P{Cfk`n<(J4sliounx)o+&hq5yGCY)q9saF4{?q3$nD6 zlxc6Pi)ovnCF7TN9Ts~Qzx-7T9~N|{9k>KXgXrq8u2aVxQ$SsJ}6xek@rylo)iFCL~E zeh1P+Ijy}KSu(ej!vU#(YYel?znT}{0(a4>YHF|ULY?>Ro7&0Jks{snE~>qfDaR_5 zZYx_)bU4{alMcm&l@u0lr|NYAMmy zDa<9c3Uo>4dRq4pX>(Sq%0$t40N<&-Cfbl25$G!M2}h{EC#%xphtp#ij!YQhwZ)y} zIGAX}^^{?D`B$Rd_nl!Idb1ZSSg>Hhg8ATI6MEZVK&@sK00000NkvXXu0mjfI}#^A literal 0 HcmV?d00001 diff --git a/Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/Contents.json b/Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/Contents.json new file mode 100644 index 000000000..a69d70fe6 --- /dev/null +++ b/Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "radio-button-selected.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "radio-button-selected@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "radio-button-selected@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/radio-button-selected.png b/Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/radio-button-selected.png new file mode 100644 index 0000000000000000000000000000000000000000..6a744d6bed5a4753d6cdfde938f85ea02d685d63 GIT binary patch literal 729 zcmV;~0w(>5P)>k%Ly)OFd9QChHvOJpg&f9sjGX}Wtz-^08;5kcp)Bz1`M644*&1^!rBBGGZ$*g(w zY8`V__12!EXd^tOK>#rrKPRGAQ7OkO|J-H!9e8rpVER7?w0nyj2?wL%&lGbJtQlF=O(zo*sHqFo4iMeImS{iI*C6mGNEVa=&3m3LJJvmJL*R8%Mb0?Joqu8eI zb?ewmzN-eOZ<0P*CC#96aoXWb&e@E3ptai9 zWE14%N~^$x{!g-81_>U2AA8(*$ruQwyEQl9-1dmvw0*tTA%u3)D-t{_*;WF9i~uRx zPCp#dz1hLBzitr=Hro-d_?Jnwq}!!uh>)|vNlDH8$n|z<$a{)^ahVuYI-wB^00000 LNkvXXu0mjf1<*je literal 0 HcmV?d00001 diff --git a/Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/radio-button-selected@2x.png b/Riot/Assets/SharedImages.xcassets/radio-button-selected.imageset/radio-button-selected@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..67c3bbd64f75d22ed14d368c2a93d7f84aecb799 GIT binary patch literal 1348 zcmV-K1-tr*P)ps1;?uYanlYXH_*o(7+6UJeh=dpVu~kwkuNw$>p+d)FZuN!-XU!~jG7;dN$3RKd9ldF}umU!JYr80bne}WWQVDE(Kha^8 zt|9iNP)Fn1`6Cp7s(0l`S@z7;Am=)N9JEf8o=qg6ms#&iL+nq)Q*D*d$H7Jd6dn!h zXZN<}FsH$P#QBslgOVt}4r-_D`6}CtO{Zm-=sAnBH0Q(oAU7H>*?9ImBIqovx7)|0 zFd2~TjbE>iLVMZ1r#M5>&cTeR0ew#RzPA?S1nQ*_K;JsnzNs)!6QT;Ad8p zdwT2zQI&GWeoQYWkkU}Xgb;?g(mn^8*^8=o8{2x}enTn4$Ei0}Lqse}rQT93q{j$IX~bT@LtCbl0M;5QJM489t;}19_$CIXN{j&A zakWyU2C-$z@joP0{+7jKQIt!v<(|`6Y0=^o}SP5zj>#YW5(k|o$5g*mht%YE` z&We^5bFc(xe|HR#Lsg>-7L4j=y0zFu$P!vHply1piQ6>7Mgo|(!3n$7taswghno%^ zn#Gw9s5w;||B&2?m@U0{OV1itN_?co?x(c=5qBF*YOy3->Tj!4`J@d+_Tj&9sZSi! zT1td#G-s;Htq$stBT<-_$ zjo8qXjx6{33M%OqSAmH?k2c!j2~->bR98u%OOr!e3q~imr2McF0gdFg_$r_YP}AnH z{_Z}{Q@JlRdaDQuYpawHCiJYA#OQTi=QVP#z#_t=LtQ-G2+O}Bv-b<-^0<@d#YoHw z6a?LLqP`6^#Y#Jrg&hjNj`5l`m}g^M@p6UP8f&bPAAbYwO>Yf)G^|Yk0000Fj2CX!EJ`2^l0 zC;^qh+MQ1KV#?Uw*_mDKu5n7=3x#H8clB-e^zR5@j4{R-V~jDrRUlN1T4%Es!uJiO z{RRQvL8OL!Oa%4o^AI5*fB=a42=qoP-Sy$3*Kck1p+ZzbkdUJFH#-Mk1$a1A!>uA!uP}n9Up|`y^n{!md|d?m3|pu7*AJlp6hhEUXM2}4 zthld0u|#dLPDB_Chr`~jTNnN77V6d?PwT$#*OdaXcK3KL|NYd`rQSio=D)4AEf^6a z5frNwO}=p3Yp{=`k2GQX?7>{Eb4RR9{lM_vL z`3iT^XJVf;?fd>o&K`cf{aj*5i_0NE&dI* zJvF#n6*+=Fn{}o9yK-;iB!1?ZlkHu|h)fbBde2J^Oyfkfe>S_G!I~A1slT>2Q&o;| z{&e#ef~k2m24VCv=09tXk8hT*=a{M)Ykk+X_Lk-uryaWUODgB-!x z?4y+wbz^UTcmR%YlAy^B`yU!#8-@efbFx~h*b>4Ia~MGb6y9TpvHsBuXGapL$LbJ( zijF;a*Uf(YUCtncugwwvqOvGfct z*3qh2RcxxW-87LS`%abzp6To?b1?gs-`-}nm`&!=;)M2J&zwASHUfiR*Ly4h$>9cn zC*IJTmP^?NhE_U+D~cMNR^;SdriYo;F?Zga`uWQ zN5DDkMx-Mr$1hEaw0^&sx;AZ)U(V4xiB~}NFm0`o zpjgKZo3p1b_Q3*C&&$oYT=1T>;j$m;$lw?8cT8JvBq)TiVHgVf4!K`(EGx<`vcL1* zDn%cBo}UG9Pf zQ4}@#PZiR01qmPKY?F{wqU1{f)3nCCNh|m<>0_;xLmH#{ss@|=hHZ}IzZ2I2W|tr| z#4w>B24I7NpP_0H-m^{dyrDsYAp0i$d9C~kPN4kYkXT`ArpoQtoNqq)|$^U*a6jtpu6YmI@ADZBxX%Hw*=)HE9x* z_(moo<*)(ME2tU<6Mh{b`1QmA%s~)=PV|^rI_h8nTR;b2sbElaY@4FA!MbYt$GFBc zkjLx1f~O`VFsyPP>U-XIUE3rw1m7t6Zv%`3X>F1Mw0g;LdI7*1@fQbdyO?k^hq-%#AO-ND#Mkn?LDQw$ zDpeI}v`W4X9oy8?8fs8v+((?HNJu)JU0dWmN#6Jf8#ngZx~|H#Tuad!rk9Q7XF{ub zj$C@y5#5yvCO9IlwMo7j@nM$IbDlQ7)ViKy>Dh@OG3%(HV;By&_;xpID&exAIMs}^ zkCI>`$?AP)ap2<=z+OR4TQ(kT?%F2VP#K`tXQ!cm0Xnx?Qr_7YD}9aW$6|vs;RvYf&{LoZ^{R -typedef NS_ENUM(NSInteger, ShareDataSourceMode) -{ - DataSourceModePeople, - DataSourceModeRooms -}; +@class ShareDataSource; +@protocol ShareDataSourceDelegate + +- (void)shareDataSourceDidChangeSelectedRoomIdentifiers:(ShareDataSource *)shareDataSource; + +@end @interface ShareDataSource : MXKRecentsDataSource -- (instancetype)initWithMode:(ShareDataSourceMode)dataSourceMode - fileStore:(MXFileStore *)fileStore - credentials:(MXCredentials *)credentials; +@property (nonatomic, weak) id shareDelegate; -/** - Returns the cell data at the index path - - @param indexPath the index of the cell - @return the MXKRecentCellData instance if it exists - */ -- (MXKRecentCellData *)cellDataAtIndexPath:(NSIndexPath *)indexPath; +@property (nonatomic, strong, readonly) NSSet *selectedRoomIdentifiers; + +- (instancetype)initWithFileStore:(MXFileStore *)fileStore + credentials:(MXCredentials *)credentials; + +- (void)selectRoomWithIdentifier:(NSString *)roomIdentifier animated:(BOOL)animated; + +- (void)deselectRoomWithIdentifier:(NSString *)roomIdentifier animated:(BOOL)animated; @end diff --git a/RiotShareExtension/Shared/ShareDataSource.m b/RiotShareExtension/Shared/ShareDataSource.m index b88a846a4..4bee2a44a 100644 --- a/RiotShareExtension/Shared/ShareDataSource.m +++ b/RiotShareExtension/Shared/ShareDataSource.m @@ -19,27 +19,28 @@ @interface ShareDataSource () -@property (nonatomic, assign, readonly) ShareDataSourceMode dataSourceMode; @property (nonatomic, strong, readonly) MXFileStore *fileStore; @property (nonatomic, strong, readonly) MXCredentials *credentials; @property NSArray *recentCellDatas; @property NSMutableArray *visibleRoomCellDatas; +@property (nonatomic, strong) NSMutableSet *internalSelectedRoomIdentifiers; + @end @implementation ShareDataSource -- (instancetype)initWithMode:(ShareDataSourceMode)dataSourceMode - fileStore:(MXFileStore *)fileStore - credentials:(MXCredentials *)credentials +- (instancetype)initWithFileStore:(MXFileStore *)fileStore + credentials:(MXCredentials *)credentials { if (self = [super init]) { - _dataSourceMode = dataSourceMode; _fileStore = fileStore; _credentials = credentials; + _internalSelectedRoomIdentifiers = [NSMutableSet set]; + [self loadCellData]; } return self; @@ -53,6 +54,25 @@ _visibleRoomCellDatas = nil; } +- (NSSet *)selectedRoomIdentifiers +{ + return self.internalSelectedRoomIdentifiers.copy; +} + +- (void)selectRoomWithIdentifier:(NSString *)roomIdentifier animated:(BOOL)animated +{ + [self.internalSelectedRoomIdentifiers addObject:roomIdentifier]; + + [self.shareDelegate shareDataSourceDidChangeSelectedRoomIdentifiers:self]; +} + +- (void)deselectRoomWithIdentifier:(NSString *)roomIdentifier animated:(BOOL)animated +{ + [self.internalSelectedRoomIdentifiers removeObject:roomIdentifier]; + + [self.shareDelegate shareDataSourceDidChangeSelectedRoomIdentifiers:self]; +} + #pragma mark - Private - (void)loadCellData @@ -66,7 +86,7 @@ for (MXRoomSummary *roomSummary in roomsSummaries) { - if (!roomSummary.hiddenFromUser && ((self.dataSourceMode == DataSourceModeRooms) ^ roomSummary.isDirect)) + if (!roomSummary.hiddenFromUser) { [roomSummary setMatrixSession:session]; @@ -137,6 +157,7 @@ { self.visibleRoomCellDatas = nil; } + [self.delegate dataSource:self didCellChange:nil]; } @@ -160,7 +181,11 @@ { RecentRoomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[RecentRoomTableViewCell defaultReuseIdentifier]]; - [cell render:[self cellDataAtIndexPath:indexPath]]; + MXKRecentCellData *data = [self cellDataAtIndexPath:indexPath]; + + [cell render:data]; + + [cell setCustomSelected:[self.selectedRoomIdentifiers containsObject:data.roomSummary.roomId] animated:NO]; return cell; } diff --git a/RiotShareExtension/Shared/ShareItemProvider/SimpleShareItemProvider.swift b/RiotShareExtension/Shared/ShareItemProvider/SimpleShareItemProvider.swift index 261c160fe..e08435aec 100644 --- a/RiotShareExtension/Shared/ShareItemProvider/SimpleShareItemProvider.swift +++ b/RiotShareExtension/Shared/ShareItemProvider/SimpleShareItemProvider.swift @@ -60,6 +60,12 @@ private class SimpleShareItem: ShareItemProtocol { let items: [ShareItemProtocol] + private override init() { + attachment = nil + textMessage = nil + self.items = [] + } + @objc public init(withAttachment attachment: MXKAttachment) { self.attachment = attachment self.items = [SimpleShareItem(withAttachment: attachment)]; @@ -78,10 +84,18 @@ private class SimpleShareItem: ShareItemProtocol { return } - attachment?.prepareShare({ url in - completion(url, nil) + guard let attachment = attachment else { + fatalError("[SimpleShareItemProvider] Invalid item provider state.") + } + + attachment.prepareShare({ url in + DispatchQueue.main.async { + completion(url, nil) + } }, failure: { error in - completion(nil, error) + DispatchQueue.main.async { + completion(nil, error) + } }) } diff --git a/RiotShareExtension/Shared/ShareManager.h b/RiotShareExtension/Shared/ShareManager.h index 965653d00..bb7245040 100644 --- a/RiotShareExtension/Shared/ShareManager.h +++ b/RiotShareExtension/Shared/ShareManager.h @@ -20,6 +20,11 @@ NS_ASSUME_NONNULL_BEGIN +typedef NS_ENUM(NSUInteger, ShareManagerType) { + ShareManagerTypeSend, + ShareManagerTypeForward, +}; + typedef NS_ENUM(NSUInteger, ShareManagerResult) { ShareManagerResultFinished, ShareManagerResultCancelled, @@ -30,17 +35,12 @@ typedef NS_ENUM(NSUInteger, ShareManagerResult) { @property (nonatomic, copy) void (^completionCallback)(ShareManagerResult); -- (instancetype)initWithShareItemProvider:(id)shareItemProvider; +- (instancetype)initWithShareItemProvider:(id)shareItemProvider + type:(ShareManagerType)type; - (UIViewController *)mainViewController; @end -@interface NSItemProvider (ShareManager) - -@property BOOL isLoaded; - -@end - NS_ASSUME_NONNULL_END diff --git a/RiotShareExtension/Shared/ShareManager.m b/RiotShareExtension/Shared/ShareManager.m index c5ebeba6b..5ca23a2b1 100644 --- a/RiotShareExtension/Shared/ShareManager.m +++ b/RiotShareExtension/Shared/ShareManager.m @@ -31,6 +31,7 @@ #endif static const CGFloat kLargeImageSizeMaxDimension = 2048.0; +static const CGSize kThumbnailSize = {800.0, 600.0}; typedef NS_ENUM(NSInteger, ImageCompressionMode) { @@ -61,6 +62,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) @implementation ShareManager - (instancetype)initWithShareItemProvider:(id)shareItemProvider + type:(ShareManagerType)type { if (self = [super init]) { @@ -91,7 +93,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) [MXLog configure:configuration]; - _shareViewController = [[ShareViewController alloc] initWithType:ShareViewControllerTypeSend + _shareViewController = [[ShareViewController alloc] initWithType:(type == ShareManagerTypeForward ? ShareViewControllerTypeForward : ShareViewControllerTypeSend) currentState:ShareViewControllerAccountStateNotConfigured]; [_shareViewController setDelegate:self]; @@ -101,7 +103,6 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) [NSBundle mxk_setLanguage:language]; [NSBundle mxk_setFallbackLanguage:@"en"]; - // Check the current matrix user. [self checkUserAccount]; } @@ -117,8 +118,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) #pragma mark - ShareViewControllerDelegate -- (void)shareViewControllerDidRequestShare:(ShareViewController *)shareViewController - forRoomIdentifier:(NSString *)roomIdentifier +- (void)shareViewController:(ShareViewController *)shareViewController didRequestShareForRoomIdentifiers:(NSSet *)roomIdentifiers { MXSession *session = [[MXSession alloc] initWithMatrixRestClient:[[MXRestClient alloc] initWithCredentials:self.userAccount.mxCredentials andOnUnrecognizedCertificateBlock:nil]]; [MXFileStore setPreloadOptions:0]; @@ -129,12 +129,22 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) session.crypto.warnOnUnknowDevices = NO; // Do not warn for unknown devices. We have cross-signing now - MXRoom *selectedRoom = [MXRoom loadRoomFromStore:self.fileStore withRoomId:roomIdentifier matrixSession:session]; - [self sendContentToRoom:selectedRoom success:nil failure:^(NSError *error){ + NSMutableArray *rooms = [NSMutableArray array]; + for (NSString *roomIdentifier in roomIdentifiers) { + MXRoom *room = [MXRoom loadRoomFromStore:self.fileStore withRoomId:roomIdentifier matrixSession:session]; + if (room) { + [rooms addObject:room]; + } + } + + [self sendContentToRooms:rooms success:^{ + self.completionCallback(ShareManagerResultFinished); + } failure:^(NSError *error){ [self showFailureAlert:[VectorL10n roomEventFailedToSend]]; }]; + } failure:^(NSError *error) { - MXLogError(@"[ShareManager] Failed preparign matrix session"); + MXLogError(@"[ShareManager] Failed preparing matrix session"); }]; } @@ -145,16 +155,37 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) #pragma mark - Private -- (void)sendContentToRoom:(MXRoom *)room success:(void(^)(void))success failure:(void(^)(NSError *))failure +- (void)showFailureAlert:(NSString *)title +{ + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleAlert]; + + MXWeakify(self); + UIAlertAction *okAction = [UIAlertAction actionWithTitle:[MatrixKitL10n ok] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + MXStrongifyAndReturnIfNil(self); + + if (self.completionCallback) + { + self.completionCallback(ShareManagerResultFailed); + } + }]; + + [alertController addAction:okAction]; + + [self.mainViewController presentViewController:alertController animated:YES completion:nil]; +} + +- (void)sendContentToRooms:(NSArray *)rooms success:(void(^)(void))success failure:(void(^)(NSError *))failure { [self resetPendingData]; - NSMutableArray > *pendingImagesItemProviders = [NSMutableArray array]; // Used to keep the items associated to pending images (used only when all items are images). - __block NSError *firstRequestError = nil; dispatch_group_t dispatchGroup = dispatch_group_create(); - void (^requestFailure)(NSError*) = ^(NSError *requestError) { + void (^requestSuccess)(void) = ^() { + dispatch_group_leave(dispatchGroup); + }; + + void (^requestFailure)(NSError *) = ^(NSError *requestError) { if (requestError && !firstRequestError) { firstRequestError = requestError; @@ -166,68 +197,93 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) MXWeakify(self); for (id item in self.shareItemProvider.items) { + if (item.type == ShareItemTypeText || item.type == ShareItemTypeURL) { + dispatch_group_enter(dispatchGroup); + [self.shareItemProvider loadItem:item completion:^(id item, NSError *error) { + MXStrongifyAndReturnIfNil(self); + + if (error) + { + requestFailure(error); + return; + } + + NSString *text = nil; + if([item isKindOfClass:[NSString class]]) + { + text = item; + } + else if([item isKindOfClass:[NSURL class]]) + { + text = [(NSURL *)item absoluteString]; + } + + if(text.length == 0) + { + requestFailure(nil); + return; + } + + [self sendText:text toRooms:rooms success:requestSuccess failure:requestFailure]; + }]; + } + if (item.type == ShareItemTypeFileURL) { dispatch_group_enter(dispatchGroup); [self.shareItemProvider loadItem:item completion:^(NSURL *url, NSError *error) { - if (error) { + MXStrongifyAndReturnIfNil(self); + + if (error) + { requestFailure(error); - dispatch_group_leave(dispatchGroup); return; } - dispatch_async(dispatch_get_main_queue(), ^{ - MXStrongifyAndReturnIfNil(self); - [self sendFileWithUrl:url toRoom:room success:^{ - dispatch_group_leave(dispatchGroup); - } failure:requestFailure]; - }); + [self sendFileWithUrl:url toRooms:rooms success:requestSuccess failure:requestFailure]; }]; } - if (item.type == ShareItemTypeText) { - dispatch_group_enter(dispatchGroup); - [self.shareItemProvider loadItem:item completion:^(NSString *text, NSError *error) { - if (error) { - requestFailure(error); - dispatch_group_leave(dispatchGroup); - return; - } - - dispatch_async(dispatch_get_main_queue(), ^{ - MXStrongifyAndReturnIfNil(self); - [self sendText:text toRoom:room success:^{ - dispatch_group_leave(dispatchGroup); - } failure:requestFailure]; - }); - }]; - } - - if (item.type == ShareItemTypeURL) + if (item.type == ShareItemTypeVideo || item.type == ShareItemTypeMovie) { dispatch_group_enter(dispatchGroup); - [self.shareItemProvider loadItem:item completion:^(NSURL *url, NSError *error) { - if (error) { + [self.shareItemProvider loadItem:item completion:^(NSURL *videoLocalUrl, NSError *error) { + MXStrongifyAndReturnIfNil(self); + + if (error) + { requestFailure(error); - dispatch_group_leave(dispatchGroup); return; } - dispatch_async(dispatch_get_main_queue(), ^{ - MXStrongifyAndReturnIfNil(self); - [self sendText:url.absoluteString toRoom:room success:^{ - dispatch_group_leave(dispatchGroup); - } failure:requestFailure]; - }); + [self sendVideo:videoLocalUrl toRooms:rooms success:requestSuccess failure:requestFailure]; }]; } + if (item.type == ShareItemTypeVoiceMessage) + { + dispatch_group_enter(dispatchGroup); + [self.shareItemProvider loadItem:item completion:^(NSURL *fileURL, NSError *error) { + MXStrongifyAndReturnIfNil(self); + + if (error) + { + requestFailure(error); + return; + } + + [self sendVoiceMessage:fileURL toRooms:rooms success:requestSuccess failure:requestFailure]; + }]; + } + if (item.type == ShareItemTypeImage) { dispatch_group_enter(dispatchGroup); [self.shareItemProvider loadItem:item completion:^(id itemProviderItem, NSError *error) { - if (error) { + MXStrongifyAndReturnIfNil(self); + + if (error) + { requestFailure(error); - dispatch_group_leave(dispatchGroup); return; } @@ -250,30 +306,23 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) imageData = UIImagePNGRepresentation(image); } - MXStrongifyAndReturnIfNil(self); - - if (imageData) + if (!imageData) { - if ([self.shareItemProvider areAllItemsImages]) - { - [self.pendingImages addObject:imageData]; - [pendingImagesItemProviders addObject:item]; - } - else - { - CGSize imageSize = [self imageSizeFromImageData:imageData]; - self.imageCompressionMode = ImageCompressionModeNone; - self.actualLargeSize = MAX(imageSize.width, imageSize.height); - - [self sendImageData:imageData withItem:item toRoom:room success:^{ - dispatch_group_leave(dispatchGroup); - } failure:requestFailure]; - } + requestFailure(error); + return; + } + + if ([self.shareItemProvider areAllItemsImages]) + { + [self.pendingImages addObject:imageData]; } else { - MXLogError(@"[ShareManager] sendContentToRoom: failed to loadItemForTypeIdentifier. Error: %@", error); - dispatch_group_leave(dispatchGroup); + CGSize imageSize = [self imageSizeFromImageData:imageData]; + self.imageCompressionMode = ImageCompressionModeNone; + self.actualLargeSize = MAX(imageSize.width, imageSize.height); + + [self sendImageData:imageData toRooms:rooms success:requestSuccess failure:requestFailure]; } // Only prompt for image resize if all items are images @@ -283,9 +332,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) if ([self.shareItemProvider areAllItemsLoaded]) { UIAlertController *compressionPrompt = [self compressionPromptForPendingImagesWithShareBlock:^{ - [self sendImageDatas:self.pendingImages.copy withItems:pendingImagesItemProviders toRoom:room success:^{ - dispatch_group_leave(dispatchGroup); - } failure:requestFailure]; + [self sendImageDatas:self.pendingImages.copy toRooms:rooms success:requestSuccess failure:requestFailure]; }]; if (compressionPrompt) @@ -300,63 +347,6 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) } }]; } - - if (item.type == ShareItemTypeVideo) - { - dispatch_group_enter(dispatchGroup); - [self.shareItemProvider loadItem:item completion:^(NSURL *videoLocalUrl, NSError *error) { - if (error) { - requestFailure(error); - dispatch_group_leave(dispatchGroup); - return; - } - - dispatch_async(dispatch_get_main_queue(), ^{ - MXStrongifyAndReturnIfNil(self); - [self sendVideo:videoLocalUrl toRoom:room success:^{ - dispatch_group_leave(dispatchGroup); - } failure:requestFailure]; - }); - }]; - } - - if (item.type == ShareItemTypeMovie) - { - dispatch_group_enter(dispatchGroup); - [self.shareItemProvider loadItem:item completion:^(NSURL *videoLocalUrl, NSError *error) { - if (error) { - requestFailure(error); - dispatch_group_leave(dispatchGroup); - return; - } - - dispatch_async(dispatch_get_main_queue(), ^{ - MXStrongifyAndReturnIfNil(self); - [self sendVideo:videoLocalUrl toRoom:room success:^{ - dispatch_group_leave(dispatchGroup); - } failure:requestFailure]; - }); - }]; - } - - if (item.type == ShareItemTypeVoiceMessage) - { - dispatch_group_enter(dispatchGroup); - [self.shareItemProvider loadItem:item completion:^(NSURL *fileURL, NSError *error) { - if (error) { - requestFailure(error); - dispatch_group_leave(dispatchGroup); - return; - } - - dispatch_async(dispatch_get_main_queue(), ^{ - MXStrongifyAndReturnIfNil(self); - [self sendVoiceMessage:fileURL toRoom:room success:^{ - dispatch_group_leave(dispatchGroup); - } failure:requestFailure]; - }); - }]; - } } dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{ @@ -368,30 +358,11 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) } else { - self.completionCallback(ShareManagerResultFinished); + success(); } }); } -- (void)showFailureAlert:(NSString *)title -{ - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleAlert]; - - MXWeakify(self); - UIAlertAction *okAction = [UIAlertAction actionWithTitle:[MatrixKitL10n ok] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - MXStrongifyAndReturnIfNil(self); - - if (self.completionCallback) - { - self.completionCallback(ShareManagerResultFailed); - } - }]; - - [alertController addAction:okAction]; - - [self.mainViewController presentViewController:alertController animated:YES completion:nil]; -} - - (void)checkUserAccount { // Force account manager to reload account from the local storage. @@ -428,21 +399,14 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) { _fileStore = [[MXFileStore alloc] initWithCredentials:self.userAccount.mxCredentials]; - ShareDataSource *roomDataSource = [[ShareDataSource alloc] initWithMode:DataSourceModeRooms - fileStore:_fileStore - credentials:self.userAccount.mxCredentials]; - - ShareDataSource *peopleDataSource = [[ShareDataSource alloc] initWithMode:DataSourceModePeople - fileStore:_fileStore - credentials:self.userAccount.mxCredentials]; + ShareDataSource *roomDataSource = [[ShareDataSource alloc] initWithFileStore:_fileStore + credentials:self.userAccount.mxCredentials]; [self.shareViewController configureWithState:ShareViewControllerAccountStateConfigured - roomDataSource:roomDataSource - peopleDataSource:peopleDataSource]; + roomDataSource:roomDataSource]; } else { [self.shareViewController configureWithState:ShareViewControllerAccountStateNotConfigured - roomDataSource:nil - peopleDataSource:nil]; + roomDataSource:nil]; } } @@ -581,7 +545,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) return compressionPrompt; } -- (void)didStartSendingToRoom:(MXRoom *)room +- (void)didStartSending { [self.shareViewController showProgressIndicator]; } @@ -738,11 +702,9 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) - (void)presentCompressionPrompt:(UIAlertController *)compressionPrompt { - dispatch_async(dispatch_get_main_queue(), ^{ - [compressionPrompt popoverPresentationController].sourceView = self.mainViewController.view; - [compressionPrompt popoverPresentationController].sourceRect = self.mainViewController.view.frame; - [self.mainViewController presentViewController:compressionPrompt animated:YES completion:nil]; - }); + [compressionPrompt popoverPresentationController].sourceView = self.mainViewController.view; + [compressionPrompt popoverPresentationController].sourceRect = self.mainViewController.view.frame; + [self.mainViewController presentViewController:compressionPrompt animated:YES completion:nil]; } #pragma mark - Notifications @@ -781,11 +743,11 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) #pragma mark - Sharing - (void)sendText:(NSString *)text - toRoom:(MXRoom *)room + toRooms:(NSArray *)rooms success:(dispatch_block_t)success failure:(void(^)(NSError *error))failure { - [self didStartSendingToRoom:room]; + [self didStartSending]; if (!text) { MXLogError(@"[ShareManager] Invalid text."); @@ -793,19 +755,34 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) return; } - [room sendTextMessage:text success:^(NSString *eventId) { - success(); - } failure:^(NSError *error) { - MXLogError(@"[ShareManager] sendTextMessage failed with error %@", error); - failure(error); - }]; + __block NSError *error = nil; + dispatch_group_t dispatchGroup = dispatch_group_create(); + for (MXRoom *room in rooms) { + dispatch_group_enter(dispatchGroup); + [room sendTextMessage:text success:^(NSString *eventId) { + dispatch_group_leave(dispatchGroup); + } failure:^(NSError *innerError) { + MXLogError(@"[ShareManager] sendTextMessage failed with error %@", error); + error = innerError; + dispatch_group_leave(dispatchGroup); + }]; + } + + dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{ + if(error) { + failure(error); + } else { + success(); + } + }); } -- (void)sendFileWithUrl:(NSURL *)fileUrl toRoom:(MXRoom *)room - success:(dispatch_block_t)success - failure:(void(^)(NSError *error))failure +- (void)sendFileWithUrl:(NSURL *)fileUrl + toRooms:(NSArray *)rooms + success:(dispatch_block_t)success + failure:(void(^)(NSError *error))failure { - [self didStartSendingToRoom:room]; + [self didStartSending]; if (!fileUrl) { MXLogError(@"[ShareManager] Invalid file url."); @@ -818,21 +795,185 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) mimeType = [self mimeTypeFromUTI:(__bridge NSString *)uti]; CFRelease(uti); - [room sendFile:fileUrl mimeType:mimeType localEcho:nil success:^(NSString *eventId) { - success(); - } failure:^(NSError *error) { - MXLogError(@"[ShareManager] sendFile failed with error %@", error); - failure(error); - } keepActualFilename:YES]; + __block NSError *error = nil; + dispatch_group_t dispatchGroup = dispatch_group_create(); + for (MXRoom *room in rooms) { + dispatch_group_enter(dispatchGroup); + [room sendFile:fileUrl mimeType:mimeType localEcho:nil success:^(NSString *eventId) { + dispatch_group_leave(dispatchGroup); + } failure:^(NSError *innerError) { + MXLogError(@"[ShareManager] sendFile failed with error %@", innerError); + error = innerError; + dispatch_group_leave(dispatchGroup); + } keepActualFilename:YES]; + } + + dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{ + if(error) { + failure(error); + } else { + success(); + } + }); +} + +- (void)sendVideo:(NSURL *)videoLocalUrl + toRooms:(NSArray *)rooms + success:(dispatch_block_t)success + failure:(void(^)(NSError *error))failure +{ + AVURLAsset *videoAsset = [[AVURLAsset alloc] initWithURL:videoLocalUrl options:nil]; + + MXWeakify(self); + + // Ignore showMediaCompressionPrompt setting due to memory constraints when encrypting large videos. + UIAlertController *compressionPrompt = [MXKTools videoConversionPromptForVideoAsset:videoAsset withCompletion:^(NSString *presetName) { + MXStrongifyAndReturnIfNil(self); + + // If the preset name is nil, the user cancelled. + if (!presetName) + { + return; + } + + // Set the chosen video conversion preset. + [MXSDKOptions sharedInstance].videoConversionPresetName = presetName; + + [self didStartSending]; + if (!videoLocalUrl) + { + MXLogError(@"[ShareManager] Invalid video file url."); + failure(nil); + return; + } + + // Retrieve the video frame at 1 sec to define the video thumbnail + AVAssetImageGenerator *assetImageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:videoAsset]; + assetImageGenerator.appliesPreferredTrackTransform = YES; + CMTime time = CMTimeMake(1, 1); + CGImageRef imageRef = [assetImageGenerator copyCGImageAtTime:time actualTime:NULL error:nil]; + // Finalize video attachment + UIImage *videoThumbnail = [[UIImage alloc] initWithCGImage:imageRef]; + CFRelease(imageRef); + + __block NSError *error = nil; + dispatch_group_t dispatchGroup = dispatch_group_create(); + for (MXRoom *room in rooms) { + dispatch_group_enter(dispatchGroup); + [room sendVideoAsset:videoAsset withThumbnail:videoThumbnail localEcho:nil success:^(NSString *eventId) { + dispatch_group_leave(dispatchGroup); + } failure:^(NSError *innerError) { + MXLogError(@"[ShareManager] Failed sending video with error %@", innerError); + error = innerError; + dispatch_group_leave(dispatchGroup); + }]; + } + + dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{ + if(error) { + failure(error); + } else { + success(); + } + }); + }]; + + [self presentCompressionPrompt:compressionPrompt]; +} + +- (void)sendVoiceMessage:(NSURL *)fileUrl + toRooms:(NSArray *)rooms + success:(dispatch_block_t)success + failure:(void(^)(NSError *error))failure +{ + [self didStartSending]; + if (!fileUrl) + { + MXLogError(@"[ShareManager] Invalid voice message file url."); + failure(nil); + return; + } + + __block NSError *error = nil; + dispatch_group_t dispatchGroup = dispatch_group_create(); + for (MXRoom *room in rooms) { + dispatch_group_enter(dispatchGroup); + [room sendVoiceMessage:fileUrl mimeType:nil duration:0.0 samples:nil localEcho:nil success:^(NSString *eventId) { + dispatch_group_leave(dispatchGroup); + } failure:^(NSError *innerError) { + MXLogError(@"[ShareManager] sendVoiceMessage failed with error %@", error); + error = innerError; + dispatch_group_leave(dispatchGroup); + } keepActualFilename:YES]; + } + + dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{ + if(error) { + failure(error); + } else { + success(); + } + }); +} + +- (void)sendImageDatas:(NSArray> *)imageDatas + toRooms:(NSArray *)rooms + success:(dispatch_block_t)success + failure:(void(^)(NSError *error))failure +{ + if (imageDatas.count == 0) + { + MXLogError(@"[ShareManager] sendImages: no images to send."); + failure(nil); + return; + } + + [self didStartSending]; + + dispatch_group_t requestsGroup = dispatch_group_create(); + __block NSError *firstRequestError; + + NSUInteger index = 0; + + for (NSData *imageData in imageDatas) + { + @autoreleasepool + { + dispatch_group_enter(requestsGroup); + [self sendImageData:imageData toRooms:rooms success:^{ + dispatch_group_leave(requestsGroup); + } failure:^(NSError *error) { + if (error && !firstRequestError) + { + firstRequestError = error; + } + + dispatch_group_leave(requestsGroup); + }]; + } + + index++; + } + + dispatch_group_notify(requestsGroup, dispatch_get_main_queue(), ^{ + + if (firstRequestError) + { + failure(firstRequestError); + } + else + { + success(); + } + }); } - (void)sendImageData:(NSData *)imageData - withItem:(id)item - toRoom:(MXRoom *)room + toRooms:(NSArray *)rooms success:(dispatch_block_t)success failure:(void(^)(NSError *error))failure { - [self didStartSendingToRoom:room]; + [self didStartSending]; NSString *imageUTI; NSString *mimeType; @@ -848,7 +989,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) if (!mimeType) { - MXLogError(@"[ShareManager] sendImage failed. Cannot determine MIME type of %@", item); + MXLogError(@"[ShareManager] sendImage failed. Cannot determine MIME type ."); if (failure) { failure(nil); @@ -921,142 +1062,33 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) imageSize = [self imageSizeFromImageData:imageData]; } - UIImage *thumbnail = nil; - // Thumbnail is useful only in case of encrypted room - if (room.summary.isEncrypted) - { - thumbnail = [MXKTools resizeImageWithData:imageData toFitInSize:CGSizeMake(800, 600)]; - } - - [room sendImage:finalImageData withImageSize:imageSize mimeType:mimeType andThumbnail:thumbnail localEcho:nil success:^(NSString *eventId) { - success(); - } failure:^(NSError *error) { - MXLogError(@"[ShareManager] sendImage failed with error %@", error); - failure(error); - }]; -} - -- (void)sendImageDatas:(NSArray> *)imageDatas - withItems:(NSArray> *)items toRoom:(MXRoom *)room - success:(dispatch_block_t)success - failure:(void(^)(NSError *error))failure -{ - if (imageDatas.count == 0 || imageDatas.count != items.count) - { - MXLogError(@"[ShareManager] sendImages: no images to send."); - failure(nil); - return; - } - - [self didStartSendingToRoom:room]; - - dispatch_group_t requestsGroup = dispatch_group_create(); - __block NSError *firstRequestError; - - NSUInteger index = 0; - - for (NSData *imageData in imageDatas) - { - @autoreleasepool - { - dispatch_group_enter(requestsGroup); - [self sendImageData:imageData withItem:items[index] toRoom:room success:^{ - dispatch_group_leave(requestsGroup); - } failure:^(NSError *error) { - if (error && !firstRequestError) - { - firstRequestError = error; - } + __block NSError *error = nil; + dispatch_group_t dispatchGroup = dispatch_group_create(); + for (MXRoom *room in rooms) { - dispatch_group_leave(requestsGroup); - }]; + UIImage *thumbnail = nil; + if (room.summary.isEncrypted) // Thumbnail is useful only in case of encrypted room + { + thumbnail = [MXKTools resizeImageWithData:imageData toFitInSize:kThumbnailSize]; } - index++; + dispatch_group_enter(dispatchGroup); + [room sendImage:finalImageData withImageSize:imageSize mimeType:mimeType andThumbnail:thumbnail localEcho:nil success:^(NSString *eventId) { + dispatch_group_leave(dispatchGroup); + } failure:^(NSError *innerError) { + MXLogError(@"[ShareManager] sendImage failed with error %@", error); + error = innerError; + dispatch_group_leave(dispatchGroup); + }]; } - dispatch_group_notify(requestsGroup, dispatch_get_main_queue(), ^{ - - if (firstRequestError) - { - failure(firstRequestError); - } - else - { + dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{ + if(error) { + failure(error); + } else { success(); } }); } -- (void)sendVideo:(NSURL *)videoLocalUrl - toRoom:(MXRoom *)room - success:(dispatch_block_t)success - failure:(void(^)(NSError *error))failure -{ - AVURLAsset *videoAsset = [[AVURLAsset alloc] initWithURL:videoLocalUrl options:nil]; - - MXWeakify(self); - - // Ignore showMediaCompressionPrompt setting due to memory constraints when encrypting large videos. - UIAlertController *compressionPrompt = [MXKTools videoConversionPromptForVideoAsset:videoAsset withCompletion:^(NSString *presetName) { - MXStrongifyAndReturnIfNil(self); - - // If the preset name is nil, the user cancelled. - if (!presetName) - { - return; - } - - // Set the chosen video conversion preset. - [MXSDKOptions sharedInstance].videoConversionPresetName = presetName; - - [self didStartSendingToRoom:room]; - if (!videoLocalUrl) - { - MXLogError(@"[ShareManager] Invalid video file url."); - failure(nil); - return; - } - - // Retrieve the video frame at 1 sec to define the video thumbnail - AVAssetImageGenerator *assetImageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:videoAsset]; - assetImageGenerator.appliesPreferredTrackTransform = YES; - CMTime time = CMTimeMake(1, 1); - CGImageRef imageRef = [assetImageGenerator copyCGImageAtTime:time actualTime:NULL error:nil]; - // Finalize video attachment - UIImage *videoThumbnail = [[UIImage alloc] initWithCGImage:imageRef]; - CFRelease(imageRef); - - [room sendVideoAsset:videoAsset withThumbnail:videoThumbnail localEcho:nil success:^(NSString *eventId) { - success(); - } failure:^(NSError *error) { - MXLogError(@"[ShareManager] Failed sending video with error %@", error); - failure(error); - }]; - }]; - - [self presentCompressionPrompt:compressionPrompt]; -} - -- (void)sendVoiceMessage:(NSURL *)fileUrl - toRoom:(MXRoom *)room - success:(dispatch_block_t)success - failure:(void(^)(NSError *error))failure -{ - [self didStartSendingToRoom:room]; - if (!fileUrl) - { - MXLogError(@"[ShareManager] Invalid voice message file url."); - failure(nil); - return; - } - - [room sendVoiceMessage:fileUrl mimeType:nil duration:0.0 samples:nil localEcho:nil success:^(NSString *eventId) { - success(); - } failure:^(NSError *error) { - MXLogError(@"[ShareManager] sendVoiceMessage failed with error %@", error); - failure(error); - } keepActualFilename:YES]; -} - @end diff --git a/RiotShareExtension/Shared/View/RecentRoomTableViewCell.h b/RiotShareExtension/Shared/View/RecentRoomTableViewCell.h index 959ca786e..63eb213f7 100644 --- a/RiotShareExtension/Shared/View/RecentRoomTableViewCell.h +++ b/RiotShareExtension/Shared/View/RecentRoomTableViewCell.h @@ -20,4 +20,6 @@ + (CGFloat)cellHeight; +- (void)setCustomSelected:(BOOL)selected animated:(BOOL)animated; + @end diff --git a/RiotShareExtension/Shared/View/RecentRoomTableViewCell.m b/RiotShareExtension/Shared/View/RecentRoomTableViewCell.m index 5abb551d9..4dec0815a 100644 --- a/RiotShareExtension/Shared/View/RecentRoomTableViewCell.m +++ b/RiotShareExtension/Shared/View/RecentRoomTableViewCell.m @@ -30,6 +30,7 @@ @property (weak, nonatomic) IBOutlet MXKImageView *avatarImageView; @property (weak, nonatomic) IBOutlet UILabel *roomTitleLabel; @property (weak, nonatomic) IBOutlet UIImageView *encryptedRoomIcon; +@property (weak, nonatomic) IBOutlet UIButton *selectionButton; @end @@ -56,6 +57,12 @@ self.roomTitleLabel.textColor = ThemeService.shared.theme.textPrimaryColor; self.contentView.backgroundColor = ThemeService.shared.theme.backgroundColor; + + [self.selectionButton setImage:[UIImage imageNamed:@"radio-button-default"] forState:UIControlStateNormal]; + [self.selectionButton setImage:[UIImage imageNamed:@"radio-button-selected"] forState:UIControlStateSelected]; + + [self.selectionButton setTitle:@"" forState:UIControlStateNormal]; + [self.selectionButton setTitle:@"" forState:UIControlStateSelected]; } - (void)layoutSubviews @@ -92,4 +99,11 @@ return 74; } +- (void)setCustomSelected:(BOOL)selected animated:(BOOL)animated +{ + [UIView animateWithDuration:(animated ? 0.25f : 0.0f) animations:^{ + [self.selectionButton setSelected:selected]; + }]; +} + @end diff --git a/RiotShareExtension/Shared/View/RecentRoomTableViewCell.xib b/RiotShareExtension/Shared/View/RecentRoomTableViewCell.xib index 80e07581e..fe39d2a67 100644 --- a/RiotShareExtension/Shared/View/RecentRoomTableViewCell.xib +++ b/RiotShareExtension/Shared/View/RecentRoomTableViewCell.xib @@ -1,9 +1,9 @@ - + - + @@ -25,12 +25,9 @@ - + diff --git a/RiotShareExtension/Shared/View/RoomsListViewController.m b/RiotShareExtension/Shared/View/RoomsListViewController.m index 8f387a4d4..414e0364a 100644 --- a/RiotShareExtension/Shared/View/RoomsListViewController.m +++ b/RiotShareExtension/Shared/View/RoomsListViewController.m @@ -18,6 +18,7 @@ #import "RoomsListViewController.h" #import "RecentRoomTableViewCell.h" +#import "ShareDataSource.h" #import "RecentCellData.h" #import "ThemeService.h" @@ -140,6 +141,22 @@ return [RecentRoomTableViewCell cellHeight]; } +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + + NSString *roomIdentifier = [self.dataSource cellDataAtIndexPath:indexPath].roomSummary.roomId; + + ShareDataSource *dataSource = (ShareDataSource *)self.dataSource; + if ([dataSource.selectedRoomIdentifiers containsObject:roomIdentifier]) { + [dataSource deselectRoomWithIdentifier:roomIdentifier animated:YES]; + } else { + [dataSource selectRoomWithIdentifier:roomIdentifier animated:YES]; + } + + [self.recentsTableView reloadData]; +} + #pragma mark - MXKDataSourceDelegate - (Class)cellViewClassForCellData:(MXKCellData*)cellData diff --git a/RiotShareExtension/Shared/View/ShareViewController.h b/RiotShareExtension/Shared/View/ShareViewController.h index edf5ce348..d45a66049 100644 --- a/RiotShareExtension/Shared/View/ShareViewController.h +++ b/RiotShareExtension/Shared/View/ShareViewController.h @@ -33,9 +33,7 @@ typedef NS_ENUM(NSUInteger, ShareViewControllerAccountState) { @protocol ShareViewControllerDelegate -- (void)shareViewControllerDidRequestShare:(ShareViewController *)shareViewController - forRoomIdentifier:(NSString *)roomIdentifier; - +- (void)shareViewController:(ShareViewController *)shareViewController didRequestShareForRoomIdentifiers:(NSSet *)roomIdentifiers; - (void)shareViewControllerDidRequestDismissal:(ShareViewController *)shareViewController; @end @@ -48,8 +46,7 @@ typedef NS_ENUM(NSUInteger, ShareViewControllerAccountState) { currentState:(ShareViewControllerAccountState)state; - (void)configureWithState:(ShareViewControllerAccountState)state - roomDataSource:(nullable ShareDataSource *)roomDataSource - peopleDataSource:(nullable ShareDataSource *)peopleDataSource; + roomDataSource:(nullable ShareDataSource *)roomDataSource; - (void)showProgressIndicator; diff --git a/RiotShareExtension/Shared/View/ShareViewController.m b/RiotShareExtension/Shared/View/ShareViewController.m index 148069665..cfe4f2a87 100644 --- a/RiotShareExtension/Shared/View/ShareViewController.m +++ b/RiotShareExtension/Shared/View/ShareViewController.m @@ -15,10 +15,9 @@ */ #import "ShareViewController.h" -#import "SegmentedViewController.h" +#import "ShareDataSource.h" #import "RoomsListViewController.h" #import "FallbackViewController.h" -#import "ShareDataSource.h" #import "ThemeService.h" @@ -28,13 +27,16 @@ #import "Riot-Swift.h" #endif -@interface ShareViewController () +@interface ShareViewController () @property (nonatomic, assign, readonly) ShareViewControllerType type; @property (nonatomic, assign) ShareViewControllerAccountState state; + +@property (nonatomic, strong) RoomsListViewController *roomListViewController; @property (nonatomic, strong) ShareDataSource *roomDataSource; -@property (nonatomic, strong) ShareDataSource *peopleDataSource; + +@property (nonatomic, strong) FallbackViewController *fallbackViewController; @property (nonatomic, weak) IBOutlet UIView *masterContainerView; @property (nonatomic, weak) IBOutlet UIButton *cancelButton; @@ -42,8 +44,6 @@ @property (nonatomic, weak) IBOutlet UIButton *shareButton; @property (nonatomic, weak) IBOutlet UIView *contentView; -@property (nonatomic, strong) SegmentedViewController *segmentedViewController; - @property (nonatomic, strong) MXKPieChartHUD *hudView; @end @@ -76,17 +76,17 @@ [self.cancelButton setTitle:[VectorL10n cancel] forState:UIControlStateNormal]; [self.shareButton setTintColor:ThemeService.shared.theme.tintColor]; + [self.shareButton setEnabled:NO]; - [self configureWithState:self.state roomDataSource:self.roomDataSource peopleDataSource:self.peopleDataSource]; + [self configureWithState:self.state roomDataSource:self.roomDataSource]; } - (void)configureWithState:(ShareViewControllerAccountState)state roomDataSource:(ShareDataSource *)roomDataSource - peopleDataSource:(ShareDataSource *)peopleDataSource { self.state = state; self.roomDataSource = roomDataSource; - self.peopleDataSource = peopleDataSource; + self.roomDataSource.shareDelegate = self; if (!self.isViewLoaded) { return; @@ -110,19 +110,11 @@ [self.hudView setProgress:progress]; } -#pragma mark - MXKRecentListViewControllerDelegate +#pragma mark - ShareDataSourceDelegate -- (void)recentListViewController:(MXKRecentListViewController *)recentListViewController - didSelectRoom:(NSString *)roomId - inMatrixSession:(MXSession *)mxSession +- (void)shareDataSourceDidChangeSelectedRoomIdentifiers:(ShareDataSource *)shareDataSource { - [self.delegate shareViewControllerDidRequestShare:self forRoomIdentifier:roomId]; -} - -- (void)recentListViewController:(MXKRecentListViewController *)recentListViewController - didSelectSuggestedRoom:(MXSpaceChildInfo *)childInfo -{ - [self.delegate shareViewControllerDidRequestShare:self forRoomIdentifier:childInfo.childRoomId]; + self.shareButton.enabled = (shareDataSource.selectedRoomIdentifiers.count > 0); } #pragma mark - Private @@ -133,60 +125,53 @@ if (self.state == ShareViewControllerAccountStateConfigured) { - self.titleLabel.text = [VectorL10n sendTo:@""]; - [self.shareButton setTitle:[VectorL10n roomEventActionForward] forState:UIControlStateNormal]; - [self configureSegmentedViewController]; + [self.shareButton setHidden:NO]; + + if (self.type == ShareViewControllerTypeSend) { + [self.titleLabel setText:[VectorL10n sendTo:@""]]; + [self.shareButton setTitle:[VectorL10n sendTo:@""] forState:UIControlStateNormal]; + } else { + [self.titleLabel setText:[VectorL10n roomEventActionForward]]; + [self.shareButton setTitle:[VectorL10n roomEventActionForward] forState:UIControlStateNormal]; + } } else { - self.titleLabel.text = [AppInfo.current displayName]; [self configureFallbackViewController]; + [self.shareButton setHidden:NO]; + + self.titleLabel.text = [AppInfo.current displayName]; } } - (void)configureSegmentedViewController { - RoomsListViewController *roomsViewController = [RoomsListViewController recentListViewController]; - [roomsViewController displayList:self.roomDataSource]; - [roomsViewController setDelegate:self]; - - RoomsListViewController *peopleViewController = [RoomsListViewController recentListViewController]; - [peopleViewController setDelegate:self]; - [peopleViewController displayList:self.peopleDataSource]; - - self.segmentedViewController = [SegmentedViewController segmentedViewController]; - [self.segmentedViewController initWithTitles:@[[VectorL10n titleRooms], [VectorL10n titlePeople]] - viewControllers:@[roomsViewController, peopleViewController] defaultSelected:0]; - - [self addChildViewController:self.segmentedViewController]; - [self.contentView vc_addSubViewMatchingParent:self.segmentedViewController.view]; - [self.segmentedViewController didMoveToParentViewController:self]; + self.roomListViewController = [RoomsListViewController recentListViewController]; + [self.roomListViewController displayList:self.roomDataSource]; + + [self addChildViewController:self.roomListViewController]; + [self.contentView vc_addSubViewMatchingParent:self.roomListViewController.view]; + [self.roomListViewController didMoveToParentViewController:self]; } - (void)configureFallbackViewController { - FallbackViewController *fallbackVC = [FallbackViewController new]; - [self addChildViewController:fallbackVC]; - [self.contentView vc_addSubViewMatchingParent:fallbackVC.view]; - [fallbackVC didMoveToParentViewController:self]; + self.fallbackViewController = [FallbackViewController new]; + [self addChildViewController:self.fallbackViewController]; + [self.contentView vc_addSubViewMatchingParent:self.fallbackViewController.view]; + [self.fallbackViewController didMoveToParentViewController:self]; } - (void)resetContentView { - NSArray *subviews = self.contentView.subviews; - for (UIView *subview in subviews) - { - [subview removeFromSuperview]; - } + [self.roomListViewController willMoveToParentViewController:nil]; + [self.roomListViewController.view removeFromSuperview]; + [self.roomListViewController removeFromParentViewController]; - if (self.segmentedViewController) - { - [self.segmentedViewController removeFromParentViewController]; - - [self.segmentedViewController destroy]; - self.segmentedViewController = nil; - } + [self.fallbackViewController willMoveToParentViewController:nil]; + [self.fallbackViewController.view removeFromSuperview]; + [self.fallbackViewController removeFromParentViewController]; } #pragma mark - Actions @@ -198,7 +183,11 @@ - (IBAction)onShareButtonTap:(UIButton *)sender { + if (self.roomDataSource.selectedRoomIdentifiers.count == 0) { + return; + } + [self.delegate shareViewController:self didRequestShareForRoomIdentifiers:self.roomDataSource.selectedRoomIdentifiers]; } @end diff --git a/RiotShareExtension/Shared/View/ShareViewController.xib b/RiotShareExtension/Shared/View/ShareViewController.xib index c6eaf5feb..ece4f812b 100644 --- a/RiotShareExtension/Shared/View/ShareViewController.xib +++ b/RiotShareExtension/Shared/View/ShareViewController.xib @@ -42,7 +42,7 @@ -