Custom contact photos [skip ci]

This commit is contained in:
Ilya Laktyushin 2022-12-06 16:03:53 +04:00
parent d4e55712e5
commit 5788f4a1ac
37 changed files with 1666 additions and 340 deletions

View File

@ -8446,3 +8446,26 @@ Sorry for the inconvenience.";
"Channel.AdminLog.AntiSpamEnabled" = "%1$@ enabled anti-spam";
"Channel.AdminLog.AntiSpamDisabled" = "%1$@ disabled anti-spam";
"UserInfo.SuggestPhoto" = "Suggest Photo for %@";
"UserInfo.SetCustomPhoto" = "Set Photo for %@";
"UserInfo.ResetCustomPhoto" = "Reset to Original Photo";
"UserInfo.SuggestPhotoTitle" = "Do you want to suggest a profile picture for %@?";
"UserInfo.SetCustomPhotoTitle" = "Do you want to set a custom profile picture for %@?";
"UserInfo.SuggestPhoto.AlertPhotoText" = "Do you want to suggest %@ to set this photo for his/her profile?";
"UserInfo.SuggestPhoto.AlertVideoText" = "Do you want to suggest %@ to set this video for his/her profile?";
"UserInfo.SuggestPhoto.AlertSuggest" = "Suggest";
"UserInfo.SetCustomPhoto.AlertPhotoText" = "Do you want to set this photo for %@? Only you will see this photo and it will replace any photo %@ sets for themselves.";
"UserInfo.SetCustomPhoto.AlertVideoText" = "Do you want to set this video for %@? Only you will see this video and it will replace any photo %@ sets for themselves.";
"UserInfo.SetCustomPhoto.AlertSet" = "Set";
"UserInfo.SetCustomPhoto.SuccessPhotoText" = "You will now always see this photo for **%@** account.";
"UserInfo.SetCustomPhoto.SuccessVideoText" = "You will now always see this video for **%@** account.";
"UserInfo.CustomPhoto" = "photo set by you";
"UserInfo.CustomVideo" = "video set by you";
"UserInfo.ResetToOriginalAlertText" = "Are you sure you want to reset to %@ original photo?";
"UserInfo.ResetToOriginalAlertReset" = "Reset";

View File

@ -236,7 +236,7 @@ func addTemporaryKeyboardSnapshotView(navigationController: NavigationController
keyboardWindow.addSubview(snapshotView)
}
Queue.mainQueue().after(0.45, {
Queue.mainQueue().after(0.5, {
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})

View File

@ -84,7 +84,7 @@ public func peerAvatarImageData(account: Account, peerReference: PeerReference?,
}
}
public func peerAvatarCompleteImage(account: Account, peer: EnginePeer, size: CGSize, round: Bool = true, font: UIFont = avatarPlaceholderFont(size: 13.0), drawLetters: Bool = true, fullSize: Bool = false, blurred: Bool = false) -> Signal<UIImage?, NoError> {
public func peerAvatarCompleteImage(account: Account, peer: EnginePeer, forceProvidedRepresentation: Bool = false, representation: TelegramMediaImageRepresentation? = nil, size: CGSize, round: Bool = true, font: UIFont = avatarPlaceholderFont(size: 13.0), drawLetters: Bool = true, fullSize: Bool = false, blurred: Bool = false) -> Signal<UIImage?, NoError> {
let iconSignal: Signal<UIImage?, NoError>
let clipStyle: AvatarNodeClipStyle
@ -97,7 +97,15 @@ public func peerAvatarCompleteImage(account: Account, peer: EnginePeer, size: CG
} else {
clipStyle = .none
}
if let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: nil, representation: peer.profileImageRepresentations.first, displayDimensions: size, clipStyle: clipStyle, blurred: blurred, inset: 0.0, emptyColor: nil, synchronousLoad: fullSize) {
let thumbnailRepresentation: TelegramMediaImageRepresentation?
if forceProvidedRepresentation {
thumbnailRepresentation = representation
} else {
thumbnailRepresentation = peer.profileImageRepresentations.first
}
if let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: nil, representation: thumbnailRepresentation, displayDimensions: size, clipStyle: clipStyle, blurred: blurred, inset: 0.0, emptyColor: nil, synchronousLoad: fullSize) {
if fullSize, let fullSizeSignal = peerAvatarImage(account: account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: nil, representation: peer.profileImageRepresentations.last, displayDimensions: size, emptyColor: nil, synchronousLoad: true) {
iconSignal = combineLatest(.single(nil) |> then(signal), .single(nil) |> then(fullSizeSignal))
|> mapToSignal { thumbnailImage, fullSizeImage -> Signal<UIImage?, NoError> in

View File

@ -178,6 +178,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
isForum = true
}
var hasRemoveFromFolder = false
if case let .chatList(currentFilter) = source {
if let currentFilter = currentFilter, case let .filter(id, title, emoticon, data) = currentFilter {
items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_RemoveFromFolder, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/RemoveFromFolder"), color: theme.contextMenu.primaryColor) }, action: { c, _ in
@ -201,119 +202,122 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
})
})
})))
} else {
var hasFolders = false
hasRemoveFromFolder = true
}
}
if !hasRemoveFromFolder && peerGroup != nil {
var hasFolders = false
for case let .filter(_, _, _, data) in filters {
let predicate = chatListFilterPredicate(filter: data)
if predicate.includes(peer: peer._asPeer(), groupId: .root, isRemovedFromTotalUnreadCount: isMuted, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: false) {
continue
}
var data = data
if data.addIncludePeer(peerId: peer.id) {
hasFolders = true
break
}
for case let .filter(_, _, _, data) in filters {
let predicate = chatListFilterPredicate(filter: data)
if predicate.includes(peer: peer._asPeer(), groupId: .root, isRemovedFromTotalUnreadCount: isMuted, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: false) {
continue
}
if hasFolders {
items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_AddToFolder, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Folder"), color: theme.contextMenu.primaryColor) }, action: { c, _ in
var updatedItems: [ContextMenuItem] = []
var data = data
if data.addIncludePeer(peerId: peer.id) {
hasFolders = true
break
}
}
for filter in filters {
if case let .filter(_, title, _, data) = filter {
let predicate = chatListFilterPredicate(filter: data)
if predicate.includes(peer: peer._asPeer(), groupId: .root, isRemovedFromTotalUnreadCount: isMuted, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: false) {
continue
}
if hasFolders {
items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_AddToFolder, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Folder"), color: theme.contextMenu.primaryColor) }, action: { c, _ in
var updatedItems: [ContextMenuItem] = []
var data = data
if !data.addIncludePeer(peerId: peer.id) {
continue
}
let filterType = chatListFilterType(data)
updatedItems.append(.action(ContextMenuActionItem(text: title, icon: { theme in
let imageName: String
switch filterType {
case .generic:
imageName = "Chat/Context Menu/List"
case .unmuted:
imageName = "Chat/Context Menu/Unmute"
case .unread:
imageName = "Chat/Context Menu/MarkAsUnread"
case .channels:
imageName = "Chat/Context Menu/Channels"
case .groups:
imageName = "Chat/Context Menu/Groups"
case .bots:
imageName = "Chat/Context Menu/Bots"
case .contacts:
imageName = "Chat/Context Menu/User"
case .nonContacts:
imageName = "Chat/Context Menu/UnknownUser"
}
return generateTintedImage(image: UIImage(bundleImageName: imageName), color: theme.contextMenu.primaryColor)
}, action: { c, f in
c.dismiss(completion: {
let isPremium = limitsData.0?.isPremium ?? false
let (_, limits, premiumLimits) = limitsData
let limit = limits.maxFolderChatsCount
let premiumLimit = premiumLimits.maxFolderChatsCount
let count = data.includePeers.peers.count - 1
if count >= premiumLimit {
let controller = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(count), action: {})
chatListController?.push(controller)
return
} else if count >= limit && !isPremium {
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(count), action: {
let controller = PremiumIntroScreen(context: context, source: .chatsPerFolder)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
chatListController?.push(controller)
return
}
let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in
var filters = filters
for i in 0 ..< filters.count {
if filters[i].id == filter.id {
if case let .filter(id, title, emoticon, data) = filter {
var updatedData = data
let _ = updatedData.addIncludePeer(peerId: peer.id)
filters[i] = .filter(id: id, title: title, emoticon: emoticon, data: updatedData)
}
break
}
}
return filters
}).start()
chatListController?.present(UndoOverlayController(presentationData: presentationData, content: .chatAddedToFolder(chatTitle: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), folderTitle: title), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
return false
}), in: .current)
})
})))
for filter in filters {
if case let .filter(_, title, _, data) = filter {
let predicate = chatListFilterPredicate(filter: data)
if predicate.includes(peer: peer._asPeer(), groupId: .root, isRemovedFromTotalUnreadCount: isMuted, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: false) {
continue
}
var data = data
if !data.addIncludePeer(peerId: peer.id) {
continue
}
let filterType = chatListFilterType(data)
updatedItems.append(.action(ContextMenuActionItem(text: title, icon: { theme in
let imageName: String
switch filterType {
case .generic:
imageName = "Chat/Context Menu/List"
case .unmuted:
imageName = "Chat/Context Menu/Unmute"
case .unread:
imageName = "Chat/Context Menu/MarkAsUnread"
case .channels:
imageName = "Chat/Context Menu/Channels"
case .groups:
imageName = "Chat/Context Menu/Groups"
case .bots:
imageName = "Chat/Context Menu/Bots"
case .contacts:
imageName = "Chat/Context Menu/User"
case .nonContacts:
imageName = "Chat/Context Menu/UnknownUser"
}
return generateTintedImage(image: UIImage(bundleImageName: imageName), color: theme.contextMenu.primaryColor)
}, action: { c, f in
c.dismiss(completion: {
let isPremium = limitsData.0?.isPremium ?? false
let (_, limits, premiumLimits) = limitsData
let limit = limits.maxFolderChatsCount
let premiumLimit = premiumLimits.maxFolderChatsCount
let count = data.includePeers.peers.count - 1
if count >= premiumLimit {
let controller = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(count), action: {})
chatListController?.push(controller)
return
} else if count >= limit && !isPremium {
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(count), action: {
let controller = PremiumIntroScreen(context: context, source: .chatsPerFolder)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
chatListController?.push(controller)
return
}
let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in
var filters = filters
for i in 0 ..< filters.count {
if filters[i].id == filter.id {
if case let .filter(id, title, emoticon, data) = filter {
var updatedData = data
let _ = updatedData.addIncludePeer(peerId: peer.id)
filters[i] = .filter(id: id, title: title, emoticon: emoticon, data: updatedData)
}
break
}
}
return filters
}).start()
chatListController?.present(UndoOverlayController(presentationData: presentationData, content: .chatAddedToFolder(chatTitle: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), folderTitle: title), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
return false
}), in: .current)
})
})))
}
}
updatedItems.append(.separator)
updatedItems.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Back, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor)
}, action: { c, _ in
c.setItems(chatContextMenuItems(context: context, peerId: peerId, promoInfo: promoInfo, source: source, chatListController: chatListController, joined: joined) |> map { ContextController.Items(content: .list($0)) }, minHeight: nil)
})))
c.setItems(.single(ContextController.Items(content: .list(updatedItems))), minHeight: nil)
updatedItems.append(.separator)
updatedItems.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Back, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor)
}, action: { c, _ in
c.setItems(chatContextMenuItems(context: context, peerId: peerId, promoInfo: promoInfo, source: source, chatListController: chatListController, joined: joined) |> map { ContextController.Items(content: .list($0)) }, minHeight: nil)
})))
}
c.setItems(.single(ContextController.Items(content: .list(updatedItems))), minHeight: nil)
})))
}
}

View File

@ -133,7 +133,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
if setup {
text.referenceDrawingSize = self.size
text.width = floor(self.size.width * 0.9)
text.fontSize = 0.4
text.fontSize = 0.3
}
}
}

View File

