Merge branch 'fix/share_function'

This commit is contained in:
Danmei Chen 2018-10-08 15:09:06 +02:00
commit 5120deaf30
11 changed files with 126 additions and 77 deletions

View file

@ -28,6 +28,8 @@
@protocol ChatConversationDelegate <NSObject>
- (BOOL)startImageUpload:(UIImage *)image assetId:(NSString *)phAssetId withQuality:(float)quality;
- (BOOL)startFileUpload:(NSData *)data assetId:(NSString *)phAssetId;
- (BOOL)startFileUpload:(NSData *)data withName:(NSString *)name;
- (void)resendChat:(NSString *)message withExternalUrl:(NSString *)url;
- (void)tableViewIsScrolling;

View file

@ -32,7 +32,7 @@
#include "linphone/linphonecore.h"
@interface ChatConversationView
: TPMultiLayoutViewController <HPGrowingTextViewDelegate, UICompositeViewDelegate, ImagePickerDelegate, ChatConversationDelegate, UIDocumentPickerDelegate,
: TPMultiLayoutViewController <HPGrowingTextViewDelegate, UICompositeViewDelegate, ImagePickerDelegate, ChatConversationDelegate,
UIDocumentInteractionControllerDelegate, UISearchBarDelegate, UIImageViewDeletableDelegate, UICollectionViewDataSource> {
OrderedDictionary *imageQualities;
BOOL scrollOnGrowingEnabled;
@ -61,7 +61,6 @@
@property (weak, nonatomic) IBOutlet UIIconButton *infoButton;
@property (weak, nonatomic) IBOutlet UILabel *particpantsLabel;
@property (nonatomic, strong) UIDocumentInteractionController *documentInteractionController;
@property (nonatomic, strong) UIDocumentPickerViewController *documentPicker;
@property NSMutableArray <UIImage *> *imagesArray;
@property NSMutableArray <NSString *> *assetIdsArray;
@property NSMutableArray <NSNumber *> *qualitySettingsArray;
@ -81,7 +80,6 @@
- (IBAction)onDeleteClick:(id)sender;
- (IBAction)onEditionChangeClick:(id)sender;
- (void)update;
- (void)getIcloudFiles;
- (void)openFile:(NSString *) filePath;
- (void)clearMessageView;

View file

@ -626,7 +626,7 @@ static UICompositeViewDescription *compositeDescription = nil;
return TRUE;
}
- (BOOL)startFileUpload:(NSData *)data assetId:phAssetId {
- (BOOL)startFileUpload:(NSData *)data assetId:(NSString *)phAssetId {
FileTransferDelegate *fileTransfer = [[FileTransferDelegate alloc] init];
[fileTransfer uploadVideo:data withassetId:phAssetId forChatRoom:_chatRoom];
[_tableController scrollToBottom:true];
@ -907,16 +907,6 @@ void on_chat_room_conference_left(LinphoneChatRoom *cr, const LinphoneEventLog *
[view.tableController scrollToBottom:true];
}
- (void)getIcloudFiles
{
_documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.data"]
inMode:UIDocumentPickerModeImport];
_documentPicker.delegate = self;
_documentPicker.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:_documentPicker animated:YES completion:nil];
}
- (void)openFile:(NSString *) filePath
{
// Open the controller.
@ -931,21 +921,6 @@ void on_chat_room_conference_left(LinphoneChatRoom *cr, const LinphoneEventLog *
}
}
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url {
NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
[fileCoordinator coordinateReadingItemAtURL:url options:NSFileCoordinatorReadingWithoutChanges error:nil byAccessor:^(NSURL * _Nonnull newURL) {
NSString *fileName = [newURL lastPathComponent];
NSData *data = [NSData dataWithContentsOfURL:newURL];
NSString *filePath = [[LinphoneManager cacheDirectory] stringByAppendingPathComponent:fileName];
[[NSFileManager defaultManager] createFileAtPath:filePath contents:data attributes:nil];
[self openFile:filePath];
}];
}
- (void)deleteImageWithAssetId:(NSString *)assetId {
NSUInteger key = [_assetIdsArray indexOfObject:assetId];
[_imagesArray removeObjectAtIndex:key];

View file

@ -163,9 +163,13 @@ static UICompositeViewDescription *compositeDescription = nil;
[self dismiss];
NSURL *alassetURL = [info objectForKey:UIImagePickerControllerReferenceURL];
PHFetchResult<PHAsset *> *phFetchResult = [PHAsset fetchAssetsWithALAssetURLs:@[alassetURL] options:nil];
PHAsset *phasset = [phFetchResult firstObject];
//PHAsset *phasset = [info objectForKey:UIImagePickerControllerPHAsset];
PHAsset *phasset = nil;
// when photo from camera, it hasn't be saved
if (alassetURL) {
PHFetchResult<PHAsset *> *phFetchResult = [PHAsset fetchAssetsWithALAssetURLs:@[alassetURL] options:nil];
phasset = [phFetchResult firstObject];
}
UIImage *image = [info objectForKey:UIImagePickerControllerEditedImage] ? [info objectForKey:UIImagePickerControllerEditedImage] : [info objectForKey:UIImagePickerControllerOriginalImage];
if (!phasset) {
__block PHObjectPlaceholder *placeHolder;

View file

@ -19,8 +19,10 @@
<outlet property="cancelButton" destination="6dl-Nz-rdv" id="ygz-nv-omC"/>
<outlet property="contactDateLabel" destination="JyR-RQ-uwF" id="Tc4-9t-i5V"/>
<outlet property="downloadButton" destination="N75-gL-R6t" id="EgN-Ab-Ded"/>
<outlet property="fileName" destination="Dho-UV-6Ev" id="Iro-II-w8a"/>
<outlet property="fileButton" destination="IGl-nl-xIE" id="tLL-3L-Byt"/>
<outlet property="fileName" destination="WkE-rP-Y0R" id="bjY-zl-mhs"/>
<outlet property="fileTransferProgress" destination="USm-wC-GvG" id="POt-YD-NCG"/>
<outlet property="fileView" destination="UzU-cc-LbF" id="OlG-uv-m4j"/>
<outlet property="finalAssetView" destination="VYJ-RC-Jmg" id="fnb-mY-nPR"/>
<outlet property="finalImage" destination="gzv-K4-5OL" id="YIw-kM-Ld6"/>
<outlet property="imageGestureRecognizer" destination="aDF-hC-ddO" id="2jh-Rr-eKk"/>
@ -29,7 +31,6 @@
<outlet property="imdmLabel" destination="44j-me-Iqi" id="m5R-Dm-V8g"/>
<outlet property="messageImageView" destination="yMW-cT-bpU" id="MNr-F2-abQ"/>
<outlet property="messageText" destination="cx9-0K-P9L" id="kPh-s4-Ioy"/>
<outlet property="openRecognizer" destination="NYA-II-xYn" id="pVM-vD-4Rg"/>
<outlet property="playButton" destination="cvc-tl-Pcf" id="eKJ-2T-LUl"/>
<outlet property="resendRecognizer" destination="5ZI-Ip-lGl" id="G2r-On-6mV"/>
<outlet property="statusInProgressSpinner" destination="Eab-ND-ix3" id="UuC-eY-MSf"/>
@ -74,14 +75,6 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
<gestureRecognizers/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Label" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Dho-UV-6Ev" userLabel="fileName">
<rect key="frame" x="0.0" y="0.0" width="200" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="GmN-7v-uuO" userLabel="imageSubView">
<rect key="frame" x="0.0" y="155" width="297" height="75"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES"/>
@ -144,7 +137,6 @@
</subviews>
<connections>
<outletCollection property="gestureRecognizers" destination="aDF-hC-ddO" appends="YES" id="lKJ-ra-dwR"/>
<outletCollection property="gestureRecognizers" destination="NYA-II-xYn" appends="YES" id="fK6-ld-zOX"/>
</connections>
</view>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="color_A.png" translatesAutoresizingMaskIntoConstraints="NO" id="6dA-3U-OPW" userLabel="bottomBarColor">
@ -179,6 +171,29 @@
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<accessibility key="accessibilityConfiguration" label="Delivery failed"/>
</imageView>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="UzU-cc-LbF" userLabel="fileView">
<rect key="frame" x="0.0" y="55" width="280" height="128"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Label" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WkE-rP-Y0R" userLabel="fileName">
<rect key="frame" x="25" y="0.0" width="180" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="IGl-nl-xIE" userLabel="fileButton">
<rect key="frame" x="205" y="0.0" width="50" height="50"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" red="1" green="0.39905477280000001" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<state key="normal" title="Open"/>
<connections>
<action selector="onFileClick:" destination="-1" eventType="touchUpInside" id="JRN-AA-UEJ"/>
</connections>
</button>
</subviews>
</view>
</subviews>
<connections>
<outletCollection property="gestureRecognizers" destination="5ZI-Ip-lGl" appends="YES" id="1iY-46-rRR"/>
@ -200,11 +215,6 @@
<action selector="onImageClick:" destination="-1" id="feN-LT-89b"/>
</connections>
</tapGestureRecognizer>
<tapGestureRecognizer id="NYA-II-xYn" userLabel="openClick">
<connections>
<action selector="onOpenClick:" destination="-1" id="XaJ-Or-uQQ"/>
</connections>
</tapGestureRecognizer>
</objects>
<resources>
<image name="avatar.png" width="259" height="259"/>

View file

@ -34,11 +34,12 @@
@property(weak, nonatomic) IBOutlet UIProgressView *fileTransferProgress;
@property(weak, nonatomic) IBOutlet UIButton *cancelButton;
@property(weak, nonatomic) IBOutlet UIView *imageSubView;
@property (strong, nonatomic) IBOutlet UITapGestureRecognizer *openRecognizer;
@property(weak, nonatomic) IBOutlet UIView *totalView;
@property (weak, nonatomic) IBOutlet UIView *finalAssetView;
@property (weak, nonatomic) IBOutlet UIImageView *finalImage;
@property(strong, nonatomic) IBOutlet UITapGestureRecognizer *imageGestureRecognizer;
@property (weak, nonatomic) IBOutlet UIButton *fileButton;
@property (weak, nonatomic) IBOutlet UIView *fileView;
- (void)setEvent:(LinphoneEventLog *)event;
- (void)setChatMessage:(LinphoneChatMessage *)message;
@ -48,8 +49,7 @@
- (IBAction)onCancelClick:(id)sender;
- (IBAction)onResendClick:(id)event;
- (IBAction)onPlayClick:(id)sender;
- (IBAction)onOpenClick:(id)event;
- (IBAction)onFileClick:(id)sender;
@end

View file

@ -72,7 +72,6 @@
- (void)setChatMessage:(LinphoneChatMessage *)amessage {
_imageGestureRecognizer.enabled = NO;
_openRecognizer.enabled = NO;
_messageImageView.image = nil;
_finalImage.image = nil;
_finalImage.hidden = TRUE;
@ -131,9 +130,8 @@
- (void) loadFileAsset {
dispatch_async(dispatch_get_main_queue(), ^{
_fileName.hidden = NO;
_fileName.hidden = _fileView.hidden = _fileButton.hidden = NO;
_imageGestureRecognizer.enabled = NO;
_openRecognizer.enabled = YES;
});
}
@ -167,14 +165,14 @@
if (!(localImage || localVideo || localFile)) {
_playButton.hidden = YES;
_fileName.hidden = YES;
_fileName.hidden = _fileView.hidden = _fileButton.hidden = YES;
_messageImageView.hidden = _cancelButton.hidden = (_ftd.message == nil);
_downloadButton.hidden = !_cancelButton.hidden;
_fileTransferProgress.hidden = NO;
} else {
// file is being saved on device - just wait for it
if ([localImage isEqualToString:@"saving..."] || [localVideo isEqualToString:@"saving..."] || [localFile isEqualToString:@"saving..."]) {
_cancelButton.hidden = _fileTransferProgress.hidden = _downloadButton.hidden = _playButton.hidden = _fileName.hidden = YES;
_cancelButton.hidden = _fileTransferProgress.hidden = _downloadButton.hidden = _playButton.hidden = _fileName.hidden = _fileView.hidden = _fileButton.hidden = YES;
fullScreenImage = YES;
} else if(!assetIsLoaded) {
assetIsLoaded = TRUE;
@ -191,7 +189,7 @@
}
}
else if (localFile) {
NSString *text = [NSString stringWithFormat:@"📎 %@",localFile];
NSString *text = [NSString stringWithFormat:@"📎 %@",localFile];
_fileName.text = text;
[self loadFileAsset];
}
@ -202,12 +200,12 @@
_fileTransferProgress.hidden = NO;
_downloadButton.hidden = YES;
_playButton.hidden = YES;
_fileName.hidden = YES;
_fileName.hidden = _fileView.hidden = _fileButton.hidden =YES;
} else {
_cancelButton.hidden = _fileTransferProgress.hidden = _downloadButton.hidden = YES;
fullScreenImage = YES;
_playButton.hidden = localVideo ? NO : YES;
_fileName.hidden = localFile ? NO : YES;
_fileName.hidden = _fileView.hidden = _fileButton.hidden = localFile ? NO : YES;
// Should fix cell not resizing after doanloading image.
[self layoutSubviews];
}
@ -250,7 +248,7 @@
_cancelButton.hidden = NO;
_downloadButton.hidden = YES;
_playButton.hidden = YES;
_fileName.hidden = YES;
_fileName.hidden = _fileView.hidden = _fileButton.hidden = YES;
}
- (IBAction)onPlayClick:(id)sender {
@ -271,7 +269,7 @@
}];
}
- (IBAction)onOpenClick:(id)event {
- (IBAction)onFileClick:(id)sender {
ChatConversationView *view = VIEW(ChatConversationView);
NSString *cachedFile = [LinphoneManager getMessageAppDataForKey:@"cachedfile" inMessage:self.message];
if (cachedFile) {
@ -281,8 +279,10 @@
} else {
[self fileErrorBlock];
}
} else
[view getIcloudFiles];
} else {
[LinphoneManager setValueInMessageAppData:@"onFileClick" forKey:@"icloudFileOption" inMessage:self.message];
[super getIcloudFiles];
}
}
@ -401,7 +401,7 @@
}
bubbleFrame.origin.x = origin_x;
super.bubbleView.frame = bubbleFrame;
// Resizing Image view