@ -279,7 +279,7 @@ struct DrawingState: Equatable {
return DrawingState(
selectedTool: .pen,
tools: [
.pen(DrawingToolState.BrushState(color: DrawingColor(rgb: 0xffffff), size: 0.5, mode: .round)),
.pen(DrawingToolState.BrushState(color: DrawingColor(rgb: 0xffffff), size: 0.3, mode: .round)),
.marker(DrawingToolState.BrushState(color: DrawingColor(rgb: 0xfee21b), size: 0.5, mode: .round)),
.neon(DrawingToolState.BrushState(color: DrawingColor(rgb: 0x34ffab), size: 0.5, mode: .round)),
.pencil(DrawingToolState.BrushState(color: DrawingColor(rgb: 0x2570f0), size: 0.5, mode: .round)),

View File

@ -71,7 +71,7 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw
var tool: Tool = .pen
var toolColor: DrawingColor = DrawingColor(color: .white)
var toolBrushSize: CGFloat = 0.15
var toolBrushSize: CGFloat = 0.35
var toolHasArrow: Bool = false
var stateUpdated: (NavigationState) -> Void = { _ in }

View File

@ -42,8 +42,8 @@
@property (nonatomic, copy) void (^cameraPressed)(TGAttachmentCameraView *cameraView);
@property (nonatomic, copy) void (^sendPressed)(TGMediaAsset *currentItem, bool asFiles, bool silentPosting, int32_t scheduleTime, bool isFromPicker);
@property (nonatomic, copy) void (^avatarCompletionBlock)(UIImage *image);
@property (nonatomic, copy) void (^avatarVideoCompletionBlock)(UIImage *image, id asset, TGVideoEditAdjustments *adjustments);
@property (nonatomic, copy) void (^avatarCompletionBlock)(UIImage *image, void(^commit)(void));
@property (nonatomic, copy) void (^avatarVideoCompletionBlock)(UIImage *image, id asset, TGVideoEditAdjustments *adjustments, void(^commit)(void));
@property (nonatomic, copy) void (^editorOpened)(void);
@property (nonatomic, copy) void (^editorClosed)(void);

View File

@ -15,6 +15,9 @@ typedef void (^TGMediaAvatarPresentImpl)(id<LegacyComponentsContext>, void (^)(U
@property (nonatomic, assign) bool forceDark;
@property (nonatomic, copy) void (^willFinishWithImage)(UIImage *image, void (^)(void));
@property (nonatomic, copy) void (^willFinishWithVideo)(UIImage *image, void (^)(void));
@property (nonatomic, copy) void (^didFinishWithImage)(UIImage *image);
@property (nonatomic, copy) void (^didFinishWithVideo)(UIImage *image, AVAsset *asset, TGVideoEditAdjustments *adjustments);
@property (nonatomic, copy) void (^didFinishWithDelete)(void);
@ -27,7 +30,7 @@ typedef void (^TGMediaAvatarPresentImpl)(id<LegacyComponentsContext>, void (^)(U
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController hasDeleteButton:(bool)hasDeleteButton saveEditedPhotos:(bool)saveEditedPhotos saveCapturedMedia:(bool)saveCapturedMedia;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController hasDeleteButton:(bool)hasDeleteButton personalPhoto:(bool)personalPhoto saveEditedPhotos:(bool)saveEditedPhotos saveCapturedMedia:(bool)saveCapturedMedia;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController hasSearchButton:(bool)hasSearchButton hasDeleteButton:(bool)hasDeleteButton hasViewButton:(bool)hasViewButton personalPhoto:(bool)personalPhoto isVideo:(bool)isVideo saveEditedPhotos:(bool)saveEditedPhotos saveCapturedMedia:(bool)saveCapturedMedia signup:(bool)signup forum:(bool)forum;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController hasSearchButton:(bool)hasSearchButton hasDeleteButton:(bool)hasDeleteButton hasViewButton:(bool)hasViewButton personalPhoto:(bool)personalPhoto isVideo:(bool)isVideo saveEditedPhotos:(bool)saveEditedPhotos saveCapturedMedia:(bool)saveCapturedMedia signup:(bool)signup forum:(bool)forum title:(NSString *)title;
- (TGMenuSheetController *)present;
@end

View File

@ -51,8 +51,8 @@ typedef enum {
@property (nonatomic, copy) void (^willFinishEditing)(id<TGMediaEditAdjustments> adjustments, id temporaryRep, bool hasChanges);
@property (nonatomic, copy) void (^didFinishRenderingFullSizeImage)(UIImage *fullSizeImage);
@property (nonatomic, copy) void (^didFinishEditing)(id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges);
@property (nonatomic, copy) void (^didFinishEditingVideo)(AVAsset *asset, id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges);
@property (nonatomic, copy) void (^didFinishEditing)(id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges, void(^commit)(void));
@property (nonatomic, copy) void (^didFinishEditingVideo)(AVAsset *asset, id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges, void(^commit)(void));
@property (nonatomic, assign) bool skipInitialTransition;
@property (nonatomic, assign) bool dontHideStatusBar;

View File

@ -933,11 +933,10 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500;
};
__weak TGPhotoEditorController *weakController = controller;
controller.didFinishEditing = ^(id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, __unused UIImage *thumbnailImage, __unused bool hasChanges)
controller.didFinishEditing = ^(id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, __unused UIImage *thumbnailImage, __unused bool hasChanges, void(^commit)(void))
{
if (!hasChanges)
return;
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
@ -970,13 +969,13 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500;
}
}
if (strongSelf.avatarVideoCompletionBlock != nil)
strongSelf.avatarVideoCompletionBlock(previewImage, [NSURL fileURLWithPath:filePath], videoAdjustments);
strongSelf.avatarVideoCompletionBlock(previewImage, [NSURL fileURLWithPath:filePath], videoAdjustments, commit);
} else {
if (strongSelf.avatarCompletionBlock != nil)
strongSelf.avatarCompletionBlock(resultImage);
strongSelf.avatarCompletionBlock(resultImage, commit);
}
};
controller.didFinishEditingVideo = ^(AVAsset *asset, id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges) {
controller.didFinishEditingVideo = ^(AVAsset *asset, id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges, void(^commit)(void)) {
if (!hasChanges)
return;
@ -989,7 +988,7 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500;
return;
if (strongSelf.avatarVideoCompletionBlock != nil)
strongSelf.avatarVideoCompletionBlock(resultImage, asset, adjustments);
strongSelf.avatarVideoCompletionBlock(resultImage, asset, adjustments, commit);
};
controller.requestThumbnailImage = ^(id<TGMediaEditableItem> editableItem)
{

View File

@ -2014,7 +2014,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
return strongSelf->_previewView;
};
controller.didFinishEditing = ^(PGPhotoEditorValues *editorValues, UIImage *resultImage, __unused UIImage *thumbnailImage, bool hasChanges)
controller.didFinishEditing = ^(PGPhotoEditorValues *editorValues, UIImage *resultImage, __unused UIImage *thumbnailImage, bool hasChanges, void(^commit)(void))
{
if (!hasChanges)
return;
@ -2064,10 +2064,12 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
[strongController updateStatusBarAppearanceForDismiss];
[strongSelf _dismissTransitionForResultController:(TGOverlayController *)strongController];
}
commit();
});
};
controller.didFinishEditingVideo = ^(AVAsset *asset, id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges) {
controller.didFinishEditingVideo = ^(AVAsset *asset, id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges, void(^commit)(void)) {
if (!hasChanges)
return;
@ -2086,6 +2088,8 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
[strongController updateStatusBarAppearanceForDismiss];
[strongSelf _dismissTransitionForResultController:(TGOverlayController *)strongController];
}
commit();
});
};

View File

@ -451,7 +451,7 @@
[[strongSelf->_assetsLibrary saveAssetWithImage:resultImage] startWithNext:nil];
};
controller.didFinishEditing = ^(id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, __unused UIImage *thumbnailImage, bool hasChanges)
controller.didFinishEditing = ^(id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, __unused UIImage *thumbnailImage, bool hasChanges, void(^commit)(void))
{
if (!hasChanges)
return;
@ -488,7 +488,7 @@
[(TGMediaAssetsController *)strongSelf.navigationController completeWithAvatarImage:resultImage];
}
};
controller.didFinishEditingVideo = ^(AVAsset *asset, id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges) {
controller.didFinishEditingVideo = ^(AVAsset *asset, id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges, void(^commit)(void)) {
if (!hasChanges)
return;

View File

@ -27,6 +27,7 @@
bool _signup;
bool _isVideo;
bool _forum;
NSString *_title;
}
@end
@ -39,10 +40,10 @@
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController hasDeleteButton:(bool)hasDeleteButton personalPhoto:(bool)personalPhoto saveEditedPhotos:(bool)saveEditedPhotos saveCapturedMedia:(bool)saveCapturedMedia
{
return [self initWithContext:context parentController:parentController hasSearchButton:false hasDeleteButton:hasDeleteButton hasViewButton:false personalPhoto:personalPhoto isVideo:false saveEditedPhotos:saveEditedPhotos saveCapturedMedia:saveCapturedMedia signup:false forum: false];
return [self initWithContext:context parentController:parentController hasSearchButton:false hasDeleteButton:hasDeleteButton hasViewButton:false personalPhoto:personalPhoto isVideo:false saveEditedPhotos:saveEditedPhotos saveCapturedMedia:saveCapturedMedia signup:false forum:false title:nil];
}
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController hasSearchButton:(bool)hasSearchButton hasDeleteButton:(bool)hasDeleteButton hasViewButton:(bool)hasViewButton personalPhoto:(bool)personalPhoto isVideo:(bool)isVideo saveEditedPhotos:(bool)saveEditedPhotos saveCapturedMedia:(bool)saveCapturedMedia signup:(bool)signup forum:(bool)forum
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController hasSearchButton:(bool)hasSearchButton hasDeleteButton:(bool)hasDeleteButton hasViewButton:(bool)hasViewButton personalPhoto:(bool)personalPhoto isVideo:(bool)isVideo saveEditedPhotos:(bool)saveEditedPhotos saveCapturedMedia:(bool)saveCapturedMedia signup:(bool)signup forum:(bool)forum title:(NSString *)title
{
self = [super init];
if (self != nil)
@ -58,6 +59,7 @@
_isVideo = isVideo;
_signup = signup;
_forum = forum;
_title = title;
}
return self;
}
@ -92,6 +94,10 @@
NSMutableArray *itemViews = [[NSMutableArray alloc] init];
if (_title.length > 0) {
[itemViews addObject:[[TGMenuSheetTitleItemView alloc] initWithTitle:nil subtitle:_title solidSubtitle:false]];
}
TGAttachmentCarouselItemView *carouselItem = [[TGAttachmentCarouselItemView alloc] initWithContext:_context camera:true selfPortrait:_personalPhoto forProfilePhoto:true assetType:_signup ? TGMediaAssetPhotoType : TGMediaAssetAnyType saveEditedPhotos:_saveEditedPhotos allowGrouping:false];
carouselItem.forum = _forum;
carouselItem.stickersContext = _stickersContext;
@ -112,7 +118,7 @@
[strongSelf _displayCameraWithView:cameraView menuController:strongController];
};
carouselItem.avatarCompletionBlock = ^(UIImage *resultImage)
carouselItem.avatarCompletionBlock = ^(UIImage *resultImage, void(^commit)(void))
{
__strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;
if (strongSelf == nil)
@ -122,12 +128,25 @@
if (strongController == nil)
return;
if (strongSelf.didFinishWithImage != nil)
strongSelf.didFinishWithImage(resultImage);
[strongController dismissAnimated:false];
if (strongSelf.willFinishWithImage != nil) {
strongSelf.willFinishWithImage(resultImage, ^{
if (strongSelf.didFinishWithImage != nil)
strongSelf.didFinishWithImage(resultImage);
commit();
[strongController dismissAnimated:false];
});
} else {
if (strongSelf.didFinishWithImage != nil)
strongSelf.didFinishWithImage(resultImage);
commit();
[strongController dismissAnimated:false];
}
};
carouselItem.avatarVideoCompletionBlock = ^(UIImage *image, AVAsset *asset, TGVideoEditAdjustments *adjustments) {
carouselItem.avatarVideoCompletionBlock = ^(UIImage *image, AVAsset *asset, TGVideoEditAdjustments *adjustments, void(^commit)(void)) {
__strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;
if (strongSelf == nil)
return;
@ -139,6 +158,8 @@
if (strongSelf.didFinishWithVideo != nil)
strongSelf.didFinishWithVideo(image, asset, adjustments);
commit();
[strongController dismissAnimated:false];
};
[itemViews addObject:carouselItem];
@ -314,10 +335,19 @@
if (strongSelf == nil)
return;
if (strongSelf.didFinishWithImage != nil)
strongSelf.didFinishWithImage(resultImage);
[menuController dismissAnimated:false];
if (strongSelf.willFinishWithImage != nil) {
strongSelf.willFinishWithImage(resultImage, ^{
if (strongSelf.didFinishWithImage != nil)
strongSelf.didFinishWithImage(resultImage);
[menuController dismissAnimated:false];
});
} else {
if (strongSelf.didFinishWithImage != nil)
strongSelf.didFinishWithImage(resultImage);
[menuController dismissAnimated:false];
}
};
controller.finishedWithVideo = ^(__unused TGOverlayController *controller, NSURL *url, UIImage *previewImage, __unused NSTimeInterval duration, __unused CGSize dimensions, TGVideoEditAdjustments *adjustments, __unused NSAttributedString *caption, __unused NSArray *stickers, __unused NSNumber *timer){
@ -431,13 +461,24 @@
__strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (strongSelf.didFinishWithImage != nil)
strongSelf.didFinishWithImage(resultImage);
__strong TGMediaAssetsController *strongController = weakController;
if (strongController != nil && strongController.dismissalBlock != nil)
strongController.dismissalBlock();
if (strongSelf.willFinishWithImage != nil) {
strongSelf.willFinishWithImage(resultImage, ^{
if (strongSelf.didFinishWithImage != nil)
strongSelf.didFinishWithImage(resultImage);
__strong TGMediaAssetsController *strongController = weakController;
if (strongController != nil && strongController.dismissalBlock != nil)
strongController.dismissalBlock();
});
} else {
if (strongSelf.didFinishWithImage != nil)
strongSelf.didFinishWithImage(resultImage);
__strong TGMediaAssetsController *strongController = weakController;
if (strongController != nil && strongController.dismissalBlock != nil)
strongController.dismissalBlock();
}
};
controller.avatarVideoCompletionBlock = ^(UIImage *image, AVAsset *asset, TGVideoEditAdjustments *adjustments) {
__strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;

View File

@ -417,7 +417,7 @@
};
void (^didFinishEditingItem)(id<TGMediaEditableItem>item, id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage) = self.didFinishEditingItem;
controller.didFinishEditing = ^(id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges)
controller.didFinishEditing = ^(id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges, void(^commit)(void))
{
__strong TGMediaPickerGalleryModel *strongSelf = weakSelf;
if (strongSelf == nil) {

View File

@ -73,7 +73,7 @@
- (CGFloat)preferredHeightForWidth:(CGFloat)width screenHeight:(CGFloat)__unused screenHeight
{
CGFloat height = 17.0f;
CGFloat height = 16.0f;
if (_titleLabel.text.length > 0)
{
@ -91,19 +91,19 @@
height += _subtitleLabel.frame.size.height;
}
height += 15.0f;
height += 8.0f;
return height;
}
- (bool)requiresDivider
{
return true;
return false;
}
- (void)layoutSubviews
{
CGFloat topOffset = 17.0f;
CGFloat topOffset = 16.0f;
if (_titleLabel.text.length > 0)
{

View File

@ -952,7 +952,7 @@
self.willFinishEditing(nil, [_currentTabController currentResultRepresentation], true);
if (self.didFinishEditing != nil)
self.didFinishEditing(nil, nil, nil, true);
self.didFinishEditing(nil, nil, nil, true, ^{});
if (completion != nil)
completion(nil);
@ -1059,7 +1059,7 @@
else
{
void (^didFinishRenderingFullSizeImage)(UIImage *) = self.didFinishRenderingFullSizeImage;
void (^didFinishEditing)(id<TGMediaEditAdjustments>, UIImage *, UIImage *, bool ) = self.didFinishEditing;
void (^didFinishEditing)(id<TGMediaEditAdjustments>, UIImage *, UIImage *, bool , void(^)(void)) = self.didFinishEditing;
[[[[renderedImageSignal map:^id(UIImage *image)
{
@ -1119,7 +1119,7 @@
}
if (!saveOnly && didFinishEditing != nil)
didFinishEditing(editorValues, image, thumbnailImage, true);
didFinishEditing(editorValues, image, thumbnailImage, true, ^{});
} error:^(__unused id error)
{
TGLegacyLog(@"renderedImageSignal error");
@ -1899,7 +1899,7 @@
strongSelf.willFinishEditing(nil, nil, false);
if (strongSelf.didFinishEditing != nil)
strongSelf.didFinishEditing(nil, nil, nil, false);
strongSelf.didFinishEditing(nil, nil, nil, false, ^{});
};
TGPaintingData *paintingData = nil;
@ -2088,7 +2088,7 @@
TGDispatchOnMainThread(^{
if (self.didFinishEditingVideo != nil)
self.didFinishEditingVideo(asset, [adjustments editAdjustmentsWithPreset:preset videoStartValue:videoStartValue trimStartValue:trimStartValue trimEndValue:trimEndValue], fullImage, nil, true);
self.didFinishEditingVideo(asset, [adjustments editAdjustmentsWithPreset:preset videoStartValue:videoStartValue trimStartValue:trimStartValue trimEndValue:trimEndValue], fullImage, nil, true, ^{});
[self dismissAnimated:true];
});
@ -2196,16 +2196,16 @@
if (self.willFinishEditing != nil)
self.willFinishEditing(hasChanges ? adjustments : nil, nil, hasChanges);
if (self.didFinishEditing != nil)
self.didFinishEditing(hasChanges ? adjustments : nil, nil, nil, hasChanges);
if ([self presentedForAvatarCreation]) {
[self dismissAnimated:true];
} else {
[self transitionOutSaving:saving completion:^
{
[self dismiss];
}];
if (self.didFinishEditing != nil) {
self.didFinishEditing(hasChanges ? adjustments : nil, nil, nil, hasChanges, ^{
if ([self presentedForAvatarCreation]) {
[self dismissAnimated:true];
} else {
[self transitionOutSaving:saving completion:^{
[self dismiss];
}];
}
});
}
}
}

View File

@ -38,12 +38,12 @@
// controller.stickersContext = _stickersContext;
controller.skipInitialTransition = true;
controller.dontHideStatusBar = true;
controller.didFinishEditing = ^(__unused id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, __unused UIImage *thumbnailImage, __unused bool hasChanges)
controller.didFinishEditing = ^(__unused id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, __unused UIImage *thumbnailImage, __unused bool hasChanges, void(^commit)(void))
{
if (didFinishWithImage != nil)
didFinishWithImage(resultImage);
};
controller.didFinishEditingVideo = ^(AVAsset *asset, id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges) {
controller.didFinishEditingVideo = ^(AVAsset *asset, id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges, void(^commit)(void)) {
if (didFinishWithVideo != nil) {
if ([asset isKindOfClass:[AVURLAsset class]]) {
didFinishWithVideo(resultImage, [(AVURLAsset *)asset URL], adjustments);

View File

@ -19,7 +19,7 @@ public func presentLegacyAvatarPicker(holder: Atomic<NSObject?>, signup: Bool, t
present(legacyController, nil)
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: false, hasDeleteButton: false, hasViewButton: openCurrent != nil, personalPhoto: true, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: signup, forum: false)!
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: false, hasDeleteButton: false, hasViewButton: openCurrent != nil, personalPhoto: true, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: signup, forum: false, title: nil)!
let _ = holder.swap(mixin)
mixin.didFinishWithImage = { image in
guard let image = image else {

View File

@ -33,10 +33,18 @@ public func peerInfoProfilePhotos(context: AccountContext, peerId: EnginePeer.Id
|> mapToSignal { entries -> Signal<(Bool, [AvatarGalleryEntry])?, NoError> in
if let entries = entries {
if let firstEntry = entries.first {
return context.account.postbox.loadedPeerWithId(peerId)
|> mapToSignal { peer -> Signal<(Bool, [AvatarGalleryEntry])?, NoError>in
return fetchedAvatarGalleryEntries(engine: context.engine, account: context.account, peer: peer, firstEntry: firstEntry)
|> map(Optional.init)
return context.account.postbox.peerView(id: peerId)
|> mapToSignal { peerView -> Signal<(Bool, [AvatarGalleryEntry])?, NoError>in
if let peer = peerViewMainPeer(peerView) {
var secondEntry: TelegramMediaImage?
if firstEntry.representations.first?.representation.isPersonal == true, let cachedData = peerView.cachedData as? CachedUserData, case let .known(photo) = cachedData.photo {
secondEntry = photo
}
return fetchedAvatarGalleryEntries(engine: context.engine, account: context.account, peer: peer, firstEntry: firstEntry, secondEntry: secondEntry)
|> map(Optional.init)
} else {
return .single(nil)
}
}
} else {
return .single((true, []))
@ -268,7 +276,7 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account
}
}
public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account, peer: Peer, firstEntry: AvatarGalleryEntry) -> Signal<(Bool, [AvatarGalleryEntry]), NoError> {
public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account, peer: Peer, firstEntry: AvatarGalleryEntry, secondEntry: TelegramMediaImage?) -> Signal<(Bool, [AvatarGalleryEntry]), NoError> {
let initialEntries = [firstEntry]
return Signal<(Bool, [AvatarGalleryEntry]), NoError>.single((false, initialEntries))
|> then(
@ -313,6 +321,10 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account
index += 1
}
} else {
var photos = photos
if let secondEntry {
photos.insert(TelegramPeerPhoto(image: secondEntry, reference: secondEntry.reference, date: 0, index: 1, totalCount: 0, messageId: nil), at: 1)
}
for photo in photos {
let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photos.count))
if result.isEmpty, let first = initialEntries.first {

View File

@ -522,6 +522,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
var highlightedSide: Bool?
public let stripContainerNode: ASDisplayNode
public let highlightContainerNode: ASDisplayNode
private let setByYouNode: ImmediateTextNode
public private(set) var galleryEntries: [AvatarGalleryEntry] = []
private var items: [PeerInfoAvatarListItem] = []
private var itemNodes: [MediaResourceId: PeerInfoAvatarListItemNode] = [:]
@ -689,6 +690,10 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
self.highlightContainerNode.addSubnode(self.leftHighlightNode)
self.highlightContainerNode.addSubnode(self.rightHighlightNode)
self.setByYouNode = ImmediateTextNode()
self.setByYouNode.alpha = 0.0
self.setByYouNode.isUserInteractionEnabled = false
self.controlsContainerNode = ASDisplayNode()
self.controlsContainerNode.isUserInteractionEnabled = false
@ -758,6 +763,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
self.controlsContainerNode.addSubnode(self.stripContainerNode)
self.controlsClippingNode.addSubnode(self.controlsContainerNode)
self.controlsClippingOffsetNode.addSubnode(self.controlsClippingNode)
self.stripContainerNode.addSubnode(self.setByYouNode)
self.view.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in
guard let strongSelf = self else {
@ -1254,6 +1260,20 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
}
}
}
if !self.items.isEmpty, self.currentIndex >= 0 && self.currentIndex < self.items.count {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let currentItem = self.items[self.currentIndex]
if let representation = currentItem.representations.first?.representation, representation.isPersonal {
transition.updateAlpha(node: self.setByYouNode, alpha: 0.7)
self.setByYouNode.attributedText = NSAttributedString(string: representation.hasVideo ? presentationData.strings.UserInfo_CustomVideo : presentationData.strings.UserInfo_CustomPhoto, font: Font.regular(12.0), textColor: UIColor.white)
let setByYouSize = self.setByYouNode.updateLayout(size)
self.setByYouNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - setByYouSize.width) / 2.0), y: 17.0), size: setByYouSize)
} else {
transition.updateAlpha(node: self.setByYouNode, alpha: 0.0)
}
}
for itemNode in addedItemNodesForAdditiveTransition {
transition.animatePositionAdditive(node: itemNode, offset: CGPoint(x: additiveTransitionOffset, y: 0.0))
}

View File

@ -6165,7 +6165,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
// return controller
// }
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos && !fromGallery, hasViewButton: false, personalPhoto: peerId.namespace == Namespaces.Peer.CloudUser, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: false)!
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos && !fromGallery, hasViewButton: false, personalPhoto: peerId.namespace == Namespaces.Peer.CloudUser, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: false, title: nil)!
mixin.forceDark = true
mixin.stickersContext = paintStickersContext
let _ = strongSelf.currentAvatarMixin.swap(mixin)

View File

@ -160,7 +160,7 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
var representations: [TelegramMediaImageRepresentation] = []
var videoRepresentations: [TelegramMediaImage.VideoRepresentation] = []
switch photo {
case let .photo(photo: apiPhoto, users: _):
case let .photo(apiPhoto, _):
switch apiPhoto {
case .photoEmpty:
break
@ -303,13 +303,33 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
}
} else {
if let _ = peer as? TelegramUser {
let signal: Signal<Api.photos.Photo, UploadPeerPhotoError> = network.request(Api.functions.photos.updateProfilePhoto(id: Api.InputPhoto.inputPhotoEmpty))
let request: Signal<Api.photos.Photo, MTRpcError>
if peer.id == accountPeerId {
request = network.request(Api.functions.photos.updateProfilePhoto(id: Api.InputPhoto.inputPhotoEmpty))
} else if let inputUser = apiInputUser(peer) {
request = network.request(Api.functions.photos.uploadContactProfilePhoto(flags: 0, userId: inputUser, file: nil, video: nil, videoStartTs: nil))
} else {
request = .complete()
}
return request
|> mapError { _ -> UploadPeerPhotoError in
return .generic
}
return signal
|> mapToSignal { _ -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
|> mapToSignal { photo -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
if peer.id != accountPeerId {
var updatedUsers: [TelegramUser] = []
switch photo {
case let .photo(_, apiUsers):
updatedUsers = apiUsers.map { TelegramUser(user: $0) }
}
return postbox.transaction { transaction -> UpdatePeerPhotoStatus in
updatePeers(transaction: transaction, peers: updatedUsers, update: { (_, updatedPeer) -> Peer? in
return updatedPeer
})
return .complete([])
} |> mapError { _ -> UploadPeerPhotoError in }
}
return .single(.complete([]))
}
} else {

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "arrow (2).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,161 @@
%PDF-1.7
1 0 obj
<< /Type /XObject
/Length 2 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 10.000000 16.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 1.500000 -1.195312 cm
0.000000 0.000000 0.000000 scn
0.707107 16.902420 m
0.316583 17.292944 -0.316583 17.292944 -0.707107 16.902420 c
-1.097631 16.511894 -1.097631 15.878730 -0.707107 15.488206 c
0.707107 16.902420 l
h
7.000000 9.195312 m
7.707107 8.488206 l
8.097631 8.878730 8.097631 9.511895 7.707107 9.902419 c
7.000000 9.195312 l
h
-0.707107 2.902419 m
-1.097631 2.511895 -1.097631 1.878730 -0.707107 1.488206 c
-0.316583 1.097681 0.316583 1.097681 0.707107 1.488206 c
-0.707107 2.902419 l
h
-0.707107 15.488206 m
6.292893 8.488206 l
7.707107 9.902419 l
0.707107 16.902420 l
-0.707107 15.488206 l
h
6.292893 9.902419 m
-0.707107 2.902419 l
0.707107 1.488206 l
7.707107 8.488206 l
6.292893 9.902419 l
h
f
n
Q
endstream
endobj
2 0 obj
781
endobj
3 0 obj
<< /Type /XObject
/Length 4 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 10.000000 16.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.000000 0.000000 0.000000 scn
0.000000 16.000000 m
10.000000 16.000000 l
10.000000 0.000000 l
0.000000 0.000000 l
0.000000 16.000000 l
h
f
n
Q
endstream
endobj
4 0 obj
232
endobj
5 0 obj
<< /XObject << /X1 1 0 R >>
/ExtGState << /E1 << /SMask << /Type /Mask
/G 3 0 R
/S /Alpha
>>
/Type /ExtGState
>> >>
>>
endobj
6 0 obj
<< /Length 7 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
/E1 gs
/X1 Do
Q
endstream
endobj
7 0 obj
46
endobj
8 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 10.000000 16.000000 ]
/Resources 5 0 R
/Contents 6 0 R
/Parent 9 0 R
>>
endobj
9 0 obj
<< /Kids [ 8 0 R ]
/Count 1
/Type /Pages
>>
endobj
10 0 obj
<< /Pages 9 0 R
/Type /Catalog
>>
endobj
xref
0 11
0000000000 65535 f
0000000010 00000 n
0000001039 00000 n
0000001061 00000 n
0000001541 00000 n
0000001563 00000 n
0000001861 00000 n
0000001963 00000 n
0000001984 00000 n
0000002157 00000 n
0000002231 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 10 0 R
/Size 11
>>
startxref
2291
%%EOF

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "suggestphoto_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,500 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 3.750031 4.915199 cm
0.000000 0.000000 0.000000 scn
7.250000 0.669800 m
7.617270 0.669800 7.915000 0.967531 7.915000 1.334801 c
7.915000 1.702070 7.617270 1.999802 7.250000 1.999802 c
7.250000 0.669800 l
h
15.250000 1.999802 m
14.882730 1.999802 14.585000 1.702070 14.585000 1.334801 c
14.585000 0.967531 14.882730 0.669800 15.250000 0.669800 c
15.250000 1.999802 l
h
1.638029 1.661781 m
1.939932 2.254301 l
1.638029 1.661781 l
h
5.406044 18.011244 m
4.995583 18.534452 l
5.406044 18.011244 l
h
4.411342 17.599224 m
4.491065 16.939022 l
4.411342 17.599224 l
h
7.861537 20.029541 m
7.706295 20.676167 l
7.861537 20.029541 l
h
6.562742 19.147543 m
7.032968 18.677317 l
6.562742 19.147543 l
h
15.216570 19.790081 m
14.869108 19.223076 l
15.216570 19.790081 l
h
14.638464 20.029541 m
14.793705 20.676167 l
14.638464 20.029541 l
h
18.088657 17.599224 m
18.008936 16.939022 l
18.088657 17.599224 l
h
16.734951 18.349850 m
16.264725 17.879623 l
16.734951 18.349850 l
h
17.093956 18.011244 m
16.683493 17.488037 l
17.093956 18.011244 l
h
22.413767 15.298920 m
23.059650 15.457216 l
22.413767 15.298920 l
h
20.214119 17.498568 m
20.055822 16.852682 l
20.214119 17.498568 l
h
22.173019 2.972830 m
22.765539 2.670925 l
22.173019 2.972830 l
h
20.861971 1.661781 m
20.560068 2.254301 l
20.861971 1.661781 l
h
0.086234 15.298920 m
-0.559651 15.457216 l
0.086234 15.298920 l
h
23.165001 6.134801 m
23.165001 13.666742 l
21.834999 13.666742 l
21.834999 6.134801 l
23.165001 6.134801 l
h
17.205177 18.820076 m
16.407484 19.617769 l
15.467031 18.677317 l
16.264725 17.879623 l
17.205177 18.820076 l
h
13.674517 20.749802 m
8.825483 20.749802 l
8.825483 19.419800 l
13.674517 19.419800 l
13.674517 20.749802 l
h
6.092515 19.617769 m
5.294821 18.820074 l
6.235273 17.879622 l
7.032968 18.677317 l
6.092515 19.617769 l
h
-0.665000 13.666742 m
-0.665000 6.459801 l
0.665000 6.459801 l
0.665000 13.666742 l
-0.665000 13.666742 l
h
-0.665000 6.459801 m
-0.665000 6.134801 l
0.665000 6.134801 l
0.665000 6.459801 l
-0.665000 6.459801 l
h
4.800000 0.669800 m
7.250000 0.669800 l
7.250000 1.999802 l
4.800000 1.999802 l
4.800000 0.669800 l
h
15.250000 0.669800 m
17.699999 0.669800 l
17.699999 1.999802 l
15.250000 1.999802 l
15.250000 0.669800 l
h
-0.665000 6.134801 m
-0.665000 5.305696 -0.665517 4.643871 -0.621919 4.110254 c
-0.577686 3.568874 -0.484757 3.101164 -0.265539 2.670925 c
0.919500 3.274733 l
0.811737 3.486229 0.741177 3.759426 0.703664 4.218559 c
0.665517 4.685454 0.665000 5.283749 0.665000 6.134801 c
-0.665000 6.134801 l
h
4.800000 1.999802 m
3.948948 1.999802 3.350653 2.000319 2.883758 2.038465 c
2.424626 2.075977 2.151429 2.146538 1.939932 2.254301 c
1.336125 1.069262 l
1.766364 0.850044 2.234073 0.757114 2.775454 0.712881 c
3.309071 0.669283 3.970894 0.669800 4.800000 0.669800 c
4.800000 1.999802 l
h
-0.265539 2.670925 m
0.085837 1.981312 0.646511 1.420637 1.336125 1.069262 c
1.939932 2.254301 l
1.500574 2.478165 1.143364 2.835375 0.919500 3.274733 c
-0.265539 2.670925 l
h
5.294821 18.820074 m
5.107314 18.632566 5.051449 18.578279 4.995583 18.534452 c
5.816506 17.488037 l
5.949968 17.592739 6.070329 17.714678 6.235273 17.879622 c
5.294821 18.820074 l
h
3.918058 16.919800 m
4.151325 16.919800 4.322658 16.918686 4.491065 16.939022 c
4.331619 18.259430 l
4.261125 18.250916 4.183235 18.249802 3.918058 18.249802 c
3.918058 16.919800 l
h
4.995583 18.534452 m
4.803562 18.383810 4.573919 18.288689 4.331619 18.259430 c
4.491065 16.939022 l
4.974757 16.997429 5.433183 17.187315 5.816506 17.488037 c
4.995583 18.534452 l
h
8.825483 20.749802 m
8.367955 20.749802 8.030805 20.754074 7.706295 20.676167 c
8.016778 19.382915 l
8.152618 19.415527 8.304649 19.419800 8.825483 19.419800 c
8.825483 20.749802 l
h
7.032968 18.677317 m
7.401253 19.045601 7.511777 19.150082 7.630892 19.223076 c
6.935968 20.357086 l
6.651416 20.182713 6.416037 19.941290 6.092515 19.617769 c
7.032968 18.677317 l
h
7.706295 20.676167 m
7.434369 20.610882 7.174412 20.503204 6.935968 20.357086 c
7.630892 19.223076 l
7.750337 19.296272 7.880559 19.350212 8.016778 19.382915 c
7.706295 20.676167 l
h
16.407484 19.617769 m
16.083963 19.941290 15.848584 20.182713 15.564032 20.357086 c
14.869108 19.223076 l
14.988222 19.150082 15.098746 19.045603 15.467031 18.677317 c
16.407484 19.617769 l
h
13.674517 19.419800 m
14.195351 19.419800 14.347382 19.415527 14.483222 19.382915 c
14.793705 20.676167 l
14.469195 20.754074 14.132045 20.749802 13.674517 20.749802 c
13.674517 19.419800 l
h
15.564032 20.357086 m
15.325588 20.503204 15.065631 20.610882 14.793705 20.676167 c
14.483222 19.382915 l
14.619441 19.350212 14.749663 19.296272 14.869108 19.223076 c
15.564032 20.357086 l
h
18.581942 18.249802 m
18.316765 18.249802 18.238874 18.250916 18.168381 18.259430 c
18.008936 16.939022 l
18.177341 16.918686 18.348675 16.919800 18.581942 16.919800 c
18.581942 18.249802 l
h
16.264725 17.879623 m
16.429670 17.714678 16.550032 17.592739 16.683493 17.488037 c
17.504417 18.534452 l
17.448551 18.578279 17.392687 18.632566 17.205177 18.820076 c
16.264725 17.879623 l
h
18.168381 18.259430 m
17.926081 18.288689 17.696438 18.383810 17.504417 18.534452 c
16.683493 17.488037 l
17.066816 17.187315 17.525242 16.997429 18.008936 16.939022 c
18.168381 18.259430 l
h
23.165001 13.666742 m
23.165001 14.481091 23.169819 15.007710 23.059650 15.457216 c
21.767881 15.140623 l
21.830181 14.886425 21.834999 14.559493 21.834999 13.666742 c
23.165001 13.666742 l
h
18.581942 16.919800 m
19.474693 16.919800 19.801624 16.914982 20.055822 16.852682 c
20.372416 18.144451 l
19.922909 18.254620 19.396290 18.249802 18.581942 18.249802 c
18.581942 16.919800 l
h
23.059650 15.457216 m
22.734556 16.783680 21.698879 17.819357 20.372416 18.144451 c
20.055822 16.852682 l
20.900923 16.645561 21.560760 15.985723 21.767881 15.140623 c
23.059650 15.457216 l
h
21.834999 6.134801 m
21.834999 5.283749 21.834482 4.685454 21.796335 4.218559 c
21.758823 3.759426 21.688263 3.486229 21.580500 3.274733 c
22.765539 2.670925 l
22.984756 3.101164 23.077686 3.568874 23.121920 4.110254 c
23.165518 4.643871 23.165001 5.305695 23.165001 6.134801 c
21.834999 6.134801 l
h
17.699999 0.669800 m
18.529106 0.669800 19.190929 0.669283 19.724546 0.712881 c
20.265926 0.757114 20.733637 0.850044 21.163876 1.069262 c
20.560068 2.254301 l
20.348572 2.146538 20.075375 2.075977 19.616241 2.038465 c
19.149347 2.000319 18.551052 1.999802 17.699999 1.999802 c
17.699999 0.669800 l
h
21.580500 3.274733 m
21.356636 2.835375 20.999426 2.478165 20.560068 2.254301 c
21.163876 1.069262 l
21.853489 1.420637 22.414164 1.981312 22.765539 2.670925 c
21.580500 3.274733 l
h
3.918058 18.249802 m
3.103710 18.249802 2.577092 18.254620 2.127584 18.144451 c
2.444177 16.852682 l
2.698376 16.914982 3.025307 16.919800 3.918058 16.919800 c
3.918058 18.249802 l
h
0.665000 13.666742 m
0.665000 14.559494 0.669818 14.886425 0.732119 15.140623 c
-0.559651 15.457216 l
-0.669818 15.007710 -0.665000 14.481091 -0.665000 13.666742 c
0.665000 13.666742 l
h
2.127584 18.144451 m
0.801121 17.819357 -0.234555 16.783680 -0.559651 15.457216 c
0.732119 15.140623 l
0.939240 15.985723 1.599077 16.645561 2.444177 16.852682 c
2.127584 18.144451 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 10.312500 7.670013 cm
0.000000 0.000000 0.000000 scn
6.687500 1.329994 m
6.687500 0.664994 l
7.054770 0.664994 7.352500 0.962725 7.352500 1.329994 c
6.687500 1.329994 l
h
6.687500 2.776849 m
6.403426 3.378120 l
6.170848 3.268237 6.022500 3.034078 6.022500 2.776849 c
6.687500 2.776849 l
h
2.687500 2.776849 m
3.352500 2.776849 l
3.352500 3.034078 3.204152 3.268237 2.971574 3.378120 c
2.687500 2.776849 l
h
2.687500 1.329994 m
2.022500 1.329994 l
2.022500 0.962725 2.320231 0.664994 2.687500 0.664994 c
2.687500 1.329994 l
h
0.665000 7.017487 m
0.665000 9.239052 2.465935 11.039987 4.687500 11.039987 c
4.687500 12.369987 l
1.731396 12.369987 -0.665000 9.973591 -0.665000 7.017487 c
0.665000 7.017487 l
h
4.687500 11.039987 m
6.909065 11.039987 8.710000 9.239052 8.710000 7.017487 c
10.040000 7.017487 l
10.040000 9.973591 7.643604 12.369987 4.687500 12.369987 c
4.687500 11.039987 l
h
8.710000 7.017487 m
8.710000 5.410845 7.768091 4.022863 6.403426 3.378120 c
6.971574 2.175577 l
8.783873 3.031808 10.040000 4.877287 10.040000 7.017487 c
8.710000 7.017487 l
h
6.022500 2.776849 m
6.022500 1.329994 l
7.352500 1.329994 l
7.352500 2.776849 l
6.022500 2.776849 l
h
2.971574 3.378120 m
1.606909 4.022863 0.665000 5.410845 0.665000 7.017487 c
-0.665000 7.017487 l
-0.665000 4.877287 0.591128 3.031808 2.403426 2.175577 c
2.971574 3.378120 l
h
6.687500 1.994994 m
2.687500 1.994994 l
2.687500 0.664994 l
6.687500 0.664994 l
6.687500 1.994994 l
h
3.352500 1.329994 m
3.352500 2.776849 l
2.022500 2.776849 l
2.022500 1.329994 l
3.352500 1.329994 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 13.500000 11.577972 cm
0.000000 0.000000 0.000000 scn
0.368876 3.975341 m
0.063289 4.179066 -0.349589 4.096490 -0.553313 3.790903 c
-0.757038 3.485317 -0.674462 3.072438 -0.368876 2.868714 c
0.368876 3.975341 l
h
3.368876 2.868714 m
3.674462 3.072438 3.757038 3.485317 3.553313 3.790903 c
3.349589 4.096490 2.936711 4.179066 2.631124 3.975341 c
3.368876 2.868714 l
h
0.835000 1.422028 m
0.835000 1.054758 1.132731 0.757028 1.500000 0.757028 c
1.867269 0.757028 2.165000 1.054758 2.165000 1.422028 c
0.835000 1.422028 l
h
-0.368876 2.868714 m
1.131124 1.868714 l
1.868876 2.975341 l
0.368876 3.975341 l
-0.368876 2.868714 l
h
1.868876 1.868714 m
3.368876 2.868714 l
2.631124 3.975341 l
1.131124 2.975341 l
1.868876 1.868714 l
h
0.835000 2.422028 m
0.835000 1.422028 l
2.165000 1.422028 l
2.165000 2.422028 l
0.835000 2.422028 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 21.250031 17.500000 cm
0.000000 0.000000 0.000000 scn
2.500000 1.250000 m
2.500000 0.559644 1.940356 0.000000 1.250000 0.000000 c
0.559644 0.000000 0.000000 0.559644 0.000000 1.250000 c
0.000000 1.940356 0.559644 2.500000 1.250000 2.500000 c
1.940356 2.500000 2.500000 1.940356 2.500000 1.250000 c
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 13.000000 4.920006 cm
0.000000 0.000000 0.000000 scn
0.000000 4.079994 m
0.000000 4.744994 l
-0.367269 4.744994 -0.665000 4.447264 -0.665000 4.079994 c
0.000000 4.079994 l
h
4.000000 4.079994 m
4.665000 4.079994 l
4.665000 4.447264 4.367270 4.744994 4.000000 4.744994 c
4.000000 4.079994 l
h
-0.665000 4.079994 m
-0.665000 3.329994 l
0.665000 3.329994 l
0.665000 4.079994 l
-0.665000 4.079994 l
h
4.665000 3.329994 m
4.665000 4.079994 l
3.335000 4.079994 l
3.335000 3.329994 l
4.665000 3.329994 l
h
4.000000 4.744994 m
0.000000 4.744994 l
0.000000 3.414994 l
4.000000 3.414994 l
4.000000 4.744994 l
h
2.000000 0.664994 m
3.471839 0.664994 4.665000 1.858155 4.665000 3.329994 c
3.335000 3.329994 l
3.335000 2.592694 2.737300 1.994994 2.000000 1.994994 c
2.000000 0.664994 l
h
-0.665000 3.329994 m
-0.665000 1.858155 0.528161 0.664994 2.000000 0.664994 c
2.000000 1.994994 l
1.262700 1.994994 0.665000 2.592694 0.665000 3.329994 c
-0.665000 3.329994 l
h
f
n
Q
endstream
endobj
3 0 obj
11022
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000011112 00000 n
0000011136 00000 n
0000011309 00000 n
0000011383 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
11442
%%EOF

View File

@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "Icon-23.pdf",
"filename" : "addphoto_30.pdf",
"idiom" : "universal"
}
],

View File

@ -1,131 +0,0 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 1.335022 2.335083 cm
0.000000 0.478431 1.000000 scn
12.735368 21.999956 m
12.289006 21.999956 11.872177 21.776876 11.624580 21.405481 c
10.812046 20.186680 l
10.317780 19.445280 9.485683 18.999956 8.594632 18.999956 c
6.665000 18.999956 l
5.927700 18.999956 5.330000 18.402256 5.330000 17.664955 c
5.330000 11.664956 l
5.330000 11.297687 5.032269 10.999956 4.665000 10.999956 c
4.297730 10.999956 4.000000 11.297687 4.000000 11.664956 c
4.000000 17.664955 l
4.000000 19.136795 5.193161 20.329956 6.665000 20.329956 c
8.594632 20.329956 l
9.040994 20.329956 9.457823 20.553036 9.705420 20.924431 c
10.517954 22.143232 l
11.012220 22.884632 11.844316 23.329956 12.735368 23.329956 c
16.594631 23.329956 l
17.485683 23.329956 18.317780 22.884632 18.812046 22.143232 c
19.624580 20.924431 l
19.872177 20.553036 20.289005 20.329956 20.735367 20.329956 c
22.665001 20.329956 l
24.136837 20.329956 25.330002 19.136795 25.330002 17.664955 c
25.330002 6.664955 l
25.330002 5.193119 24.136837 3.999954 22.665001 3.999954 c
11.665000 3.999954 l
11.297730 3.999954 11.000000 4.297688 11.000000 4.664955 c
11.000000 5.032227 11.297730 5.329956 11.665000 5.329956 c
22.665001 5.329956 l
23.402302 5.329956 24.000000 5.927654 24.000000 6.664955 c
24.000000 17.664955 l
24.000000 18.402256 23.402302 18.999956 22.665001 18.999956 c
20.735367 18.999956 l
19.844316 18.999956 19.012220 19.445280 18.517954 20.186680 c
17.705420 21.405481 l
17.457823 21.776876 17.040993 21.999956 16.594631 21.999956 c
12.735368 21.999956 l
h
5.330000 8.664956 m
5.330000 9.032226 5.032269 9.329956 4.665000 9.329956 c
4.297730 9.329956 4.000000 9.032226 4.000000 8.664956 c
4.000000 5.329956 l
0.665000 5.329956 l
0.297731 5.329956 0.000000 5.032225 0.000000 4.664955 c
0.000000 4.297686 0.297731 3.999954 0.665000 3.999954 c
4.000000 3.999954 l
4.000000 0.664955 l
4.000000 0.297688 4.297730 -0.000044 4.665000 -0.000044 c
5.032269 -0.000044 5.330000 0.297688 5.330000 0.664955 c
5.330000 3.999954 l
8.665000 3.999954 l
9.032269 3.999954 9.330000 4.297686 9.330000 4.664955 c
9.330000 5.032225 9.032269 5.329956 8.665000 5.329956 c
5.330000 5.329956 l
5.330000 8.664956 l
h
11.330000 12.664956 m
11.330000 14.506825 12.823131 15.999956 14.665000 15.999956 c
16.506870 15.999956 18.000000 14.506825 18.000000 12.664956 c
18.000000 10.823086 16.506870 9.329956 14.665000 9.329956 c
12.823131 9.329956 11.330000 10.823086 11.330000 12.664956 c
h
14.665000 17.329956 m
12.088592 17.329956 10.000000 15.241364 10.000000 12.664956 c
10.000000 10.088548 12.088592 7.999956 14.665000 7.999956 c
17.241409 7.999956 19.330000 10.088548 19.330000 12.664956 c
19.330000 15.241364 17.241409 17.329956 14.665000 17.329956 c
h
f*
n
Q
endstream
endobj
3 0 obj
2753
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Type /Catalog
/Pages 5 0 R
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000002843 00000 n
0000002866 00000 n
0000003039 00000 n
0000003113 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
3172
%%EOF

View File

@ -0,0 +1,183 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.840027 3.334705 cm
0.000000 0.000000 0.000000 scn
11.735510 22.330292 m
11.667685 22.330313 l
11.244255 22.330513 10.924585 22.330666 10.616322 22.256659 c
10.344395 22.191374 10.084438 22.083696 9.845995 21.937578 c
9.575688 21.771935 9.349753 21.545786 9.050485 21.246233 c
9.002542 21.198259 l
8.204847 20.400566 l
8.017340 20.213058 7.961475 20.158772 7.905609 20.114943 c
7.713588 19.964300 7.483945 19.869179 7.241645 19.839920 c
7.171151 19.831408 7.093261 19.830292 6.828084 19.830292 c
6.729855 19.830307 l
5.970903 19.830494 5.468783 19.830616 5.037611 19.724943 c
3.711147 19.399847 2.675471 18.364172 2.350375 17.037708 c
2.244702 16.606535 2.244824 16.104416 2.245010 15.345463 c
2.245026 15.247234 l
2.245026 11.040292 l
2.245026 10.673022 2.542757 10.375292 2.910026 10.375292 c
3.277296 10.375292 3.575026 10.673022 3.575026 11.040292 c
3.575026 15.247234 l
3.575026 16.139984 3.579845 16.466915 3.642145 16.721115 c
3.849266 17.566216 4.509103 18.226051 5.354204 18.433174 c
5.608402 18.495474 5.935334 18.500292 6.828084 18.500292 c
6.862669 18.500286 l
6.862678 18.500286 l
7.078507 18.500235 7.241119 18.500195 7.401091 18.519512 c
7.884783 18.577921 8.343209 18.767807 8.726533 19.068527 c
8.853310 19.167986 8.968266 19.282997 9.120847 19.435654 c
9.145300 19.460114 l
9.942994 20.257809 l
10.311279 20.626093 10.421803 20.730574 10.540918 20.803566 c
10.660363 20.876762 10.790585 20.930702 10.926804 20.963406 c
11.062645 20.996017 11.214676 21.000292 11.735510 21.000292 c
16.584543 21.000292 l
17.105377 21.000292 17.257408 20.996017 17.393248 20.963406 c
17.529467 20.930702 17.659689 20.876762 17.779135 20.803566 c
17.898249 20.730574 18.008772 20.626093 18.377058 20.257809 c
19.174751 19.460114 l
19.199215 19.435642 l
19.199238 19.435621 l
19.351803 19.282984 19.466751 19.167980 19.593519 19.068527 c
19.976843 18.767807 20.435268 18.577921 20.918962 18.519512 c
21.078936 18.500195 21.241550 18.500235 21.457384 18.500286 c
21.491968 18.500292 l
22.384720 18.500292 22.711651 18.495474 22.965849 18.433174 c
23.810949 18.226051 24.470787 17.566216 24.677908 16.721115 c
24.740208 16.466915 24.745026 16.139984 24.745026 15.247233 c
24.745026 7.715292 l
24.745026 6.864240 24.744509 6.265945 24.706362 5.799051 c
24.668850 5.339918 24.598289 5.066721 24.490526 4.855225 c
24.266663 4.415867 23.909452 4.058657 23.470095 3.834793 c
23.258598 3.727030 22.985401 3.656469 22.526268 3.618958 c
22.059374 3.580811 21.461079 3.580294 20.610025 3.580294 c
11.160027 3.580294 l
10.792757 3.580294 10.495026 3.282562 10.495026 2.915293 c
10.495026 2.548023 10.792757 2.250292 11.160027 2.250292 c
20.610025 2.250292 l
20.638830 2.250292 l
20.638893 2.250292 l
21.454515 2.250286 22.107164 2.250282 22.634573 2.293373 c
23.175953 2.337606 23.643663 2.430536 24.073902 2.649754 c
24.763515 3.001129 25.324190 3.561804 25.675566 4.251417 c
25.894783 4.681656 25.987713 5.149366 26.031946 5.690746 c
26.075037 6.218155 26.075033 6.870803 26.075027 7.686420 c
26.075027 7.686486 l
26.075027 7.715292 l
26.075027 15.247233 l
26.075043 15.345453 l
26.075230 16.104412 26.075352 16.606533 25.969677 17.037708 c
25.644583 18.364172 24.608906 19.399847 23.282442 19.724943 c
22.851271 19.830616 22.349150 19.830494 21.590197 19.830307 c
21.491968 19.830292 l
21.226791 19.830292 21.148901 19.831408 21.078407 19.839920 c
20.836107 19.869179 20.606464 19.964300 20.414444 20.114943 c
20.358578 20.158772 20.302713 20.213058 20.115204 20.400566 c
19.317511 21.198261 l
19.269571 21.246229 l
18.970303 21.545784 18.744366 21.771933 18.474058 21.937578 c
18.235615 22.083696 17.975657 22.191374 17.703732 22.256659 c
17.395468 22.330666 17.075798 22.330513 16.652369 22.330313 c
16.584543 22.330292 l
11.735510 22.330292 l
h
10.137527 11.352793 m
10.137527 13.574358 11.938460 15.375292 14.160027 15.375292 c
16.381592 15.375292 18.182526 13.574358 18.182526 11.352793 c
18.182526 9.131227 16.381592 7.330292 14.160027 7.330292 c
11.938460 7.330292 10.137527 9.131227 10.137527 11.352793 c
h
14.160027 16.705292 m
11.203922 16.705292 8.807527 14.308896 8.807527 11.352793 c
8.807527 8.396688 11.203922 6.000292 14.160027 6.000292 c
17.116131 6.000292 19.512526 8.396688 19.512526 11.352793 c
19.512526 14.308896 17.116131 16.705292 14.160027 16.705292 c
h
22.910027 15.415292 m
22.910027 14.724936 22.350382 14.165293 21.660027 14.165293 c
20.969671 14.165293 20.410027 14.724936 20.410027 15.415292 c
20.410027 16.105648 20.969671 16.665291 21.660027 16.665291 c
22.350382 16.665291 22.910027 16.105648 22.910027 15.415292 c
h
4.665000 9.330334 m
5.032269 9.330334 5.330000 9.032602 5.330000 8.665333 c
5.330000 5.330334 l
8.665000 5.330334 l
9.032269 5.330334 9.330000 5.032602 9.330000 4.665333 c
9.330000 4.298063 9.032269 4.000332 8.665000 4.000332 c
5.330000 4.000332 l
5.330000 0.665333 l
5.330000 0.298063 5.032269 0.000332 4.665000 0.000332 c
4.297730 0.000332 4.000000 0.298063 4.000000 0.665333 c
4.000000 4.000332 l
0.665000 4.000332 l
0.297731 4.000332 0.000000 4.298063 0.000000 4.665333 c
0.000000 5.032602 0.297731 5.330334 0.665000 5.330334 c
4.000000 5.330334 l
4.000000 8.665333 l
4.000000 9.032602 4.297730 9.330334 4.665000 9.330334 c
h
f*
n
Q
endstream
endobj
3 0 obj
5244
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000005334 00000 n
0000005357 00000 n
0000005530 00000 n
0000005604 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
5663
%%EOF

View File

@ -454,7 +454,7 @@ public func createChannelController(context: AccountContext) -> ViewController {
}
}
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: stateValue.with({ $0.avatar }) != nil, hasViewButton: false, personalPhoto: false, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: false)!
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: stateValue.with({ $0.avatar }) != nil, hasViewButton: false, personalPhoto: false, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: false, title: nil)!
let _ = currentAvatarMixin.swap(mixin)
mixin.requestSearchController = { assetsController in
let controller = WebSearchController(context: context, peer: peer, chatLocation: nil, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: title, completion: { result in

View File

@ -727,7 +727,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
}
}
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: stateValue.with({ $0.avatar }) != nil, hasViewButton: false, personalPhoto: false, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: false)!
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: stateValue.with({ $0.avatar }) != nil, hasViewButton: false, personalPhoto: false, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: false, title: nil)!
let _ = currentAvatarMixin.swap(mixin)
mixin.requestSearchController = { assetsController in
let controller = WebSearchController(context: context, peer: peer, chatLocation: nil, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: title, completion: { result in

View File

@ -1,6 +1,8 @@
import AsyncDisplayKit
import Display
import SwiftSignalKit
import TelegramPresentationData
import AvatarNode
enum PeerInfoScreenActionColor {
case accent
@ -18,14 +20,16 @@ final class PeerInfoScreenActionItem: PeerInfoScreenItem {
let text: String
let color: PeerInfoScreenActionColor
let icon: UIImage?
let iconSignal: Signal<UIImage?, NoError>?
let alignment: PeerInfoScreenActionAligmnent
let action: (() -> Void)?
init(id: AnyHashable, text: String, color: PeerInfoScreenActionColor = .accent, icon: UIImage? = nil, alignment: PeerInfoScreenActionAligmnent = .natural, action: (() -> Void)?) {
init(id: AnyHashable, text: String, color: PeerInfoScreenActionColor = .accent, icon: UIImage? = nil, iconSignal: Signal<UIImage?, NoError>? = nil, alignment: PeerInfoScreenActionAligmnent = .natural, action: (() -> Void)?) {
self.id = id
self.text = text
self.color = color
self.icon = icon
self.iconSignal = iconSignal
self.alignment = alignment
self.action = action
}
@ -45,6 +49,8 @@ private final class PeerInfoScreenActionItemNode: PeerInfoScreenItemNode {
private var item: PeerInfoScreenActionItem?
private let iconDisposable = MetaDisposable()
override init() {
var bringToFrontForHighlightImpl: (() -> Void)?
self.selectionNode = PeerInfoScreenSelectableBackgroundNode(bringToFrontForHighlight: { bringToFrontForHighlightImpl?() })
@ -79,17 +85,21 @@ private final class PeerInfoScreenActionItemNode: PeerInfoScreenItemNode {
self.addSubnode(self.activateArea)
}
deinit {
self.iconDisposable.dispose()
}
override func update(width: CGFloat, safeInsets: UIEdgeInsets, presentationData: PresentationData, item: PeerInfoScreenItem, topItem: PeerInfoScreenItem?, bottomItem: PeerInfoScreenItem?, hasCorners: Bool, transition: ContainedViewLayoutTransition) -> CGFloat {
guard let item = item as? PeerInfoScreenActionItem else {
return 10.0
}
self.item = item
self.selectionNode.pressed = item.action
let sideInset: CGFloat = 16.0 + safeInsets.left
var leftInset = (item.icon == nil ? sideInset : sideInset + 29.0 + 16.0)
var leftInset = (item.icon == nil && item.iconSignal == nil ? sideInset : sideInset + 29.0 + 16.0)
var iconInset = sideInset
if case .peerList = item.alignment {
leftInset += 5.0
@ -126,6 +136,19 @@ private final class PeerInfoScreenActionItemNode: PeerInfoScreenItemNode {
self.iconNode.image = generateTintedImage(image: icon, color: textColorValue)
let iconFrame = CGRect(origin: CGPoint(x: iconInset, y: floorToScreenPixels((height - icon.size.height) / 2.0)), size: icon.size)
transition.updateFrame(node: self.iconNode, frame: iconFrame)
} else if let iconSignal = item.iconSignal {
self.iconDisposable.set((iconSignal
|> deliverOnMainQueue).start(next: { [weak self] image in
if let strongSelf = self, let image {
strongSelf.iconNode.image = image
let iconFrame = CGRect(origin: CGPoint(x: iconInset, y: floorToScreenPixels((height - image.size.height) / 2.0)), size: image.size)
transition.updateFrame(node: strongSelf.iconNode, frame: iconFrame)
}
}))
if self.iconNode.supernode == nil {
self.addSubnode(self.iconNode)
}
} else if self.iconNode.supernode != nil {
self.iconNode.image = nil
self.iconNode.removeFromSupernode()

View File

@ -668,7 +668,14 @@ final class PeerInfoEditingAvatarOverlayNode: ASDisplayNode {
clipStyle = .round
}
if canEditPeerInfo(context: self.context, peer: peer, chatLocation: chatLocation, threadData: threadData) {
var isPersonal = false
if let updatingAvatar, case let .image(image) = updatingAvatar, image.isPersonal {
isPersonal = true
}
if canEditPeerInfo(context: self.context, peer: peer, chatLocation: chatLocation, threadData: threadData)
|| isPersonal
|| self.currentRepresentation != nil && updatingAvatar == nil {
var overlayHidden = true
if let updatingAvatar = updatingAvatar {
overlayHidden = false

View File

@ -481,6 +481,9 @@ private final class PeerInfoInteraction {
let editingOpenSoundSettings: () -> Void
let editingToggleShowMessageText: (Bool) -> Void
let requestDeleteContact: () -> Void
let suggestPhoto: () -> Void
let setCustomPhoto: () -> Void
let resetCustomPhoto: () -> Void
let openAddContact: () -> Void
let updateBlocked: (Bool) -> Void
let openReport: (PeerInfoReportType) -> Void
@ -526,6 +529,9 @@ private final class PeerInfoInteraction {
editingOpenSoundSettings: @escaping () -> Void,
editingToggleShowMessageText: @escaping (Bool) -> Void,
requestDeleteContact: @escaping () -> Void,
suggestPhoto: @escaping () -> Void,
setCustomPhoto: @escaping () -> Void,
resetCustomPhoto: @escaping () -> Void,
openChat: @escaping () -> Void,
openAddContact: @escaping () -> Void,
updateBlocked: @escaping (Bool) -> Void,
@ -571,6 +577,9 @@ private final class PeerInfoInteraction {
self.editingOpenSoundSettings = editingOpenSoundSettings
self.editingToggleShowMessageText = editingToggleShowMessageText
self.requestDeleteContact = requestDeleteContact
self.suggestPhoto = suggestPhoto
self.setCustomPhoto = setCustomPhoto
self.resetCustomPhoto = resetCustomPhoto
self.openChat = openChat
self.openAddContact = openAddContact
self.updateBlocked = updateBlocked
@ -1329,7 +1338,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
return result
}
private func editingItems(data: PeerInfoScreenData?, chatLocation: ChatLocation, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction) -> [(AnyHashable, [PeerInfoScreenItem])] {
private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatLocation: ChatLocation, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction) -> [(AnyHashable, [PeerInfoScreenItem])] {
enum Section: Int, CaseIterable {
case notifications
case groupLocation
@ -1346,8 +1355,33 @@ private func editingItems(data: PeerInfoScreenData?, chatLocation: ChatLocation,
}
if let data = data {
if let _ = data.peer as? TelegramUser {
let ItemDelete = 0
if let user = data.peer as? TelegramUser {
let ItemSuggest = 0
let ItemCustom = 1
let ItemReset = 2
let ItemDelete = 3
let compactName = EnginePeer(user).compactDisplayTitle
items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemSuggest, text: presentationData.strings.UserInfo_SuggestPhoto(compactName).string, color: .accent, icon: UIImage(bundleImageName: "Peer Info/SuggestAvatar"), action: {
interaction.suggestPhoto()
}))
items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemCustom, text: presentationData.strings.UserInfo_SetCustomPhoto(compactName).string, color: .accent, icon: UIImage(bundleImageName: "Settings/SetAvatar"), action: {
interaction.setCustomPhoto()
}))
if user.photo.first?.isPersonal == true || state.updatingAvatar != nil {
var representation: TelegramMediaImageRepresentation?
if let cachedData = data.cachedData as? CachedUserData, case let .known(photo) = cachedData.photo {
representation = photo?.representationForDisplayAtSize(PixelDimensions(width: 28, height: 28))
}
items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemReset, text: presentationData.strings.UserInfo_ResetCustomPhoto, color: .accent, icon: nil, iconSignal: peerAvatarCompleteImage(account: context.account, peer: EnginePeer(user), forceProvidedRepresentation: true, representation: representation, size: CGSize(width: 28.0, height: 28.0)), action: {
interaction.resetCustomPhoto()
}))
}
if data.isContact {
items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemDelete, text: presentationData.strings.UserInfo_DeleteContact, color: .destructive, action: {
interaction.requestDeleteContact()
@ -1985,6 +2019,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
requestDeleteContact: { [weak self] in
self?.requestDeleteContact()
},
suggestPhoto: { [weak self] in
self?.suggestPhoto()
},
setCustomPhoto: { [weak self] in
self?.setCustomPhoto()
},
resetCustomPhoto: { [weak self] in
self?.resetCustomPhoto()
},
openChat: { [weak self] in
self?.openChat()
},
@ -2827,10 +2870,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
self?.openAvatarForEditing(fromGallery: true, completion: completion)
}
galleryController.avatarPhotoEditCompletion = { [weak self] image in
self?.updateProfilePhoto(image)
self?.updateProfilePhoto(image, mode: .generic)
}
galleryController.avatarVideoEditCompletion = { [weak self] image, asset, adjustments in
self?.updateProfileVideo(image, asset: asset, adjustments: adjustments)
self?.updateProfileVideo(image, asset: asset, adjustments: adjustments, mode: .generic)
}
galleryController.removedEntry = { [weak self] entry in
if let item = PeerInfoAvatarListItem(entry: entry) {
@ -3634,6 +3677,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
var previousAbout: String?
var currentAbout: String?
var previousPhotoIsPersonal: Bool?
var currentPhotoIsPersonal: Bool?
if let previousUser = previousData?.peer as? TelegramUser {
previousPhotoIsPersonal = previousUser.profileImageRepresentations.first?.isPersonal == true
}
if let user = data.peer as? TelegramUser {
currentPhotoIsPersonal = user.profileImageRepresentations.first?.isPersonal == true
}
if let previousCachedData = previousData?.cachedData as? CachedChannelData, let cachedData = data.cachedData as? CachedChannelData {
previousCall = previousCachedData.activeCall
currentCall = cachedData.activeCall
@ -3670,6 +3722,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
if (previousAbout?.isEmpty ?? true) != (currentAbout?.isEmpty ?? true) {
infoUpdated = true
}
if let previousPhotoIsPersonal, let currentPhotoIsPersonal, previousPhotoIsPersonal != currentPhotoIsPersonal {
infoUpdated = true
}
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: self.didSetReady && (membersUpdated || infoUpdated) ? .animated(duration: 0.3, curve: .spring) : .immediate)
}
}
@ -6686,7 +6741,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}
}
private func updateProfilePhoto(_ image: UIImage) {
private func updateProfilePhoto(_ image: UIImage, mode: AvatarEditingMode) {
guard let data = image.jpegData(compressionQuality: 0.6) else {
return
}
@ -6700,20 +6755,30 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: mode == .custom ? true : false)
self.state = self.state.withUpdatingAvatar(.image(representation))
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: mode == .custom ? .animated(duration: 0.2, curve: .easeInOut) : .immediate, additive: false)
}
self.headerNode.ignoreCollapse = false
let postbox = self.context.account.postbox
let signal = self.isSettings ? self.context.engine.accountData.updateAccountPhoto(resource: resource, videoResource: nil, videoStartTimestamp: nil, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
}) : self.context.engine.peers.updatePeerPhoto(peerId: self.peerId, photo: self.context.engine.peers.uploadedPeerPhoto(resource: resource), mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
})
let signal: Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError>
if self.isSettings {
signal = self.context.engine.accountData.updateAccountPhoto(resource: resource, videoResource: nil, videoStartTimestamp: nil, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
})
} else if case .custom = mode {
signal = self.context.engine.contacts.updateContactPhoto(peerId: self.peerId, resource: resource, videoResource: nil, videoStartTimestamp: nil, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
})
} else {
signal = self.context.engine.peers.updatePeerPhoto(peerId: self.peerId, photo: self.context.engine.peers.uploadedPeerPhoto(resource: resource), mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
})
}
self.updateAvatarDisposable.set((signal
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let strongSelf = self else {
@ -6728,10 +6793,19 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
}
if case .complete = result, case .custom = mode {
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
if let strongSelf = self, let peer {
strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_SuccessPhotoText(peer.compactDisplayTitle).string, action: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
}
})
}
}))
}
private func updateProfileVideo(_ image: UIImage, asset: Any?, adjustments: TGVideoEditAdjustments?) {
private func updateProfileVideo(_ image: UIImage, asset: Any?, adjustments: TGVideoEditAdjustments?, mode: AvatarEditingMode) {
guard let data = image.jpegData(compressionQuality: 0.6) else {
return
}
@ -6745,11 +6819,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
let photoResource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
self.context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: photoResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: photoResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: mode == .custom ? true : false)
self.state = self.state.withUpdatingAvatar(.image(representation))
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: mode == .custom ? .animated(duration: 0.2, curve: .easeInOut) : .immediate, additive: false)
}
self.headerNode.ignoreCollapse = false
@ -6847,6 +6921,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
return context.engine.accountData.updateAccountPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
})
} else if case .custom = mode {
return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
})
} else {
return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: photoResource), video: context.engine.peers.uploadedPeerVideo(resource: videoResource) |> map(Optional.init), videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
@ -6866,11 +6944,26 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
}
if case .complete = result, case .custom = mode {
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
if let strongSelf = self, let peer {
strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_SuccessVideoText(peer.compactDisplayTitle).string, action: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
}
})
}
}))
}
private func openAvatarForEditing(fromGallery: Bool = false, completion: @escaping () -> Void = {}) {
guard let peer = self.data?.peer, canEditPeerInfo(context: self.context, peer: peer, chatLocation: self.chatLocation, threadData: self.data?.threadData) else {
private enum AvatarEditingMode {
case generic
case suggest
case custom
}
private func openAvatarForEditing(mode: AvatarEditingMode = .generic, fromGallery: Bool = false, completion: @escaping () -> Void = {}) {
guard let peer = self.data?.peer, mode != .generic || canEditPeerInfo(context: self.context, peer: peer, chatLocation: self.chatLocation, threadData: self.data?.threadData) else {
return
}
@ -6929,7 +7022,36 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
isForum = true
}
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos && !fromGallery, hasViewButton: false, personalPhoto: strongSelf.isSettings, isVideo: currentIsVideo, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: isForum)!
var hasDeleteButton = false
if case .generic = mode {
hasDeleteButton = hasPhotos && !fromGallery
} else if case .custom = mode {
hasDeleteButton = peer.profileImageRepresentations.first?.isPersonal == true
}
let title: String?
let confirmationTextPhoto: String?
let confirmationTextVideo: String?
let confirmationAction: String?
switch mode {
case .suggest:
title = strongSelf.presentationData.strings.UserInfo_SuggestPhotoTitle(peer.compactDisplayTitle).string
confirmationTextPhoto = strongSelf.presentationData.strings.UserInfo_SuggestPhoto_AlertPhotoText(peer.compactDisplayTitle).string
confirmationTextVideo = strongSelf.presentationData.strings.UserInfo_SuggestPhoto_AlertVideoText(peer.compactDisplayTitle).string
confirmationAction = strongSelf.presentationData.strings.UserInfo_SuggestPhoto_AlertSuggest
case .custom:
title = strongSelf.presentationData.strings.UserInfo_SetCustomPhotoTitle(peer.compactDisplayTitle).string
confirmationTextPhoto = strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_AlertPhotoText(peer.compactDisplayTitle, peer.compactDisplayTitle).string
confirmationTextVideo = strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_AlertVideoText(peer.compactDisplayTitle, peer.compactDisplayTitle).string
confirmationAction = strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_AlertSet
default:
title = nil
confirmationTextPhoto = nil
confirmationTextVideo = nil
confirmationAction = nil
}
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasDeleteButton, hasViewButton: false, personalPhoto: strongSelf.isSettings, isVideo: currentIsVideo, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: isForum, title: title)!
mixin.stickersContext = paintStickersContext
let _ = strongSelf.currentAvatarMixin.swap(mixin)
mixin.requestSearchController = { [weak self] assetsController in
@ -6938,7 +7060,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}
let controller = WebSearchController(context: strongSelf.context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, peer: peer, chatLocation: nil, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: strongSelf.isSettings ? nil : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), completion: { [weak self] result in
assetsController?.dismiss()
self?.updateProfilePhoto(result)
self?.updateProfilePhoto(result, mode: mode)
}))
controller.navigationPresentation = .modal
strongSelf.controller?.push(controller)
@ -6947,16 +7069,36 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
completion()
}
}
if let confirmationTextPhoto, let confirmationAction {
mixin.willFinishWithImage = { [weak self] image, commit in
if let strongSelf = self, let image {
let controller = photoUpdateConfirmationController(context: strongSelf.context, peer: peer, image: image, text: confirmationTextPhoto, doneTitle: confirmationAction, commit: {
commit?()
})
strongSelf.controller?.presentInGlobalOverlay(controller)
}
}
}
if let confirmationTextVideo, let confirmationAction {
mixin.willFinishWithVideo = { [weak self] image, commit in
if let strongSelf = self, let image {
let controller = photoUpdateConfirmationController(context: strongSelf.context, peer: peer, image: image, text: confirmationTextVideo, doneTitle: confirmationAction, commit: {
commit?()
})
strongSelf.controller?.presentInGlobalOverlay(controller)
}
}
}
mixin.didFinishWithImage = { [weak self] image in
if let image = image {
completion()
self?.updateProfilePhoto(image)
self?.updateProfilePhoto(image, mode: mode)
}
}
mixin.didFinishWithVideo = { [weak self] image, asset, adjustments in
if let image = image, let asset = asset {
completion()
self?.updateProfileVideo(image, asset: asset, adjustments: adjustments)
self?.updateProfileVideo(image, asset: asset, adjustments: adjustments, mode: mode)
}
}
mixin.didFinishWithDelete = {
@ -8030,6 +8172,44 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
peerController.present(controller, in: .window(.root))
}
private func suggestPhoto() {
self.openAvatarForEditing(mode: .suggest)
}
private func setCustomPhoto() {
self.openAvatarForEditing(mode: .custom)
}
private func resetCustomPhoto() {
guard let peer = self.data?.peer else {
return
}
let alertController = textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: nil, text: self.presentationData.strings.UserInfo_ResetToOriginalAlertText(EnginePeer(peer).compactDisplayTitle).string, actions: [
TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {
}),
TextAlertAction(type: .defaultAction, title: self.presentationData.strings.UserInfo_ResetToOriginalAlertReset, action: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.updateAvatarDisposable.set((strongSelf.context.engine.contacts.updateContactPhoto(peerId: strongSelf.peerId, resource: nil, videoResource: nil, videoStartTimestamp: nil, mapResourceToAvatarSizes: { resource, representations in
mapResourceToAvatarSizes(postbox: strongSelf.context.account.postbox, resource: resource, representations: representations)
})
|> deliverOnMainQueue).start(next: { [weak self] _ in
guard let strongSelf = self else {
return
}
strongSelf.state = strongSelf.state.withUpdatingAvatar(nil).withAvatarUploadProgress(nil)
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.2, curve: .easeInOut), additive: false)
}
}))
})
])
self.controller?.present(alertController, in: .window(.root))
}
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
@ -8160,7 +8340,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}
var validEditingSections: [AnyHashable] = []
let editItems = self.isSettings ? settingsEditingItems(data: self.data, state: self.state, context: self.context, presentationData: self.presentationData, interaction: self.interaction) : editingItems(data: self.data, chatLocation: self.chatLocation, context: self.context, presentationData: self.presentationData, interaction: self.interaction)
let editItems = self.isSettings ? settingsEditingItems(data: self.data, state: self.state, context: self.context, presentationData: self.presentationData, interaction: self.interaction) : editingItems(data: self.data, state: self.state, chatLocation: self.chatLocation, context: self.context, presentationData: self.presentationData, interaction: self.interaction)
for (sectionId, sectionItems) in editItems {
var insets = UIEdgeInsets()

View File

@ -0,0 +1,244 @@
import Foundation
import UIKit
import SwiftSignalKit
import AsyncDisplayKit
import Display
import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import AccountContext
import AppBundle
import AvatarNode
private final class PhotoUpdateConfirmationAlertContentNode: AlertContentNode {
private let strings: PresentationStrings
private let text: String
private let textNode: ASTextNode
private let avatarNode: AvatarNode
private let arrowNode: ASImageNode
private let iconNode: ASImageNode
private let actionNodesSeparator: ASDisplayNode
private let actionNodes: [TextAlertContentActionNode]
private let actionVerticalSeparators: [ASDisplayNode]
private var validLayout: CGSize?
override var dismissOnOutsideTap: Bool {
return self.isUserInteractionEnabled
}
init(context: AccountContext, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, peer: EnginePeer, image: UIImage, text: String, actions: [TextAlertAction]) {
self.strings = strings
self.text = text
self.textNode = ASTextNode()
self.textNode.maximumNumberOfLines = 0
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0))
self.arrowNode = ASImageNode()
self.arrowNode.displaysAsynchronously = false
self.arrowNode.displayWithoutProcessing = true
self.iconNode = ASImageNode()
self.iconNode.clipsToBounds = true
self.iconNode.displaysAsynchronously = false
self.iconNode.displayWithoutProcessing = true
self.iconNode.image = image
self.iconNode.cornerRadius = 30.0
self.actionNodesSeparator = ASDisplayNode()
self.actionNodesSeparator.isLayerBacked = true
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
return TextAlertContentActionNode(theme: theme, action: action)
}
var actionVerticalSeparators: [ASDisplayNode] = []
if actions.count > 1 {
for _ in 0 ..< actions.count - 1 {
let separatorNode = ASDisplayNode()
separatorNode.isLayerBacked = true
actionVerticalSeparators.append(separatorNode)
}
}
self.actionVerticalSeparators = actionVerticalSeparators
super.init()
self.addSubnode(self.textNode)
self.addSubnode(self.avatarNode)
self.addSubnode(self.arrowNode)
self.addSubnode(self.iconNode)
self.addSubnode(self.actionNodesSeparator)
for actionNode in self.actionNodes {
self.addSubnode(actionNode)
}
for separatorNode in self.actionVerticalSeparators {
self.addSubnode(separatorNode)
}
self.updateTheme(theme)
self.avatarNode.setPeer(context: context, theme: ptheme, peer: peer)
}
override func updateTheme(_ theme: AlertControllerTheme) {
self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center)
self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.secondaryColor)
self.actionNodesSeparator.backgroundColor = theme.separatorColor
for actionNode in self.actionNodes {
actionNode.updateTheme(theme)
}
for separatorNode in self.actionVerticalSeparators {
separatorNode.backgroundColor = theme.separatorColor
}
if let size = self.validLayout {
_ = self.updateLayout(size: size, transition: .immediate)
}
}
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
var size = size
size.width = min(size.width, 270.0)
self.validLayout = size
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
let avatarSize = CGSize(width: 60.0, height: 60.0)
self.avatarNode.updateSize(size: avatarSize)
let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) - 44.0, y: origin.y), size: avatarSize)
transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
if let arrowImage = self.arrowNode.image {
let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
transition.updateFrame(node: self.arrowNode, frame: arrowFrame)
}
if let _ = self.iconNode.image {
let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) + 44.0, y: origin.y), size: avatarSize)
transition.updateFrame(node: self.iconNode, frame: iconFrame)
origin.y += avatarSize.height + 10.0
}
let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height))
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
let actionButtonHeight: CGFloat = 44.0
var minActionsWidth: CGFloat = 0.0
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
let actionTitleInsets: CGFloat = 8.0
var effectiveActionLayout = TextAlertContentActionLayout.horizontal
for actionNode in self.actionNodes {
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 {
effectiveActionLayout = .vertical
}
switch effectiveActionLayout {
case .horizontal:
minActionsWidth += actionTitleSize.width + actionTitleInsets
case .vertical:
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
}
}
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
let contentWidth = max(size.width, minActionsWidth)
var actionsHeight: CGFloat = 0.0
switch effectiveActionLayout {
case .horizontal:
actionsHeight = actionButtonHeight
case .vertical:
actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
}
let resultSize = CGSize(width: contentWidth, height: avatarSize.height + textSize.height + actionsHeight + 16.0 + insets.top + insets.bottom)
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
var actionOffset: CGFloat = 0.0
let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
var separatorIndex = -1
var nodeIndex = 0
for actionNode in self.actionNodes {
if separatorIndex >= 0 {
let separatorNode = self.actionVerticalSeparators[separatorIndex]
switch effectiveActionLayout {
case .horizontal:
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
case .vertical:
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
}
}
separatorIndex += 1
let currentActionWidth: CGFloat
switch effectiveActionLayout {
case .horizontal:
if nodeIndex == self.actionNodes.count - 1 {
currentActionWidth = resultSize.width - actionOffset
} else {
currentActionWidth = actionWidth
}
case .vertical:
currentActionWidth = resultSize.width
}
let actionNodeFrame: CGRect
switch effectiveActionLayout {
case .horizontal:
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
actionOffset += currentActionWidth
case .vertical:
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
actionOffset += actionButtonHeight
}
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
nodeIndex += 1
}
return resultSize
}
}
func photoUpdateConfirmationController(context: AccountContext, peer: EnginePeer, image: UIImage, text: String, doneTitle: String, commit: @escaping () -> Void) -> AlertController {
let theme = defaultDarkColorPresentationTheme
let presentationData = context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: theme)
let strings = presentationData.strings
var dismissImpl: ((Bool) -> Void)?
var contentNode: PhotoUpdateConfirmationAlertContentNode?
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
dismissImpl?(true)
}), TextAlertAction(type: .defaultAction, title: doneTitle, action: {
dismissImpl?(true)
commit()
})]
contentNode = PhotoUpdateConfirmationAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, peer: peer, image: image, text: text, actions: actions)
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!)
dismissImpl = { [weak controller] animated in
if animated {
controller?.dismissAnimated()
} else {
controller?.dismiss()
}
}
return controller
}

View File

@ -37,13 +37,14 @@ func presentLegacyWebSearchEditor(context: AccountContext, theme: PresentationTh
legacyController.bind(controller: controller)
controller.editingContext = TGMediaEditingContext()
controller.didFinishEditing = { [weak controller] _, result, _, hasChanges in
controller.didFinishEditing = { [weak controller] _, result, _, hasChanges, commit in
if !hasChanges {
return
}
if let result = result {
completed(result)
}
commit?()
controller?.dismiss(animated: true)
}
controller.requestThumbnailImage = { _ -> SSignal in