View file

@ -23,7 +23,7 @@
#import "ChatConversationTableView.h"
#import "UIRoundedImageView.h"
@interface UIChatBubbleTextCell : UITableViewCell
@interface UIChatBubbleTextCell : UITableViewCell <UIDocumentPickerDelegate>
@property(readonly, nonatomic) LinphoneEventLog *event;
@property(readonly, nonatomic) LinphoneChatMessage *message;
@ -40,11 +40,14 @@
@property(weak, nonatomic) IBOutlet UIImageView *imdmIcon;
@property(weak, nonatomic) IBOutlet UILabel *imdmLabel;
@property (nonatomic, strong) UIDocumentPickerViewController *documentPicker;
+ (CGSize)ViewSizeForMessage:(LinphoneChatMessage *)chat withWidth:(int)width;
+ (CGSize)getMediaMessageSizefromOriginalSize:(CGSize)originalSize withWidth:(int)width;
- (void)setEvent:(LinphoneEventLog *)event;
- (void)setChatMessage:(LinphoneChatMessage *)message;
- (void)getIcloudFiles;
- (void)onDelete;
- (void)onResend;

View file

@ -128,7 +128,7 @@
_statusInProgressSpinner.accessibilityLabel = @"Delivery in progress";
if (_messageText) {
if (_messageText && ![LinphoneManager getMessageAppDataForKey:@"localvideo" inMessage:_message]) {
LOGD(_messageText.text);
[_messageText setHidden:FALSE];
/* We need to use an attributed string here so that data detector don't mess
@ -207,6 +207,36 @@
[PhoneMainView.instance presentViewController:errView animated:YES completion:nil];
}
- (void)getIcloudFiles {
_documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.data"]
inMode:UIDocumentPickerModeImport];
_documentPicker.delegate = self;
_documentPicker.modalPresentationStyle = UIModalPresentationOverCurrentContext ;
ChatConversationView *view = VIEW(ChatConversationView);
[view presentViewController:_documentPicker animated:YES completion:nil];
}
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url {
NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
[fileCoordinator coordinateReadingItemAtURL:url options:NSFileCoordinatorReadingWithoutChanges error:nil byAccessor:^(NSURL * _Nonnull newURL) {
NSString *fileName = [newURL lastPathComponent];
NSData *data = [NSData dataWithContentsOfURL:newURL];
NSString *option = [LinphoneManager getMessageAppDataForKey:@"icloudFileOption" inMessage:self.message];
if ([option isEqualToString:@"onResend"])
[_chatRoomDelegate startFileUpload:data withName:fileName];
else if ([option isEqualToString:@"onFileClick"]) {
ChatConversationView *view = VIEW(ChatConversationView);
NSString *filePath = [[LinphoneManager cacheDirectory] stringByAppendingPathComponent:fileName];
[[NSFileManager defaultManager] createFileAtPath:filePath contents:data attributes:nil];
[view openFile:filePath];
}
}];
}
#pragma mark - Action Functions
- (void)onDelete {
@ -235,10 +265,8 @@
if (linphone_chat_message_get_file_transfer_information(_message) != NULL) {
NSString *localImage = [LinphoneManager getMessageAppDataForKey:@"localimage" inMessage:_message];
NSNumber *uploadQuality =[LinphoneManager getMessageAppDataForKey:@"uploadQuality" inMessage:_message];
// TODO: do resend for video and files
/*NSString *localVideo = [LinphoneManager getMessageAppDataForKey:@"localvideo" inMessage:_message];
NSString *localFile = [LinphoneManager getMessageAppDataForKey:@"localfile" inMessage:_message];*/
NSString *localVideo = [LinphoneManager getMessageAppDataForKey:@"localvideo" inMessage:_message];
NSString *localFile = [LinphoneManager getMessageAppDataForKey:@"localfile" inMessage:_message];
[self onDelete];
if(localImage){
@ -254,6 +282,9 @@
if (![assets firstObject])
return;
PHAsset *asset = [assets firstObject];
if (asset.mediaType != PHAssetMediaTypeImage)
return;
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.synchronous = TRUE;
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:options
@ -268,10 +299,36 @@
}
}];
}
} /*else if(fileName) {
NSString *filePath = [LinphoneManager documentFile:fileName];
[_chatRoomDelegate startFileUpload:[NSData dataWithContentsOfFile:filePath] withUrl:[NSURL URLWithString:filePath]];
}*/
} else if (localVideo) {
PHFetchResult<PHAsset *> *assets = [PHAsset fetchAssetsWithLocalIdentifiers:[NSArray arrayWithObject:localVideo] options:nil];
if (![assets firstObject])
return;
PHAsset *asset = [assets firstObject];
if (asset.mediaType != PHAssetMediaTypeVideo)
return;
PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init];
options.version = PHImageRequestOptionsVersionCurrent;
options.deliveryMode = PHVideoRequestOptionsDeliveryModeAutomatic;
[[PHImageManager defaultManager] requestAVAssetForVideo:asset options:options resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
AVURLAsset *urlAsset = (AVURLAsset *)asset;
NSURL *url = urlAsset.URL;
NSData *data = [NSData dataWithContentsOfURL:url];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL),
^(void) {
[_chatRoomDelegate startFileUpload:data assetId:localVideo];
});
}];
} else if (localFile) {
[LinphoneManager setValueInMessageAppData:@"onResend" forKey:@"icloudFileOption" inMessage:_message];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL),
^(void) {
[self getIcloudFiles];
});
}
} else {
[self onDelete];
double delayInSeconds = 0.4;

View file

@ -77,12 +77,12 @@ static NSString* groupName = @"group.belledonne-communications.linphone";
NSDictionary *dict = @{@"nsData" : nsData,
@"url" : filename};
[defaults setObject:dict forKey:@"photoData"];
} else if ([imgPath containsString:@"var/mobile/Library/Mobile Documents/com~apple~CloudDocs"]) {
} else if ([imgPath containsString:@"var/mobile/Library/Mobile Documents/com~apple~CloudDocs"] || [[url scheme] isEqualToString:@"file"]) {
// shared files from icloud drive
NSDictionary *dict = @{@"nsData" : nsData,
@"url" : filename};
[defaults setObject:dict forKey:@"icloudData"];
}else {
} else {
//Others
NSDictionary *dict = @{@"url" : [url absoluteString]};
[defaults setObject:dict forKey:@"url"];

@ -1 +1 @@
Subproject commit f319f5cf17eb0f54596fdd02a0c1bd6f1ebca552
Subproject commit 4c7c3de24bf6fc2d1d95c3c6d025b788275f9554