Merge commit '0a2b9ad3d1c7332e8c02144450160ef3a2a5e9ba' into beta

This commit is contained in:
Ilya Laktyushin 2020-07-03 14:43:15 +03:00
commit c2fa985bc2
73 changed files with 250 additions and 8669 deletions

View File

@ -107,9 +107,6 @@
#import <LegacyComponents/TGDoubleTapGestureRecognizer.h>
#import <LegacyComponents/TGEmbedPIPButton.h>
#import <LegacyComponents/TGEmbedPIPPullArrowView.h>
#import <LegacyComponents/TGEmbedPlayerControls.h>
#import <LegacyComponents/TGEmbedPlayerState.h>
#import <LegacyComponents/TGEmbedPlayerView.h>
#import <LegacyComponents/TGFont.h>
#import <LegacyComponents/TGForwardedMessageMediaAttachment.h>
#import <LegacyComponents/TGFullscreenContainerView.h>
@ -125,7 +122,6 @@
#import <LegacyComponents/TGImageManager.h>
#import <LegacyComponents/TGImageManagerTask.h>
#import <LegacyComponents/TGImageMediaAttachment.h>
#import <LegacyComponents/TGImagePickerController.h>
#import <LegacyComponents/TGImageUtils.h>
#import <LegacyComponents/TGImageView.h>
#import <LegacyComponents/TGInputTextTag.h>
@ -137,7 +133,6 @@
#import <LegacyComponents/TGKeyCommand.h>
#import <LegacyComponents/TGKeyCommandController.h>
#import <LegacyComponents/TGLabel.h>
#import <LegacyComponents/TGLegacyCameraController.h>
#import <LegacyComponents/TGLetteredAvatarView.h>
#import <LegacyComponents/TGListsTableView.h>
#import <LegacyComponents/TGLiveUploadInterface.h>
@ -217,7 +212,6 @@
#import <LegacyComponents/TGModernGalleryDefaultInterfaceView.h>
#import <LegacyComponents/TGModernGalleryEditableItem.h>
#import <LegacyComponents/TGModernGalleryEditableItemView.h>
#import <LegacyComponents/TGModernGalleryEmbeddedStickersHeaderView.h>
#import <LegacyComponents/TGModernGalleryImageItem.h>
#import <LegacyComponents/TGModernGalleryImageItemContainerView.h>
#import <LegacyComponents/TGModernGalleryImageItemImageView.h>
@ -280,7 +274,6 @@
#import <LegacyComponents/TGStaticBackdropAreaData.h>
#import <LegacyComponents/TGStaticBackdropImageData.h>
#import <LegacyComponents/TGStickerAssociation.h>
#import <LegacyComponents/TGStickerKeyboardTabPanel.h>
#import <LegacyComponents/TGStickerPack.h>
#import <LegacyComponents/TGStickerPackReference.h>
#import <LegacyComponents/TGStringUtils.h>

View File

@ -1,51 +0,0 @@
#import <UIKit/UIKit.h>
@class TGModernButton;
@class TGEmbedPlayerState;
typedef enum {
TGEmbedPlayerControlsTypeNone,
TGEmbedPlayerControlsTypeSimple,
TGEmbedPlayerControlsTypeFull
} TGEmbedPlayerControlsType;
typedef enum {
TGEmbedPlayerWatermarkPositionTopLeft,
TGEmbedPlayerWatermarkPositionBottomLeft,
TGEmbedPlayerWatermarkPositionBottomRight
} TGEmbedPlayerWatermarkPosition;
@interface TGEmbedPlayerControls : UIView
@property (nonatomic, copy) void (^panelVisibilityChange)(bool hidden);
@property (nonatomic, copy) void (^playPressed)(void);
@property (nonatomic, copy) void (^pausePressed)(void);
@property (nonatomic, copy) void (^fullscreenPressed)(void);
@property (nonatomic, copy) void (^seekToPosition)(CGFloat position);
@property (nonatomic, copy) void (^pictureInPicturePressed)(void);
@property (nonatomic, assign) TGEmbedPlayerWatermarkPosition watermarkPosition;
@property (nonatomic, strong) UIImage *watermarkImage;
@property (nonatomic, assign) bool watermarkPrerenderedOpacity;
@property (nonatomic, assign) CGPoint watermarkOffset;
@property (nonatomic, copy) void(^watermarkPressed)(void);
- (instancetype)initWithFrame:(CGRect)frame type:(TGEmbedPlayerControlsType)type;
- (void)setWatermarkHidden:(bool)hidden;
- (void)setDisabled;
- (void)hidePlayButton;
- (void)setPictureInPictureHidden:(bool)hidden;
- (void)showLargePlayButton:(bool)force;
- (void)setState:(TGEmbedPlayerState *)state;
- (void)notifyOfPlaybackStart;
- (void)setHidden:(bool)hidden animated:(bool)animated;
@property (nonatomic, assign) bool inhibitFullscreenButton;
- (void)setFullscreenButtonHidden:(bool)hidden animated:(bool)animated;
@end

View File

@ -1,9 +0,0 @@
#import <Foundation/Foundation.h>
#import "TGPIPAblePlayerView.h"
@interface TGEmbedPlayerState : NSObject <TGPIPAblePlayerState>
+ (instancetype)stateWithPlaying:(bool)playing;
+ (instancetype)stateWithPlaying:(bool)playing duration:(NSTimeInterval)duration position:(NSTimeInterval)position downloadProgress:(CGFloat)downloadProgress buffering:(bool)buffering;
@end

View File

@ -1,105 +0,0 @@
#import <UIKit/UIKit.h>
#import <LegacyComponents/LegacyComponents.h>
#import <WebKit/WebKit.h>
#import <SSignalKit/SSignalKit.h>
#import <LegacyComponents/TGEmbedPlayerControls.h>
#import <LegacyComponents/TGMessageImageViewOverlayView.h>
#import <LegacyComponents/TGPIPAblePlayerView.h>
@class TGEmbedPlayerView;
@protocol TGEmbedPlayerWrapperView <NSObject>
- (void)reattachPlayerView;
- (void)reattachPlayerView:(TGEmbedPlayerView *)playerView;
@end
@interface TGEmbedPlayerView : UIView <TGPIPAblePlayerView>
{
TGWebPageMediaAttachment *_webPage;
TGMessageImageViewOverlayView *_overlayView;
CGSize _embedSize;
}
@property (nonatomic, readonly) TGEmbedPlayerState *state;
@property (nonatomic, readonly) TGEmbedPlayerControls *controlsView;
@property (nonatomic, readonly) UIView *dimWrapperView;
@property (nonatomic, assign) bool disableWatermarkAction;
@property (nonatomic, assign) bool inhibitFullscreenButton;
@property (nonatomic, assign) UIRectCorner roundCorners;
@property (nonatomic, assign) bool disallowAutoplay;
@property (nonatomic, assign) bool disallowPIP;
@property (nonatomic, assign) bool disableControls;
@property (nonatomic, assign) CGRect initialFrame;
@property (nonatomic, copy) void (^onWatermarkAction)(void);
@property (nonatomic, copy) void (^requestFullscreen)(NSTimeInterval duration);
@property (nonatomic, copy) void (^onMetadataLoaded)(NSString *title, NSString *subtitle);
@property (nonatomic, copy) void (^onBeganLoading)(void);
@property (nonatomic, copy) void (^onBeganPlaying)(void);
@property (nonatomic, copy) void (^onRealLoadProgress)(CGFloat progress, NSTimeInterval duration);
- (instancetype)initWithWebPageAttachment:(TGWebPageMediaAttachment *)webPage;
- (instancetype)initWithWebPageAttachment:(TGWebPageMediaAttachment *)webPage thumbnailSignal:(SSignal *)thumbnailSignal;
- (instancetype)initWithWebPageAttachment:(TGWebPageMediaAttachment *)webPage thumbnailSignal:(SSignal *)thumbnailSignal alternateCachePathSignal:(SSignal *)alternateCachePathSignal;
- (void)setupWithEmbedSize:(CGSize)embedSize;
- (void)setDimmed:(bool)dimmed animated:(bool)animated shouldDelay:(bool)shouldDelay;
- (void)setCoverImage:(UIImage *)image;
- (void)pauseVideo:(bool)manually;
- (void)updateState:(TGEmbedPlayerState *)state;
- (void)hideControls;
- (void)enterFullscreen:(NSTimeInterval)duration;
- (void)enterPictureInPicture:(TGEmbedPIPCorner)corner;
- (void)_onPageReady;
- (void)_didBeginPlayback;
- (void)_onPanelAppearance;
- (void)_watermarkAction;
- (void)_openWebPage:(NSURL *)url;
- (bool)_scaleViewToMaxSize;
- (void)onLockInPlace;
- (bool)_useFakeLoadingProgress;
- (void)setLoadProgress:(CGFloat)value duration:(NSTimeInterval)duration;
- (void)setDimmed:(bool)dimmed animated:(bool)animated;
- (TGEmbedPlayerControlsType)_controlsType;
- (void)_evaluateJS:(NSString *)jsString completion:(void (^)(NSString *))completion;
- (NSURL *)_embedURL;
- (NSString *)_embedHTML;
- (NSURL *)_baseURL;
- (void)_notifyOfCallbackURL:(NSURL *)url;
- (void)_setupUserScripts:(WKUserContentController *)contentController;
- (bool)_applyViewportUserScript;
- (UIView *)_webView;
- (CGFloat)_compensationEdges;
- (void)_cleanWebView;
- (SSignal *)loadProgress;
+ (bool)_supportsWebPage:(TGWebPageMediaAttachment *)webPage;
+ (bool)hasNativeSupportForX:(TGWebPageMediaAttachment *)webPage;
+ (Class)playerViewClassForWebPage:(TGWebPageMediaAttachment *)webPage onlySpecial:(bool)onlySpecial;
+ (TGEmbedPlayerView *)makePlayerViewForWebPage:(TGWebPageMediaAttachment *)webPage thumbnailSignal:(SSignal *)signal;
@end

View File

@ -1,32 +0,0 @@
#import <LegacyComponents/ActionStage.h>
#import <AssetsLibrary/AssetsLibrary.h>
#ifdef __cplusplus
extern "C" {
#endif
void dispatchOnAssetsProcessingQueue(dispatch_block_t block);
void sharedAssetsLibraryRetain();
void sharedAssetsLibraryRelease();
#ifdef __cplusplus
}
#endif
@protocol TGImagePickerControllerDelegate;
@interface TGImagePickerController : NSObject
+ (id)sharedAssetsLibrary;
+ (id)preloadLibrary;
+ (void)loadAssetWithUrl:(NSURL *)url completion:(void (^)(ALAsset *asset))completion;
+ (void)storeImageAsset:(NSData *)data;
@end
@protocol TGImagePickerControllerDelegate <NSObject>
- (void)imagePickerController:(TGImagePickerController *)imagePicker didFinishPickingWithAssets:(NSArray *)assets;
@end

View File

@ -1,27 +0,0 @@
#import <UIKit/UIKit.h>
#import <LegacyComponents/LegacyComponentsContext.h>
@protocol TGLegacyCameraControllerDelegate <NSObject>
@optional
- (void)legacyCameraControllerCapturedVideoWithTempFilePath:(NSString *)tempVideoFilePath fileSize:(int32_t)fileSize previewImage:(UIImage *)previewImage duration:(NSTimeInterval)duration dimensions:(CGSize)dimenstions assetUrl:(NSString *)assetUrl;
- (void)legacyCameraControllerCompletedWithExistingMedia:(id)media;
- (void)legacyCameraControllerCompletedWithNoResult;
- (void)legacyCameraControllerCompletedWithDocument:(NSURL *)fileUrl fileName:(NSString *)fileName mimeType:(NSString *)mimeType;
@end
@interface TGLegacyCameraController : UIImagePickerController
@property (nonatomic, copy) void (^finishedWithImage)(UIImage *);
@property (nonatomic, weak) id<TGLegacyCameraControllerDelegate> completionDelegate;
@property (nonatomic) bool storeCapturedAssets;
@property (nonatomic) bool isInDocumentMode;
@property (nonatomic) bool avatarMode;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context;
@end

View File

@ -1,7 +0,0 @@
#import <UIKit/UIKit.h>
@interface TGModernGalleryEmbeddedStickersHeaderView : UIView
@property (nonatomic, copy) void (^showEmbeddedStickers)();
@end

View File

@ -20,7 +20,7 @@
@property (nonatomic, readonly) bool isTracking;
@property (nonatomic, readonly) bool isAnimating;
- (instancetype)initWithOriginalSize:(CGSize)originalSize screenSize:(CGSize)screenSize fullPreviewView:(PGPhotoEditorView *)fullPreviewView;
- (instancetype)initWithOriginalSize:(CGSize)originalSize screenSize:(CGSize)screenSize fullPreviewView:(PGPhotoEditorView *)fullPreviewView fullPaintingView:(UIImageView *)fullPaintingView;
- (void)setSnapshotImage:(UIImage *)image;
- (void)setSnapshotView:(UIView *)snapshotView;

View File

@ -1,60 +0,0 @@
#import <UIKit/UIKit.h>
typedef enum
{
TGStickerKeyboardViewDefaultStyle,
TGStickerKeyboardViewDarkBlurredStyle,
TGStickerKeyboardViewPaintStyle,
TGStickerKeyboardViewPaintDarkStyle
} TGStickerKeyboardViewStyle;
@interface TGStickerKeyboardPallete : NSObject
@property (nonatomic, readonly) UIColor *backgroundColor;
@property (nonatomic, readonly) UIColor *separatorColor;
@property (nonatomic, readonly) UIColor *selectionColor;
@property (nonatomic, readonly) UIImage *gifIcon;
@property (nonatomic, readonly) UIImage *trendingIcon;
@property (nonatomic, readonly) UIImage *favoritesIcon;
@property (nonatomic, readonly) UIImage *recentIcon;
@property (nonatomic, readonly) UIImage *settingsIcon;
@property (nonatomic, readonly) UIImage *badge;
@property (nonatomic, readonly) UIColor *badgeTextColor;
+ (instancetype)palleteWithBackgroundColor:(UIColor *)backgroundColor separatorColor:(UIColor *)separatorColor selectionColor:(UIColor *)selectionColor gifIcon:(UIImage *)gifIcon trendingIcon:(UIImage *)trendingIcon favoritesIcon:(UIImage *)favoritesIcon recentIcon:(UIImage *)recentIcon settingsIcon:(UIImage *)settingsIcon badge:(UIImage *)badge badgeTextColor:(UIColor *)badgeTextColor;
@end
@interface TGStickerKeyboardTabPanel : UIView
@property (nonatomic, copy) void (^currentStickerPackIndexChanged)(NSUInteger);
@property (nonatomic, copy) void (^navigateToGifs)();
@property (nonatomic, copy) void (^navigateToTrendingFirst)();
@property (nonatomic, copy) void (^navigateToTrendingLast)();
@property (nonatomic, copy) void (^openSettings)();
@property (nonatomic, copy) void (^toggleExpanded)(void);
@property (nonatomic, copy) void (^expandInteraction)(CGFloat offset);
@property (nonatomic, strong) TGStickerKeyboardPallete *pallete;
@property (nonatomic, assign) UIEdgeInsets safeAreaInset;
- (instancetype)initWithFrame:(CGRect)frame style:(TGStickerKeyboardViewStyle)style;
- (void)setStickerPacks:(NSArray *)stickerPacks showRecent:(bool)showRecent showFavorite:(bool)showFavorite showGroup:(bool)showGroup showGroupLast:(bool)showGroupLast showGifs:(bool)showGifs showTrendingFirst:(bool)showTrendingFirst showTrendingLast:(bool)showTrendingLast;
- (void)setCurrentStickerPackIndex:(NSUInteger)currentStickerPackIndex animated:(bool)animated;
- (void)setCurrentGifsModeSelected;
- (void)setCurrentTrendingModeSelected;
- (void)setTrendingStickersBadge:(NSString *)badge;
- (void)setAvatarUrl:(NSString *)avatarUrl peerId:(int64_t)peerId title:(NSString *)title;
- (void)setInnerAlpha:(CGFloat)alpha;
- (void)setExpanded:(bool)expanded;
- (void)updateExpanded:(bool)expanded;
- (void)setHidden:(bool)hidden animated:(bool)animated;
@end

View File

@ -1,12 +0,0 @@
#import "TGEmbedPlayerView.h"
@interface TGEmbedCoubPlayerView : TGEmbedPlayerView
+ (NSString *)_coubVideoIdFromText:(NSString *)text;
+ (NSDictionary *)coubJSONByPermalink:(NSString *)permalink;
+ (void)setCoubJSON:(NSDictionary *)json forPermalink:(NSString *)permalink;
- (void)setVideoPath:(NSString *)videoPath;
@end

View File

@ -1,561 +0,0 @@
#import "TGEmbedCoubPlayerView.h"
#import "TGEmbedPlayerState.h"
#import "LegacyComponentsInternal.h"
#import <LegacyComponents/TGMediaAssetImageSignals.h>
#import <SSignalKit/SSignalKit.h>
#import "CBPlayerView.h"
#import "CBCoubAsset.h"
#import "CBCoubPlayer.h"
#import "CBCoubNew.h"
#import <LegacyComponents/PSLMDBKeyValueStore.h>
@interface TGEmbedCoubURLTaskAdapter : NSObject <NSURLSessionTaskDelegate>
{
NSURLSession *_session;
}
@property (nonatomic, copy) void (^redirectUrl)(NSString *);
- (instancetype)initWithURL:(NSString *)url;
- (void)invalidate;
@end
@interface TGEmbedCoubPlayerView () <CBCoubPlayerDelegate>
{
NSString *_permalink;
bool _started;
UIImage *_coverImage;
CBPlayerView *_playerView;
CBCoubPlayer *_coubPlayer;
SVariable *_videoPath;
SDisposableSet *_disposables;
id<CBCoubAsset> _asset;
}
@end
@implementation TGEmbedCoubPlayerView
- (instancetype)initWithWebPageAttachment:(TGWebPageMediaAttachment *)webPage thumbnailSignal:(SSignal *)thumbnailSignal alternateCachePathSignal:(SSignal *)alternateCachePathSignal
{
self = [super initWithWebPageAttachment:webPage thumbnailSignal:thumbnailSignal alternateCachePathSignal:alternateCachePathSignal];
if (self != nil)
{
_permalink = [TGEmbedCoubPlayerView _coubVideoIdFromText:webPage.embedUrl];
_disposables = [[SDisposableSet alloc] init];
TGDocumentMediaAttachment *document = webPage.document;
NSString *videoPath = nil;
if ([document.mimeType isEqualToString:@"video/mp4"])
{
if (document.localDocumentId != 0) {
videoPath = [[[LegacyComponentsGlobals provider] localDocumentDirectoryForLocalDocumentId:document.localDocumentId version:document.version] stringByAppendingPathComponent:[document safeFileName]];
} else {
videoPath = [[[LegacyComponentsGlobals provider] localDocumentDirectoryForDocumentId:document.documentId version:document.version] stringByAppendingPathComponent:[document safeFileName]];
}
}
__weak TGEmbedCoubPlayerView *weakSelf = self;
if (videoPath != nil && [[NSFileManager defaultManager] fileExistsAtPath:videoPath isDirectory:NULL])
{
_videoPath = [[SVariable alloc] init];
[_videoPath set:[SSignal single:[NSURL fileURLWithPath:videoPath]]];
if (thumbnailSignal == nil)
{
[_disposables add:[[[TGMediaAssetImageSignals videoThumbnailForAVAsset:[AVURLAsset assetWithURL:[NSURL fileURLWithPath:videoPath]] size:CGSizeMake(480, 480) timestamp:CMTimeMake(1, 100)] deliverOn:[SQueue mainQueue]] startWithNext:^(id next)
{
__strong TGEmbedCoubPlayerView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if ([next isKindOfClass:[UIImage class]])
[strongSelf setCoverImage:next];
}]];
}
}
else if (alternateCachePathSignal != nil)
{
_videoPath = [[SVariable alloc] init];
[_disposables add:[alternateCachePathSignal startWithNext:^(NSString *path)
{
__strong TGEmbedCoubPlayerView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:NULL])
{
if (path.pathExtension.length == 0)
{
[[NSFileManager defaultManager] createSymbolicLinkAtPath:[path stringByAppendingString:@".mov"] withDestinationPath:[path lastPathComponent] error:nil];
path = [path stringByAppendingString:@".mov"];
}
NSURL *url = [NSURL fileURLWithPath:path];
[strongSelf->_videoPath set:[SSignal single:url]];
if (thumbnailSignal == nil)
{
[strongSelf->_disposables add:[[[TGMediaAssetImageSignals videoThumbnailForAVAsset:[AVURLAsset assetWithURL:url] size:CGSizeMake(480, 480) timestamp:CMTimeMake(1, 100)] deliverOn:[SQueue mainQueue]] startWithNext:^(id next)
{
__strong TGEmbedCoubPlayerView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if ([next isKindOfClass:[UIImage class]])
[strongSelf setCoverImage:next];
}]];
}
}
else
{
[strongSelf->_videoPath set:[SSignal single:nil]];
}
}]];
}
self.controlsView.watermarkImage = TGComponentsImageNamed(@"CoubWatermark");
self.controlsView.watermarkOffset = CGPointMake(12.0f, 12.0f);
}
return self;
}
- (void)dealloc
{
[_disposables dispose];
}
- (bool)supportsPIP
{
return false;
}
- (void)_watermarkAction
{
[super _watermarkAction];
if (self.onWatermarkAction != nil)
self.onWatermarkAction();
NSString *permalink = _permalink;
NSString *coubId = nil;
if ([_asset isKindOfClass:[CBCoubNew class]])
coubId = ((CBCoubNew *)_asset).coubID;
NSURL *appUrl = [[NSURL alloc] initWithString:[[NSString alloc] initWithFormat:@"coub://view/%@", coubId]];
if ([[LegacyComponentsGlobals provider] canOpenURL:appUrl])
{
[[LegacyComponentsGlobals provider] openURL:appUrl];
return;
}
NSURL *webUrl = [NSURL URLWithString:[NSString stringWithFormat:@"https://coub.com/view/%@", permalink]];
[[LegacyComponentsGlobals provider] openURL:webUrl];
}
- (void)setupWithEmbedSize:(CGSize)embedSize
{
[super setupWithEmbedSize:embedSize];
_playerView = [[CBPlayerView alloc] initWithFrame:[self _webView].bounds];
[[self _webView].superview insertSubview:_playerView aboveSubview:[self _webView]];
_coubPlayer = [[CBCoubPlayer alloc] initWithVideoLayer:(AVPlayerLayer *)_playerView.videoPlayerView.layer];
_coubPlayer.withoutAudio = false;
_coubPlayer.delegate = self;
[self _cleanWebView];
[self setDimmed:true animated:false shouldDelay:false];
[self initializePlayer];
[self setLoadProgress:0.01f duration:0.01];
}
- (void)playVideo
{
[_coubPlayer resume];
}
- (void)pauseVideo:(bool)manually
{
[super pauseVideo:manually];
[_coubPlayer pause];
}
- (void)_onPageReady
{
}
- (void)_didBeginPlayback
{
[super _didBeginPlayback];
[self setDimmed:false animated:true shouldDelay:false];
}
- (TGEmbedPlayerControlsType)_controlsType
{
return TGEmbedPlayerControlsTypeSimple;
}
- (NSString *)_embedHTML
{
NSError *error = nil;
NSString *path = TGComponentsPathForResource(@"DefaultPlayer", @"html");
NSString *embedHTMLTemplate = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
if (error != nil)
{
TGLegacyLog(@"[CoubEmbedPlayer]: Received error rendering template: %@", error);
return nil;
}
NSString *embedHTML = [NSString stringWithFormat:embedHTMLTemplate, @"about:blank"];
return embedHTML;
}
- (NSURL *)_baseURL
{
return [NSURL URLWithString:@"https://coub.com/"];
}
#pragma mark -
- (bool)_useFakeLoadingProgress
{
return false;
}
+ (SSignal *)webHeadersRequestSignal:(NSString *)url
{
return [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
{
TGEmbedCoubURLTaskAdapter *adapter = [[TGEmbedCoubURLTaskAdapter alloc] initWithURL:url];
adapter.redirectUrl = ^(NSString *redirectUrl)
{
[subscriber putNext:redirectUrl];
[subscriber putCompletion];
};
return [[SBlockDisposable alloc] initWithBlock:^
{
[adapter invalidate];
}];
}];
}
- (void)initializePlayer
{
NSString *url = [NSString stringWithFormat:@"http://coub.com/api/v2/coubs/%@", _permalink];
__weak TGEmbedCoubPlayerView *weakSelf = self;
SSignal *cachedSignal = [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
{
__strong TGEmbedCoubPlayerView *strongSelf = weakSelf;
if (strongSelf == nil)
{
[subscriber putCompletion];
return nil;
}
NSDictionary *json = [TGEmbedCoubPlayerView coubJSONByPermalink:strongSelf->_permalink];
if (json != nil)
{
[subscriber putNext:json];
[subscriber putCompletion];
}
else
{
[subscriber putError:nil];
}
return nil;
}];
SSignal *dataSignal = [[cachedSignal mapToSignal:^SSignal *(NSDictionary *json)
{
return [[SSignal single:@{ @"json": json, @"cached": @true }] delay:0.2 onQueue:[SQueue mainQueue]];
}] catch:^SSignal *(__unused id error)
{
return [[[LegacyComponentsGlobals provider] jsonForHttpLocation:url] map:^id(NSDictionary *json)
{
return @{ @"json": json, @"cached": @false };
}];
}];
SSignal *locationSignal = [dataSignal mapToSignal:^SSignal *(NSDictionary *data)
{
NSDictionary *attributes = data[@"json"];
NSString *remoteVideoLocation = nil;
NSDictionary *fileVersions = attributes[@"file_versions"];
if (fileVersions != nil)
remoteVideoLocation = fileVersions[@"iphone"][@"url"];
if (!remoteVideoLocation || [remoteVideoLocation isKindOfClass:[NSNull class]])
remoteVideoLocation = attributes[@"file"];
if (!remoteVideoLocation || [remoteVideoLocation isKindOfClass:[NSNull class]])
remoteVideoLocation = fileVersions[@"html5"][@"video"][@"med"][@"url"];
if ([remoteVideoLocation rangeOfString:@"getvideo?"].location != NSNotFound)
{
NSString *location = [remoteVideoLocation stringByReplacingOccurrencesOfString:@"//coub" withString:@"https://coub"];
return [[TGEmbedCoubPlayerView webHeadersRequestSignal:location] map:^id(id result) {
NSMutableDictionary *updatedJson = [attributes mutableCopy];
updatedJson[@"explicitVideoLocation"] = result;
return updatedJson;
}];
}
else
{
return [SSignal single:attributes];
}
}];
[_disposables add:[[locationSignal deliverOn:[SQueue mainQueue]] startWithNext:^(NSDictionary *data)
{
__strong TGEmbedCoubPlayerView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
SSignal *signal = [SSignal single:nil];
if (strongSelf->_videoPath != nil)
signal = strongSelf->_videoPath.signal;
[strongSelf->_disposables add:[signal startWithNext:^(NSURL *videoPath)
{
__strong TGEmbedCoubPlayerView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
CBCoubNew *coub = [CBCoubNew coubWithAttributes:data];
coub.customLocalVideoFileURL = videoPath;
strongSelf->_asset = coub;
if ([coub isKindOfClass:[CBCoubNew class]])
{
CBCoubNew *coubNew = (CBCoubNew *)coub;
if (strongSelf.onMetadataLoaded != nil)
strongSelf.onMetadataLoaded(coubNew.title, coubNew.author.name);
}
[strongSelf->_coubPlayer playAsset:strongSelf->_asset];
if (![data[@"cached"] boolValue])
[TGEmbedCoubPlayerView setCoubJSON:data[@"json"] forPermalink:strongSelf->_permalink];
}]];
}]];
}
- (void)setVideoPath:(NSString *)videoPath {
[_videoPath set:[SSignal single:[NSURL fileURLWithPath:videoPath]]];
}
- (void)playerReadyToPlay:(CBCoubPlayer *)__unused player
{
[self setLoadProgress:1.0f duration:0.2];
[_playerView play];
if (self.onRealLoadProgress != nil)
self.onRealLoadProgress(1.0f, 0.2);
}
- (void)playerDidStartPlaying:(CBCoubPlayer *)__unused player
{
if (!_started)
{
_started = true;
[self _didBeginPlayback];
TGEmbedPlayerState *state = [TGEmbedPlayerState stateWithPlaying:true];
[self updateState:state];
}
}
- (void)player:(CBCoubPlayer *)__unused player didReachProgressWhileDownloading:(float)progress
{
[self setLoadProgress:progress duration:0.3];
if (self.onRealLoadProgress != nil)
self.onRealLoadProgress(progress, 0.3);
}
- (void)playerDidPause:(CBCoubPlayer *)__unused player withUserAction:(BOOL)isUserAction
{
if (!isUserAction)
return;
TGEmbedPlayerState *state = [TGEmbedPlayerState stateWithPlaying:false];
[self updateState:state];
}
- (void)playerDidResume:(CBCoubPlayer *)__unused player
{
TGEmbedPlayerState *state = [TGEmbedPlayerState stateWithPlaying:true];
[self updateState:state];
}
- (void)playerDidFail:(CBCoubPlayer *)__unused player error:(NSError *)error
{
TGLegacyLog(@"[CoubPlayer] ERROR: %@", error.localizedDescription);
}
- (void)playerDidStop:(CBCoubPlayer *)__unused player
{
}
#pragma mark -
+ (NSString *)_coubVideoIdFromText:(NSString *)text
{
NSMutableArray *prefixes = [NSMutableArray arrayWithArray:@
[
@"http://coub.com/v/",
@"https://coub.com/v/",
@"http://coub.com/embed/",
@"https://coub.com/embed/",
@"http://coub.com/view/",
@"https://coub.com/view/"
]];
NSString *prefix = nil;
for (NSString *p in prefixes)
{
if ([text hasPrefix:p])
{
prefix = p;
break;
}
}
if (prefix != nil)
{
NSString *suffix = [text substringFromIndex:prefix.length];
for (int i = 0; i < (int)suffix.length; i++)
{
unichar c = [suffix characterAtIndex:i];
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '=' || c == '&' || c == '#'))
return nil;
}
return suffix;
}
return nil;
}
+ (bool)_supportsWebPage:(TGWebPageMediaAttachment *)webPage
{
NSString *url = webPage.embedUrl;
return ([url hasPrefix:@"http://coub.com/embed/"] || [url hasPrefix:@"https://coub.com/embed/"]);
}
+ (PSLMDBKeyValueStore *)coubMetaStore
{
static PSLMDBKeyValueStore *store = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
NSString *documentsPath = [[LegacyComponentsGlobals provider] dataStoragePath];
store = [PSLMDBKeyValueStore storeWithPath:[documentsPath stringByAppendingPathComponent:@"misc/coubmetadata"] size:1 * 1024 * 1024];
});
return store;
}
+ (NSDictionary *)coubJSONByPermalink:(NSString *)permalink
{
if (permalink.length == 0)
return nil;
__block NSData *jsonData = nil;
[[self coubMetaStore] readInTransaction:^(id<PSKeyValueReader> reader)
{
NSMutableData *keyData = [[NSMutableData alloc] init];
int8_t keyspace = 0;
[keyData appendBytes:&keyspace length:1];
[keyData appendData:[permalink dataUsingEncoding:NSUTF8StringEncoding]];
PSData key = {.data = (uint8_t *)keyData.bytes, .length = keyData.length};
PSData value;
if ([reader readValueForRawKey:&key value:&value])
jsonData = [[NSData alloc] initWithBytes:value.data length:value.length];
}];
if (jsonData.length > 0)
{
@try
{
NSDictionary *json = [NSKeyedUnarchiver unarchiveObjectWithData:jsonData];
return json;
}
@catch(NSException *)
{
}
}
return nil;
}
+ (void)setCoubJSON:(NSDictionary *)json forPermalink:(NSString *)permalink
{
if (permalink.length == 0 || json.allKeys.count == 0)
return;
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:json];
if (data.length == 0)
return;
[[self coubMetaStore] readWriteInTransaction:^(id<PSKeyValueReader,PSKeyValueWriter> writer)
{
NSMutableData *keyData = [[NSMutableData alloc] init];
int8_t keyspace = 0;
[keyData appendBytes:&keyspace length:1];
[keyData appendData:[permalink dataUsingEncoding:NSUTF8StringEncoding]];
PSData key = {.data = (uint8_t *)keyData.bytes, .length = keyData.length};
PSData value = {.data = (uint8_t *)data.bytes, .length = data.length};
[writer writeValueForRawKey:key.data keyLength:key.length value:value.data valueLength:value.length];
}];
}
@end
@implementation TGEmbedCoubURLTaskAdapter
- (instancetype)initWithURL:(NSString *)url
{
self = [super init];
if (self != nil)
{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
[[_session dataTaskWithURL:[NSURL URLWithString:url] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {}] resume];
}
return self;
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler
{
if (self.redirectUrl != nil)
self.redirectUrl(response.allHeaderFields[@"Location"]);
}
- (void)invalidate
{
[_session invalidateAndCancel];
}
@end

View File

@ -1,5 +0,0 @@
#import "TGEmbedPlayerView.h"
@interface TGEmbedInstagramPlayerView : TGEmbedPlayerView
@end

View File

@ -1,113 +0,0 @@
#import "TGEmbedInstagramPlayerView.h"
#import "TGEmbedPlayerState.h"
#import "LegacyComponentsInternal.h"
NSString *const TGInstagramPlayerCallbackOnPlayback = @"onPlayback";
@interface TGEmbedInstagramPlayerView ()
{
NSString *_url;
bool _playing;
bool _started;
}
@end
@implementation TGEmbedInstagramPlayerView
- (instancetype)initWithWebPageAttachment:(TGWebPageMediaAttachment *)webPage thumbnailSignal:(SSignal *)thumbnailSignal alternateCachePathSignal:(SSignal *)alternateCachePathSignal
{
self = [super initWithWebPageAttachment:webPage thumbnailSignal:thumbnailSignal alternateCachePathSignal:alternateCachePathSignal];
if (self != nil)
{
_url = webPage.embedUrl;
}
return self;
}
- (void)playVideo
{
_playing = true;
[self _evaluateJS:@"play()" completion:nil];
TGEmbedPlayerState *state = [TGEmbedPlayerState stateWithPlaying:_playing];
[self updateState:state];
}
- (void)pauseVideo:(bool)manually
{
[super pauseVideo:manually];
_playing = false;
[self _evaluateJS:@"pause();" completion:nil];
TGEmbedPlayerState *state = [TGEmbedPlayerState stateWithPlaying:_playing];
[self updateState:state];
}
- (TGEmbedPlayerControlsType)_controlsType
{
return TGEmbedPlayerControlsTypeSimple;
}
- (void)_onPageReady
{
}
- (void)_didBeginPlayback
{
[super _didBeginPlayback];
[self setDimmed:false animated:true shouldDelay:false];
}
- (void)_notifyOfCallbackURL:(NSURL *)url
{
NSString *action = url.host;
NSString *query = url.query;
NSString *data;
if (query != nil)
data = [query componentsSeparatedByString:@"="][1];
if ([action isEqual:TGInstagramPlayerCallbackOnPlayback])
{
if (!_started)
{
_started = true;
[self _didBeginPlayback];
}
_playing = true;
TGEmbedPlayerState *state = [TGEmbedPlayerState stateWithPlaying:true];
[self updateState:state];
}
}
- (NSString *)_embedHTML
{
NSError *error = nil;
NSString *path = TGComponentsPathForResource(@"InstagramPlayer", @"html");
NSString *embedHTMLTemplate = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
if (error != nil)
{
TGLegacyLog(@"[InstagramEmbedPlayer]: Received error rendering template: %@", error);
return nil;
}
NSString *embedHTML = [NSString stringWithFormat:embedHTMLTemplate, _url];
return embedHTML;
}
- (NSURL *)_baseURL
{
return [NSURL URLWithString:@"https://instagram.com/"];
}
+ (bool)_supportsWebPage:(TGWebPageMediaAttachment *)webPage
{
NSURL *url = [NSURL URLWithString:webPage.embedUrl];
return ([url.host containsString:@"cdninstagram"] && [url.pathExtension isEqualToString:@"mp4"]);
}
@end

View File

@ -1,8 +0,0 @@
#import <UIKit/UIKit.h>
@interface TGEmbedPIPScrubber : UIView
@property (nonatomic, assign) CGFloat playProgress;
@property (nonatomic, assign) CGFloat downloadProgress;
@end

View File

@ -1,67 +0,0 @@
#import "TGEmbedPIPScrubber.h"
#import "LegacyComponentsInternal.h"
@interface TGEmbedPIPScrubber ()
{
UIVisualEffectView *_playProgressView;
UIVisualEffectView *_remainingProgressView;
UIView *_downloadProgressView;
}
@end
@implementation TGEmbedPIPScrubber
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil)
{
self.userInteractionEnabled = false;
UIVisualEffect *lightBlurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleExtraLight];
_playProgressView = [[UIVisualEffectView alloc] initWithEffect:lightBlurEffect];
[self addSubview:_playProgressView];
_downloadProgressView = [[UIView alloc] init];
_downloadProgressView.backgroundColor = UIColorRGBA(0x000000, 0.45f);
[self addSubview:_downloadProgressView];
UIVisualEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
_remainingProgressView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
[self addSubview:_remainingProgressView];
}
return self;
}
- (void)setPlayProgress:(CGFloat)playProgress
{
if (isnan(playProgress))
playProgress = 0.0f;
_playProgress = playProgress;
[self setNeedsLayout];
}
- (void)setDownloadProgress:(CGFloat)downloadProgress
{
_downloadProgress = downloadProgress;
[self setNeedsLayout];
}
- (void)layoutSubviews
{
[super layoutSubviews];
CGFloat playedWidth = floor(self.frame.size.width * _playProgress);
if (isnan(playedWidth))
playedWidth = 0.0f;
_playProgressView.frame = CGRectMake(0, 0, playedWidth, self.frame.size.height);
_remainingProgressView.frame = CGRectMake(playedWidth, 0, self.frame.size.width - playedWidth, self.frame.size.height);
CGFloat downloadedWidth = MAX(0.0f, playedWidth - floor(self.frame.size.width * _downloadProgress));
_downloadProgressView.frame = CGRectMake(playedWidth, 0, downloadedWidth, self.frame.size.height);
}
@end

View File

@ -1,689 +0,0 @@
#import "TGEmbedPlayerControls.h"
#import "LegacyComponentsInternal.h"
#import "TGFont.h"
#import <SSignalKit/SSignalKit.h>
#import <LegacyComponents/TGTimerTarget.h>
#import <LegacyComponents/TGModernButton.h>
#import <LegacyComponents/UIControl+HitTestEdgeInsets.h>
#import "TGEmbedPlayerScrubber.h"
#import "TGEmbedPlayerState.h"
const CGFloat TGEmbedPlayerControlsPanelHeight = 32.0f;
@interface TGEmbedPlayerControls ()
{
TGEmbedPlayerControlsType _type;
UIButton *_screenAreaButton;
UIView *_backgroundView;
UIView *_backgroundContentView;
TGModernButton *_playButton;
TGModernButton *_pauseButton;
UILabel *_positionLabel;
UILabel *_remainingLabel;
TGEmbedPlayerScrubber *_scrubber;
TGModernButton *_pictureInPictureButton;
UIView *_fullscreenButtonWrapper;
TGModernButton *_fullscreenButton;
UIView *_largePlayButtonBack;
TGModernButton *_largePlayButton;
UIButton *_watermarkView;
bool _watermarkDenyHiding;
bool _disabled;
bool _controlsHidden;
bool _panelHidden;
bool _animatingPanel;
bool _playing;
bool _hasPlaybackButton;
bool _showingLargeButton;
bool _wasPlayingBeforeScrubbing;
NSTimer *_hidePanelTimer;
}
@end
@implementation TGEmbedPlayerControls
- (instancetype)initWithFrame:(CGRect)frame type:(TGEmbedPlayerControlsType)type
{
self = [super initWithFrame:frame];
if (self != nil)
{
_type = type;
_panelHidden = true;
self.clipsToBounds = true;
_screenAreaButton = [[UIButton alloc] initWithFrame:self.bounds];
_screenAreaButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_screenAreaButton.exclusiveTouch = true;
[_screenAreaButton addTarget:self action:@selector(screenAreaPressed) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_screenAreaButton];
if (type == TGEmbedPlayerControlsTypeFull)
{
if (iosMajorVersion() >= 8)
{
UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
_backgroundView = effectView;
_backgroundContentView = effectView.contentView;
UIView *whiteView = [[UIView alloc] initWithFrame:effectView.bounds];
whiteView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
whiteView.backgroundColor = UIColorRGBA(0xffffff, 0.3f);
[effectView.contentView addSubview:whiteView];
}
else
{
_backgroundView = [[UIView alloc] initWithFrame:CGRectZero];
_backgroundContentView = _backgroundView;
}
[self addSubview:_backgroundView];
_pauseButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, 38, TGEmbedPlayerControlsPanelHeight)];
_pauseButton.exclusiveTouch = true;
[_pauseButton setImage:TGComponentsImageNamed(@"EmbedVideoPauseIcon") forState:UIControlStateNormal];
[_pauseButton addTarget:self action:@selector(pauseButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[_backgroundContentView addSubview:_pauseButton];
_playButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, 38, TGEmbedPlayerControlsPanelHeight)];
_playButton.exclusiveTouch = true;
[_playButton setImage:TGComponentsImageNamed(@"EmbedVideoPlayIcon") forState:UIControlStateNormal];
[_playButton addTarget:self action:@selector(playButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[_backgroundContentView addSubview:_playButton];
_positionLabel = [[UILabel alloc] initWithFrame:CGRectMake(24.0f, 0, 56.0f, TGEmbedPlayerControlsPanelHeight)];
_positionLabel.backgroundColor = [UIColor clearColor];
_positionLabel.font = TGSystemFontOfSize(13.0f);
_positionLabel.text = @"0:00";
_positionLabel.textAlignment = NSTextAlignmentCenter;
_positionLabel.textColor = UIColorRGB(0x302e2e);
_positionLabel.userInteractionEnabled = false;
[_backgroundContentView addSubview:_positionLabel];
_remainingLabel = [[UILabel alloc] initWithFrame:CGRectMake(frame.size.width - 56.0f, 0, 56, TGEmbedPlayerControlsPanelHeight)];
_remainingLabel.backgroundColor = [UIColor clearColor];
_remainingLabel.font = TGSystemFontOfSize(13.0f);
_remainingLabel.text = @"-0:00";
_remainingLabel.textAlignment = NSTextAlignmentCenter;
_remainingLabel.textColor = UIColorRGB(0x302e2e);
_remainingLabel.userInteractionEnabled = false;
[_backgroundContentView addSubview:_remainingLabel];
_pictureInPictureButton = [[TGModernButton alloc] initWithFrame:CGRectMake(frame.size.width - 45.0f, 0, 45.0f, TGEmbedPlayerControlsPanelHeight)];
_pictureInPictureButton.exclusiveTouch = true;
[_pictureInPictureButton setImage:TGComponentsImageNamed(@"EmbedVideoPIPIcon") forState:UIControlStateNormal];
[_pictureInPictureButton addTarget:self action:@selector(pictureInPictureButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[_backgroundContentView addSubview:_pictureInPictureButton];
__weak TGEmbedPlayerControls *weakSelf = self;
_scrubber = [[TGEmbedPlayerScrubber alloc] initWithFrame:CGRectZero];
_scrubber.onInteractionStart = ^
{
__strong TGEmbedPlayerControls *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (strongSelf->_playing)
{
strongSelf->_wasPlayingBeforeScrubbing = true;
if (strongSelf.pausePressed != nil)
strongSelf.pausePressed();
}
[strongSelf _invalidateTimer];
};
_scrubber.onSeek = ^(CGFloat position)
{
__strong TGEmbedPlayerControls *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (strongSelf.seekToPosition != nil)
strongSelf.seekToPosition(position);
};
_scrubber.onInteractionEnd = ^
{
__strong TGEmbedPlayerControls *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf _startTimerIfNeeded];
if (strongSelf->_wasPlayingBeforeScrubbing)
{
strongSelf->_wasPlayingBeforeScrubbing = false;
if (strongSelf.playPressed != nil)
strongSelf.playPressed();
}
};
[_scrubber setTintColor:UIColorRGB(0x2f2e2e)];
[_backgroundContentView addSubview:_scrubber];
}
if (type == TGEmbedPlayerControlsTypeSimple)
{
[self showLargePlayButton:false];
}
if (type == TGEmbedPlayerControlsTypeSimple || type == TGEmbedPlayerControlsTypeFull)
{
_fullscreenButtonWrapper = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 51.0f, 51.0f)];
_fullscreenButtonWrapper.alpha = 0.0f;
_fullscreenButtonWrapper.userInteractionEnabled = false;
[self addSubview:_fullscreenButtonWrapper];
if (iosMajorVersion() >= 8)
{
UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
effectView.contentView.backgroundColor = UIColorRGBA(0xffffff, 0.3f);
effectView.clipsToBounds = true;
effectView.frame = CGRectMake(12.0f, 12.0f, 27.0f, 27.0f);
effectView.layer.cornerRadius = 13.5f;
[_fullscreenButtonWrapper addSubview:effectView];
}
_fullscreenButton = [[TGModernButton alloc] initWithFrame:_fullscreenButtonWrapper.bounds];
_fullscreenButton.exclusiveTouch = true;
[_fullscreenButton setImage:TGComponentsImageNamed(@"EmbedVideoFullScreenIcon") forState:UIControlStateNormal];
[_fullscreenButton addTarget:self action:@selector(fullscreenButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[_fullscreenButtonWrapper addSubview:_fullscreenButton];
}
_watermarkView = [[UIButton alloc] init];
_watermarkView.alpha = 0.6f;
_watermarkView.adjustsImageWhenHighlighted = false;
_watermarkView.exclusiveTouch = true;
_watermarkView.hitTestEdgeInsets = UIEdgeInsetsMake(-12.0f, -12.0f, -12.0f, -12.0f);
[_watermarkView addTarget:self action:@selector(watermarkButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_watermarkView];
_watermarkDenyHiding = true;
}
return self;
}
- (void)showLargePlayButton:(bool)force
{
if (_largePlayButton != nil)
return;
if (iosMajorVersion() >= 8)
{
UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:effect];
effectView.alpha = 1.0f;
effectView.clipsToBounds = true;
effectView.frame = CGRectMake(0.0f, 0.0f, 72.0f, 72.0f);
effectView.layer.cornerRadius = 36.0f;
[self addSubview:effectView];
UIVisualEffectView *vibrancyView = [[UIVisualEffectView alloc] initWithEffect:[UIVibrancyEffect effectForBlurEffect:effect]];
vibrancyView.contentView.backgroundColor = [UIColor colorWithWhite:1.0f alpha:0.6f];
vibrancyView.frame = effectView.bounds;
[effectView.contentView addSubview:vibrancyView];
_largePlayButtonBack = effectView;
if (!force)
_largePlayButtonBack.hidden = true;
static dispatch_once_t onceToken;
static UIImage *largePlayIcon;
dispatch_once(&onceToken, ^
{
UIGraphicsBeginImageContextWithOptions(CGSizeMake(72, 72), false, 0.0f);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(ctx, [UIColor colorWithWhite:0.8f alpha:0.09f].CGColor);
CGContextFillRect(ctx, CGRectMake(0, 0, 72, 72));
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, 25.0f, 18.0f);
CGContextAddLineToPoint(ctx, 58.0f, 36.5f);
CGContextAddLineToPoint(ctx, 25.0f, 55.0f);
CGContextClosePath(ctx);
CGContextSetFillColorWithColor(ctx, [UIColor colorWithWhite:0.0f alpha:0.7f].CGColor);
CGContextFillPath(ctx);
largePlayIcon = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
});
_largePlayButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, 72, 72)];
[_largePlayButton setImage:largePlayIcon forState:UIControlStateNormal];
[_largePlayButton addTarget:self action:@selector(playButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[effectView.contentView addSubview:_largePlayButton];
_showingLargeButton = force;
}
}
- (void)setHidden:(bool)hidden animated:(bool)__unused animated
{
if (hidden == _controlsHidden)
return;
_controlsHidden = hidden;
_backgroundView.hidden = hidden;
_fullscreenButtonWrapper.hidden = hidden;
if (_type == TGEmbedPlayerControlsTypeSimple)
_largePlayButtonBack.hidden = hidden || _playing;
if (hidden)
[self setPanelHidden:true animated:false];
}
#pragma mark -
- (void)watermarkButtonPressed
{
if (self.watermarkPressed != nil)
self.watermarkPressed();
}
- (void)setWatermarkHidden:(bool)hidden
{
_watermarkView.hidden = hidden;
}
- (void)setWatermarkPrerenderedOpacity:(bool)watermarkPrerenderedOpacity
{
_watermarkPrerenderedOpacity = watermarkPrerenderedOpacity;
if (watermarkPrerenderedOpacity && _watermarkView.alpha > FLT_EPSILON)
_watermarkView.alpha = 1.0f;
}
- (void)setInternalWatermarkHidden:(bool)hidden animated:(bool)animated
{
if (_type != TGEmbedPlayerControlsTypeFull)
return;
CGFloat visibleAlpha = _watermarkPrerenderedOpacity ? 1.0f : 0.6f;
if (animated)
{
[UIView animateWithDuration:0.25 animations:^
{
_watermarkView.alpha = hidden ? 0.0f : visibleAlpha;
}];
}
else
{
_watermarkView.alpha = hidden ? 0.0f : visibleAlpha;
}
}
- (UIImage *)watermarkImage
{
return [_watermarkView imageForState:UIControlStateNormal];
}
- (void)setWatermarkImage:(UIImage *)watermarkImage
{
[_watermarkView setImage:watermarkImage forState:UIControlStateNormal];
[_watermarkView sizeToFit];
[self setNeedsLayout];
}
- (void)setWatermarkOffset:(CGPoint)watermarkOffset
{
_watermarkOffset = watermarkOffset;
[self setNeedsLayout];
}
- (void)setWatermarkPosition:(TGEmbedPlayerWatermarkPosition)watermarkPosition
{
_watermarkPosition = watermarkPosition;
[self setNeedsLayout];
}
#pragma mark -
- (void)setState:(TGEmbedPlayerState *)state
{
_playing = state.isPlaying;
if (_type == TGEmbedPlayerControlsTypeFull)
{
_playButton.hidden = _playing;
_pauseButton.hidden = !_playing;
NSInteger position = (NSInteger)state.position;
NSString *positionString = [[NSString alloc] initWithFormat:@"%d:%02d", (int)position / 60, (int)position % 60];
_positionLabel.text = positionString;
NSInteger remaining = (NSInteger)(state.duration - state.position);
NSString *remainingString = [[NSString alloc] initWithFormat:@"-%d:%02d", (int)remaining / 60, (int)remaining % 60];
_remainingLabel.text = remainingString;
CGFloat fractPosition = state.position / MAX(state.duration, 0.001);
if (state.duration <= 0.01 || isnan(state.downloadProgress))
{
_remainingLabel.hidden = true;
_scrubber.hidden = true;
}
else
{
_remainingLabel.hidden = false;
_scrubber.hidden = false;
}
_positionLabel.hidden = (state.position < 0.0);
if (!_scrubber.hidden)
{
[_scrubber setDownloadProgress:state.downloadProgress];
[_scrubber setPosition:fractPosition];
}
if (!_watermarkDenyHiding)
[self setInternalWatermarkHidden:_playing animated:true];
if (_playing)
{
_largePlayButtonBack.hidden = true;
_showingLargeButton = false;
}
if (!_playing && !_animatingPanel && _panelHidden && !_showingLargeButton)
{
[self setPanelHidden:false animated:true];
}
else
{
if (!_playing && _hidePanelTimer != nil)
[self _invalidateTimer];
else
[self _startTimerIfNeeded];
}
}
else if (_type == TGEmbedPlayerControlsTypeSimple)
{
_largePlayButtonBack.hidden = _controlsHidden || _playing;
}
}
- (void)hidePanelEvent
{
[self _invalidateTimer];
[self setPanelHidden:true animated:true];
}
- (void)_startTimer
{
_hidePanelTimer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(hidePanelEvent) interval:3.0 repeat:false];
}
- (void)_startTimerIfNeeded
{
if (_playing && !_panelHidden && _hidePanelTimer == nil)
[self _startTimer];
}
- (void)_invalidateTimer
{
[_hidePanelTimer invalidate];
_hidePanelTimer = nil;
}
- (void)screenAreaPressed
{
if (_type == TGEmbedPlayerControlsTypeFull)
{
if (_panelHidden)
{
[self setPanelHidden:false animated:true];
}
else
{
if (_playing)
[self pauseButtonPressed];
else
[self playButtonPressed];
}
}
else if (_type == TGEmbedPlayerControlsTypeSimple)
{
if (_playing)
[self pauseButtonPressed];
else
[self playButtonPressed];
}
}
- (void)playButtonPressed
{
if (_type == TGEmbedPlayerControlsTypeFull)
{
_largePlayButtonBack.hidden = true;
}
if (self.playPressed != nil)
self.playPressed();
}
- (void)pauseButtonPressed
{
_watermarkDenyHiding = false;
if (self.pausePressed != nil)
self.pausePressed();
}
- (void)fullscreenButtonPressed
{
if (self.fullscreenPressed != nil)
self.fullscreenPressed();
}
- (void)pictureInPictureButtonPressed
{
if (self.pictureInPicturePressed != nil)
self.pictureInPicturePressed();
}
- (void)setPictureInPictureHidden:(bool)hidden
{
_pictureInPictureButton.hidden = hidden;
[self setNeedsLayout];
}
- (void)setPanelHidden:(bool)hidden animated:(bool)animated
{
if (_panelHidden == hidden)
return;
_panelHidden = hidden;
if (self.panelVisibilityChange != nil)
self.panelVisibilityChange(hidden);
[self setFullscreenButtonDimmed:hidden animated:true];
if (animated)
{
UIViewAnimationOptions options = kNilOptions;
if (!hidden && iosMajorVersion() >= 7)
options |= (7 << 16);
else if (hidden)
options |= UIViewAnimationOptionCurveEaseOut;
_animatingPanel = true;
NSTimeInterval duration = hidden ? 0.4 : 0.25;
[UIView animateWithDuration:duration delay:0.0 options:options animations:^
{
if (hidden)
_backgroundView.frame = CGRectMake(0, self.frame.size.height, self.frame.size.width, TGEmbedPlayerControlsPanelHeight);
else
_backgroundView.frame = CGRectMake(0, self.frame.size.height - _backgroundView.frame.size.height, self.frame.size.width, TGEmbedPlayerControlsPanelHeight);
[self _layoutWatermark];
} completion:^(__unused BOOL finished)
{
_animatingPanel = false;
}];
}
else
{
_animatingPanel = false;
if (hidden)
_backgroundView.frame = CGRectMake(0, self.frame.size.height, self.frame.size.width, TGEmbedPlayerControlsPanelHeight);
else
_backgroundView.frame = CGRectMake(0, self.frame.size.height - _backgroundView.frame.size.height, self.frame.size.width, TGEmbedPlayerControlsPanelHeight);
[self _layoutWatermark];
}
}
- (void)setFullscreenButtonHidden:(bool)hidden animated:(bool)animated
{
CGFloat visibleAlpha = self.inhibitFullscreenButton ? 0.65f : 1.0f;
if (animated)
{
_fullscreenButtonWrapper.userInteractionEnabled = !hidden;
[UIView animateWithDuration:0.25 animations:^
{
_fullscreenButtonWrapper.alpha = hidden ? 0.0f : visibleAlpha;
}];
}
else
{
_fullscreenButtonWrapper.userInteractionEnabled = !hidden;
_fullscreenButtonWrapper.alpha = hidden ? 0.0f : visibleAlpha;
}
}
- (void)setFullscreenButtonDimmed:(bool)dimmed animated:(bool)animated
{
if (self.inhibitFullscreenButton && dimmed && _fullscreenButtonWrapper.alpha < FLT_EPSILON)
return;
if (animated)
{
NSTimeInterval duration = dimmed ? 0.5 : 0.25;
[UIView animateWithDuration:duration animations:^
{
_fullscreenButtonWrapper.alpha = dimmed ? 0.65f : 1.0f;
}];
}
else
{
_fullscreenButtonWrapper.alpha = dimmed ? 0.65f : 1.0f;
}
}
- (void)setDisabled
{
_disabled = true;
_screenAreaButton.userInteractionEnabled = false;
}
- (void)hidePlayButton
{
[_largePlayButtonBack removeFromSuperview];
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *view = [super hitTest:point withEvent:event];
if (!_disabled)
return view;
if (view == _watermarkView)
return view;
return nil;
}
- (void)notifyOfPlaybackStart
{
TGDispatchAfter(0.1, dispatch_get_main_queue(), ^
{
if (!self.inhibitFullscreenButton)
[self setFullscreenButtonHidden:false animated:true];
TGDispatchAfter(2.5, dispatch_get_main_queue(), ^
{
if (_panelHidden)
[self setFullscreenButtonDimmed:true animated:true];
if (_playing)
[self setInternalWatermarkHidden:true animated:true];
_watermarkDenyHiding = false;
});
});
}
- (void)_layoutWatermark
{
CGFloat visiblePanelHeight = _panelHidden ? 0.0f : TGEmbedPlayerControlsPanelHeight;
CGRect watermarkFrame = CGRectMake(0, 0, _watermarkView.frame.size.width, _watermarkView.frame.size.height);
switch (_watermarkPosition)
{
case TGEmbedPlayerWatermarkPositionTopLeft:
{
watermarkFrame.origin = _watermarkOffset;
}
break;
case TGEmbedPlayerWatermarkPositionBottomLeft:
{
watermarkFrame.origin = CGPointMake(_watermarkOffset.x, self.frame.size.height - watermarkFrame.size.height - visiblePanelHeight + _watermarkOffset.y);
}
break;
case TGEmbedPlayerWatermarkPositionBottomRight:
{
watermarkFrame.origin = CGPointMake(self.frame.size.width - watermarkFrame.size.width + _watermarkOffset.x, self.frame.size.height - watermarkFrame.size.height - visiblePanelHeight + _watermarkOffset.y);
}
break;
default:
break;
}
_watermarkView.frame = watermarkFrame;
}
- (void)layoutSubviews
{
_fullscreenButtonWrapper.frame = CGRectMake(self.bounds.size.width - _fullscreenButtonWrapper.frame.size.width, 0, _fullscreenButtonWrapper.frame.size.width, _fullscreenButtonWrapper.frame.size.height);
CGFloat rightOffset = _pictureInPictureButton.hidden ? 0.0f : 35.0f;
_remainingLabel.frame = CGRectMake(self.frame.size.width - _remainingLabel.frame.size.width - rightOffset, 0.0f, _remainingLabel.frame.size.width, _remainingLabel.frame.size.height);
_pictureInPictureButton.frame = CGRectMake(self.frame.size.width - _pictureInPictureButton.frame.size.width, 0, _pictureInPictureButton.frame.size.width, _pictureInPictureButton.frame.size.height);
_scrubber.frame = CGRectMake(CGRectGetMaxX(_positionLabel.frame), 14.5f, self.frame.size.width - CGRectGetMaxX(_positionLabel.frame) - _remainingLabel.frame.size.width - rightOffset, 3.0f);
if (!_animatingPanel || _backgroundView.frame.size.width < FLT_EPSILON)
{
if (_panelHidden)
_backgroundView.frame = CGRectMake(0, self.frame.size.height, self.frame.size.width, TGEmbedPlayerControlsPanelHeight);
else
_backgroundView.frame = CGRectMake(0, self.frame.size.height - _backgroundView.frame.size.height, self.frame.size.width, TGEmbedPlayerControlsPanelHeight);
}
[self _layoutWatermark];
_largePlayButtonBack.frame = CGRectMake(CGFloor((self.frame.size.width - _largePlayButtonBack.frame.size.width) / 2.0f), CGFloor((self.frame.size.height - _largePlayButtonBack.frame.size.height) / 2.0f), _largePlayButtonBack.frame.size.width, _largePlayButtonBack.frame.size.height);
}
@end

View File

@ -1,16 +0,0 @@
#import <UIKit/UIKit.h>
@interface TGEmbedPlayerScrubber : UIControl
@property (nonatomic, copy) void (^onInteractionStart)();
@property (nonatomic, copy) void (^onSeek)(CGFloat position);
@property (nonatomic, copy) void (^onInteractionEnd)();
@property (nonatomic, readonly) bool isTracking;
- (void)setPosition:(CGFloat)position;
- (void)setDownloadProgress:(CGFloat)progress;
- (void)setTintColor:(UIColor *)tintColor;
@end

View File

@ -1,174 +0,0 @@
#import "TGEmbedPlayerScrubber.h"
#import "LegacyComponentsInternal.h"
#import "TGImageUtils.h"
#import <LegacyComponents/UIControl+HitTestEdgeInsets.h>
const CGFloat TGEmbedPlayerKnobMargin = 8.0f;
@interface TGEmbedPlayerScrubber ()
{
UIImageView *_backgroundView;
UIView *_downloadProgressView;
UIImageView *_playPositionView;
UIControl *_knobView;
CGFloat _position;
CGFloat _downloadProgress;
CGFloat _knobDragPosition;
bool _tracking;
UIPanGestureRecognizer *_panGestureRecognizer;
}
@end
@implementation TGEmbedPlayerScrubber
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil)
{
self.hitTestEdgeInsets = UIEdgeInsetsMake(-20, -20, -20, -20);
UIImage *hollowTrackImage = [TGComponentsImageNamed(@"EmbedVideoTrackHollow") resizableImageWithCapInsets:UIEdgeInsetsMake(0, 2, 0, 2)];
_backgroundView = [[UIImageView alloc] initWithImage:hollowTrackImage];
[self addSubview:_backgroundView];
_downloadProgressView = [[UIView alloc] initWithFrame:CGRectZero];
_downloadProgressView.backgroundColor = [UIColor blackColor];
[self addSubview:_downloadProgressView];
static UIImage *trackImage = nil;
static dispatch_once_t onceToken1;
dispatch_once(&onceToken1, ^
{
UIGraphicsBeginImageContextWithOptions(CGSizeMake(3.0f, 3.0f), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextFillEllipseInRect(context, CGRectMake(0, 0, 3.0f, 3.0f));
trackImage = [UIGraphicsGetImageFromCurrentImageContext() resizableImageWithCapInsets:UIEdgeInsetsMake(0, 1, 0, 1)];
UIGraphicsEndImageContext();
});
_playPositionView = [[UIImageView alloc] initWithImage:trackImage];
[self addSubview:_playPositionView];
static UIImage *knobViewImage = nil;
static dispatch_once_t onceToken2;
dispatch_once(&onceToken2, ^
{
UIGraphicsBeginImageContextWithOptions(CGSizeMake(21.0f, 21.0f), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetShadowWithColor(context, CGSizeMake(0, 1.0f), 2.0f, [UIColor colorWithWhite:0.0f alpha:0.5f].CGColor);
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextFillEllipseInRect(context, CGRectMake(3.0f, 3.0f, 15.0f, 15.0f));
knobViewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
});
_knobView = [[UIControl alloc] initWithFrame:CGRectMake(0, 0, 21.0f, 21.0f)];
_knobView.hitTestEdgeInsets = UIEdgeInsetsMake(-10, -10, -10, -10);
[self addSubview:_knobView];
UIImageView *knobBackground = [[UIImageView alloc] initWithImage:knobViewImage];
[_knobView addSubview:knobBackground];
_panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
[_knobView addGestureRecognizer:_panGestureRecognizer];
}
return self;
}
- (void)handlePan:(UIPanGestureRecognizer *)gestureRecognizer
{
CGPoint touchLocation = [gestureRecognizer locationInView:self];
switch (gestureRecognizer.state)
{
case UIGestureRecognizerStateBegan:
{
_tracking = true;
if (self.onInteractionStart != nil)
self.onInteractionStart();
}
case UIGestureRecognizerStateChanged:
{
_knobDragPosition = [self knobPositionForX:touchLocation.x];
[self setNeedsLayout];
if (self.onSeek != nil)
self.onSeek(_knobDragPosition);
}
break;
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
{
_tracking = false;
_position = _knobDragPosition;
[self setNeedsLayout];
if (self.onInteractionEnd != nil)
self.onInteractionEnd();
}
break;
default:
break;
}
}
- (bool)isTracking
{
return _knobView.highlighted;
}
- (void)setPosition:(CGFloat)position
{
_position = position;
[self setNeedsLayout];
}
- (void)setDownloadProgress:(CGFloat)progress
{
_downloadProgress = progress;
[self setNeedsLayout];
}
- (void)setTintColor:(UIColor *)tintColor
{
UIImage *tintedImage = [TGTintedImage(TGComponentsImageNamed(@"EmbedVideoTrackHollow"), tintColor) resizableImageWithCapInsets:UIEdgeInsetsMake(0, 2, 0, 2)];
_backgroundView.image = tintedImage;
_downloadProgressView.backgroundColor = tintColor;
}
- (CGFloat)knobPositionRange
{
return MAX(0.0f, self.bounds.size.width - TGEmbedPlayerKnobMargin * 2);
}
- (CGFloat)knobPositionForX:(CGFloat)x
{
return MAX(0, MIN(1.0f, (x - TGEmbedPlayerKnobMargin) / [self knobPositionRange]));
}
- (void)layoutSubviews
{
_backgroundView.frame = self.bounds;
CGFloat downloadProgressRange = MAX(0.0f, self.bounds.size.width - 2.0f);
_downloadProgressView.frame = CGRectMake(1.0f, 1.0f, TGRetinaFloor(_downloadProgress * downloadProgressRange), 1.0f);
CGFloat position = _tracking ? _knobDragPosition : _position;
_knobView.center = CGPointMake(TGRetinaCeil(TGEmbedPlayerKnobMargin + position * [self knobPositionRange]), 1.5f);
_playPositionView.frame = CGRectMake(0, 0, _knobView.center.x, 3.0f);
}
@end

View File

@ -1,29 +0,0 @@
#import "TGEmbedPlayerState.h"
@implementation TGEmbedPlayerState
@synthesize playing = _playing;
@synthesize duration = _duration;
@synthesize position = _position;
@synthesize downloadProgress = _downloadProgress;
@synthesize buffering = _buffering;
+ (instancetype)stateWithPlaying:(bool)playing
{
TGEmbedPlayerState *state = [[TGEmbedPlayerState alloc] init];
state->_playing = playing;
return state;
}
+ (instancetype)stateWithPlaying:(bool)playing duration:(NSTimeInterval)duration position:(NSTimeInterval)position downloadProgress:(CGFloat)downloadProgress buffering:(bool)buffering
{
TGEmbedPlayerState *state = [[TGEmbedPlayerState alloc] init];
state->_playing = playing;
state->_duration = duration;
state->_position = position;
state->_downloadProgress = downloadProgress;
state->_buffering = buffering;
return state;
}
@end

View File

@ -1,948 +0,0 @@
#import "TGEmbedPlayerView.h"
#import "LegacyComponentsInternal.h"
#import <LegacyComponents/TGPhotoEditorUtils.h>
#import <LegacyComponents/TGImageView.h>
#import "TGEmbedPlayerState.h"
#import "TGEmbedYoutubePlayerView.h"
#import "TGEmbedVimeoPlayerView.h"
#import "TGEmbedCoubPlayerView.h"
#import "TGEmbedVKPlayerView.h"
#import "TGEmbedVinePlayerView.h"
#import "TGEmbedInstagramPlayerView.h"
#import "TGEmbedSoundCloudPlayerView.h"
#import "TGEmbedTwitchPlayerView.h"
#import "TGEmbedVideoPlayerView.h"
#import <libkern/OSAtomic.h>
@interface TGEmbedPlayerView () <UIWebViewDelegate, WKNavigationDelegate>
{
CGFloat _embedScale;
TGImageView *_coverView;
UIView *_dimView;
UILabel *_errorLabel;
UIWebView *_uiWebView;
WKWebView *_wkWebView;
UIView *_interactionView;
CGSize _maxPlayerSize;
bool _loading;
dispatch_semaphore_t _sema;
SQueue *_jsQueue;
SPipe *_statePipe;
bool _pausedManually;
bool _shouldResumePIPPlayback;
id<SDisposable> _currentAudioSession;
SVariable *_loadProgressValue;
}
@end
@implementation TGEmbedPlayerView
@synthesize requestPictureInPicture = _requestPictureInPicture;
@synthesize disallowPIP = _disallowPIP;
@synthesize initialFrame = _initialFrame;
- (instancetype)initWithWebPageAttachment:(TGWebPageMediaAttachment *)webPage
{
return [self initWithWebPageAttachment:webPage thumbnailSignal:nil];
}
- (instancetype)initWithWebPageAttachment:(TGWebPageMediaAttachment *)webPage thumbnailSignal:(SSignal *)thumbnailSignal
{
return [self initWithWebPageAttachment:webPage thumbnailSignal:nil alternateCachePathSignal:nil];
}
- (instancetype)initWithWebPageAttachment:(TGWebPageMediaAttachment *)webPage thumbnailSignal:(SSignal *)thumbnailSignal alternateCachePathSignal:(SSignal *)__unused alternateCachePathSignal
{
self = [super initWithFrame:CGRectZero];
if (self != nil)
{
self.clipsToBounds = true;
_statePipe = [[SPipe alloc] init];
_loadProgressValue = [[SVariable alloc] init];
_webPage = webPage;
_state = [TGEmbedPlayerState stateWithPlaying:false duration:0.0 position:0.0 downloadProgress:0.0f buffering:false];
TGEmbedPlayerControlsType controlsType = [self _controlsType];
if (controlsType != TGEmbedPlayerControlsTypeNone)
{
__weak TGEmbedPlayerView *weakSelf = self;
_controlsView = [[TGEmbedPlayerControls alloc] initWithFrame:CGRectZero type:controlsType];
_controlsView.playPressed = ^
{
__strong TGEmbedPlayerView *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf playVideo];
};
_controlsView.pausePressed = ^
{
__strong TGEmbedPlayerView *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf pauseVideo];
};
_controlsView.seekToPosition = ^(CGFloat position)
{
__strong TGEmbedPlayerView *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf seekToFractPosition:position];
};
_controlsView.fullscreenPressed = ^
{
__strong TGEmbedPlayerView *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf enterFullscreen:0.0];
};
_controlsView.pictureInPicturePressed = ^
{
__strong TGEmbedPlayerView *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf _pictureInPicturePressed];
};
_controlsView.watermarkPressed = ^
{
__strong TGEmbedPlayerView *strongSelf = weakSelf;
if (strongSelf != nil && !strongSelf.disableWatermarkAction)
[strongSelf _watermarkAction];
};
_controlsView.panelVisibilityChange = ^(bool hidden)
{
if (hidden)
return;
__strong TGEmbedPlayerView *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf _onPanelAppearance];
};
[_controlsView setPictureInPictureHidden:![self supportsPIP]];
[self addSubview:_controlsView];
}
CGSize imageSize = CGSizeZero;
if (webPage.photo != nil)
[webPage.photo.imageInfo closestImageUrlWithSize:CGSizeMake(1136, 1136) resultingSize:&imageSize];
CGFloat imageAspect = imageSize.width / imageSize.height;
CGSize fitSize = CGSizeMake(215.0f, 180.0f);
if (ABS(imageAspect - 1.0f) < FLT_EPSILON)
fitSize = CGSizeMake(215.0f, 215.0f);
imageSize = TGScaleToFill(imageSize, fitSize);
_dimWrapperView = [[UIView alloc] init];
_dimWrapperView.backgroundColor = [UIColor blackColor];
[self addSubview:_dimWrapperView];
SSignal *coverSignal = thumbnailSignal ?: [[LegacyComponentsGlobals provider] squarePhotoThumbnail:webPage.photo ofSize:imageSize threadPool:[[LegacyComponentsGlobals provider] sharedMediaImageProcessingThreadPool] memoryCache:[[LegacyComponentsGlobals provider] sharedMediaMemoryImageCache] pixelProcessingBlock:nil downloadLargeImage:false placeholder:nil];
_coverView = [[TGImageView alloc] init];
_coverView.contentMode = UIViewContentModeScaleAspectFill;
[_coverView setSignal:coverSignal];
[_dimWrapperView addSubview:_coverView];
_dimView = [[UIView alloc] init];
_dimView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_dimView.backgroundColor = UIColorRGBA(0x000000, 0.5f);
[_dimWrapperView addSubview:_dimView];
_overlayView = [[TGMessageImageViewOverlayView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 44.0f, 44.0f)];
[_overlayView setRadius:44.0f];
[_dimWrapperView addSubview:_overlayView];
_errorLabel = [[UILabel alloc] init];
_errorLabel.backgroundColor = [UIColor clearColor];
_errorLabel.font = TGSystemFontOfSize(16.0f);
_errorLabel.hidden = true;
_errorLabel.text = TGLocalized(@"Web.Error");
_errorLabel.textColor = [UIColor whiteColor];
[_errorLabel sizeToFit];
[_dimWrapperView addSubview:_errorLabel];
if (iosMajorVersion() >= 11)
{
_coverView.accessibilityIgnoresInvertColors = true;
_dimView.accessibilityIgnoresInvertColors = true;
_overlayView.accessibilityIgnoresInvertColors = true;
_errorLabel.accessibilityIgnoresInvertColors = true;
}
_interactionView = [[UIView alloc] initWithFrame:self.bounds];
_interactionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_interactionView.hidden = true;
[self addSubview:_interactionView];
_jsQueue = [[SQueue alloc] init];
_sema = dispatch_semaphore_create(0);
}
return self;
}
- (void)dealloc
{
WKWebView *wkWebView = _wkWebView;
[_jsQueue dispatchSync:^
{
wkWebView.navigationDelegate = nil;
}];
_uiWebView.delegate = nil;
[_currentAudioSession dispose];
[[LegacyComponentsGlobals provider] resumePictureInPicturePlayback];
TGDispatchAfter(0.1, dispatch_get_main_queue(), ^
{
[[LegacyComponentsGlobals provider] maybeReleaseVolumeOverlay];
});
}
- (void)setDisallowPIP:(bool)disallowPIP
{
_disallowPIP = disallowPIP;
[_controlsView setPictureInPictureHidden:disallowPIP];
}
- (void)setDisallowAutoplay:(bool)disallowAutoplay
{
_disallowAutoplay = disallowAutoplay;
_dimView.hidden = true;
[_controlsView showLargePlayButton:true];
[self insertSubview:_dimWrapperView belowSubview:_controlsView];
}
- (void)setDisableControls:(bool)disableControls {
_disableControls = disableControls;
if (disableControls) {
for (UIView *view in [_dimWrapperView.subviews copy]) {
if (view != _coverView) {
view.alpha = 0.0f;
view.hidden = true;
}
}
_controlsView.hidden = true;
_dimView.hidden = true;
}
}
- (void)_setupAudioSessionIfNeeded
{
if (_currentAudioSession != nil)
return;
_currentAudioSession = [[LegacyComponentsGlobals provider] requestAudioSession:TGAudioSessionTypePlayEmbedVideo interrupted:^{}];
}
- (void)setupWithEmbedSize:(CGSize)embedSize
{
if (!self.disallowAutoplay || iosMajorVersion() < 8)
[self _setupAudioSessionIfNeeded];
CGFloat horEdge = [self _compensationEdges];
CGFloat verEdge = horEdge * embedSize.width / embedSize.height;
_embedSize = CGSizeMake(embedSize.width + horEdge * 2.0f, embedSize.height + verEdge * 2.0f);
CGSize screenSize = TGScreenSize();
screenSize = CGSizeMake(screenSize.height, screenSize.width);
_maxPlayerSize = [self _scaleViewToMaxSize] ? TGScaleToSize(embedSize, screenSize) : _embedSize;
_embedScale = _embedSize.width / _maxPlayerSize.width;
if (iosMajorVersion() >= 8)
[self setupWKWebView];
else
[self setupUIWebView];
if (!self.disallowAutoplay)
{
_overlayView.hidden = false;
[self setLoadProgress:0.01f duration:0.01];
[_loadProgressValue set:[SSignal single:@(0.01f)]];
}
}
- (CGFloat)_compensationEdges
{
return 0.0f;
}
- (SSignal *)loadProgress {
return [_loadProgressValue signal];
}
- (void)hideControls
{
[_controlsView hidePlayButton];
}
- (void)switchToPictureInPicture
{
[self _pictureInPicturePressed];
}
- (void)_requestSystemPictureInPictureMode
{
[self _evaluateJS:@"injectCmd('switchToPIP');" completion:^(__unused NSString *result)
{
}];
}
- (void)pausePIPPlayback
{
if (_pausedManually)
return;
_shouldResumePIPPlayback = true;
[self pauseVideo:false];
}
- (void)resumePIPPlayback
{
if (_shouldResumePIPPlayback)
[self playVideo];
_shouldResumePIPPlayback = false;
}
- (bool)supportsPIP
{
CGSize screenSize = TGScreenSize();
return !self.disallowPIP && (int)screenSize.height != 480;
}
- (void)setDimmed:(bool)dimmed animated:(bool)animated
{
[self setDimmed:dimmed animated:animated shouldDelay:false];
}
- (void)setDimmed:(bool)dimmed animated:(bool)animated shouldDelay:(bool)shouldDelay
{
bool useFakeProgress = [self _useFakeLoadingProgress];
if (animated)
{
if (dimmed)
{
_overlayView.hidden = false;
if (useFakeProgress) {
[self setLoadProgress:0.88f duration:3.0];
[_loadProgressValue set:[SSignal single:@(0.88f)]];
}
_dimWrapperView.hidden = false;
_dimWrapperView.alpha = 1.0f;
}
else
{
[self setLoadProgress:1.0f duration:0.2];
[_loadProgressValue set:[SSignal single:@(1.0f)]];
NSTimeInterval delay = shouldDelay ? 0.4 : 0.0;
[UIView animateWithDuration:0.2 delay:delay options:UIViewAnimationOptionAllowAnimatedContent | UIViewAnimationOptionCurveLinear animations:^
{
_dimWrapperView.alpha = 0.0f;
} completion:^(__unused BOOL finished)
{
_dimWrapperView.hidden = true;
_dimWrapperView.alpha = 1.0f;
}];
}
}
else
{
_dimWrapperView.hidden = !dimmed;
_overlayView.hidden = !dimmed;
if (dimmed && useFakeProgress) {
[self setLoadProgress:0.88f duration:3.0];
[_loadProgressValue set:[SSignal single:@(0.88f)]];
}
else
[_overlayView setNone];
}
}
- (void)setLoadProgress:(CGFloat)value duration:(NSTimeInterval)duration
{
[_overlayView setProgressAnimated:value duration:duration cancelEnabled:false];
[_loadProgressValue set:[SSignal single:@(value)]];
}
- (bool)_useFakeLoadingProgress
{
return true;
}
- (void)setCoverImage:(UIImage *)image
{
[_coverView setSignal:[SSignal single:image]];
}
#pragma mark -
- (void)beginLeavingFullscreen
{
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:self.roundCorners cornerRadii:CGSizeMake(14.5f, 14.5f)];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
self.layer.mask = maskLayer;
}
- (void)finishedLeavingFullscreen
{
self.layer.mask = nil;
}
- (void)onLockInPlace
{
[_controlsView setFullscreenButtonHidden:false animated:true];
}
- (void)setInhibitFullscreenButton:(bool)inhibitFullscreenButton
{
_inhibitFullscreenButton = inhibitFullscreenButton;
_controlsView.inhibitFullscreenButton = inhibitFullscreenButton;
}
#pragma mark -
- (void)setupWKWebView
{
WKUserContentController *contentController = [[WKUserContentController alloc] init];
if ([self _applyViewportUserScript])
{
NSString *jScript = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
WKUserScript *viewportScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:true];
[contentController addUserScript:viewportScript];
}
[self _setupUserScripts:contentController];
WKWebViewConfiguration *conf = [[WKWebViewConfiguration alloc] init];
conf.allowsInlineMediaPlayback = true;
conf.userContentController = contentController;
if ([conf respondsToSelector:@selector(setRequiresUserActionForMediaPlayback:)])
conf.requiresUserActionForMediaPlayback = false;
else if ([conf respondsToSelector:@selector(setMediaPlaybackRequiresUserAction:)])
conf.mediaPlaybackRequiresUserAction = false;
if ([conf respondsToSelector:@selector(setAllowsPictureInPictureMediaPlayback:)] && !TGIsPad())
conf.allowsPictureInPictureMediaPlayback = false;
_wkWebView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:conf];
_wkWebView.navigationDelegate = self;
_wkWebView.scrollView.scrollEnabled = false;
if (iosMajorVersion() >= 11)
_wkWebView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
NSString *embedHTML = [self _embedHTML];
bool useURL = (embedHTML.length == 0);
[self commonSetupWithWebView:_wkWebView useURL:useURL completion:^(NSURLRequest *request)
{
if (useURL)
[_wkWebView loadRequest:request];
else
[_wkWebView loadHTMLString:embedHTML baseURL:[self _baseURL]];
}];
}
- (void)webView:(WKWebView *)__unused webView didStartProvisionalNavigation:(WKNavigation *)__unused navigation
{
if (_loading)
return;
_loading = true;
if (!self.disallowAutoplay)
[self setDimmed:true animated:false];
if (self.onBeganLoading != nil)
self.onBeganLoading();
}
- (void)webView:(WKWebView *)__unused webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
NSURL *url = navigationAction.request.URL;
if (![url.scheme isEqualToString:@"http"] && ![url.scheme isEqualToString:@"https"] && ![url.absoluteString isEqualToString:@"about:blank"])
{
[self _notifyOfCallbackURL:url];
decisionHandler(WKNavigationActionPolicyCancel);
}
else
{
if (navigationAction.targetFrame == nil)
{
[self _openWebPage:url];
decisionHandler(WKNavigationActionPolicyCancel);
}
else
{
decisionHandler(WKNavigationActionPolicyAllow);
}
}
}
- (void)webView:(WKWebView *)__unused webView didFinishNavigation:(WKNavigation *)__unused navigation
{
if (!_loading)
return;
_loading = false;
[self _onPageReady];
}
- (void)webView:(WKWebView *)__unused webView didFailNavigation:(WKNavigation *)__unused navigation withError:(NSError *)__unused error
{
if ([error.domain isEqualToString:@"WebKitErrorDomain"] && error.code == 204)
return;
if (!_loading)
return;
_loading = false;
_overlayView.hidden = true;
[_overlayView setNone];
_errorLabel.hidden = false;
}
#pragma mark -
- (void)setupUIWebView
{
_uiWebView = [[UIWebView alloc] initWithFrame:CGRectZero];
_uiWebView.mediaPlaybackRequiresUserAction = false;
_uiWebView.delegate = self;
_uiWebView.scrollView.scrollEnabled = false;
NSString *embedHTML = [self _embedHTML];
bool useURL = (embedHTML.length == 0);
[self commonSetupWithWebView:_uiWebView useURL:useURL completion:^(NSURLRequest *request)
{
if (useURL)
[_uiWebView loadRequest:request];
else
[_uiWebView loadHTMLString:embedHTML baseURL:[self _baseURL]];
}];
}
- (BOOL)webView:(UIWebView *)__unused webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)__unused navigationType
{
NSURL *url = request.URL;
if (![url.scheme isEqualToString:@"http"] && ![url.scheme isEqualToString:@"https"] && ![url.absoluteString isEqualToString:@"about:blank"])
{
[self _notifyOfCallbackURL:url];
return false;
}
return true;
}
- (void)webViewDidStartLoad:(UIWebView *)__unused webView
{
if (_loading)
return;
_loading = true;
[self setDimmed:true animated:false];
if (self.onBeganLoading != nil)
self.onBeganLoading();
}
- (void)webViewDidFinishLoad:(UIWebView *)__unused webView
{
if (!_loading)
return;
_loading = false;
[self _onPageReady];
}
- (void)webView:(UIWebView *)__unused webView didFailLoadWithError:(NSError *)__unused error
{
if (!_loading)
return;
_loading = false;
_overlayView.hidden = true;
[_overlayView setNone];
_errorLabel.hidden = false;
}
- (void)commonSetupWithWebView:(UIView *)webView useURL:(bool)useURL completion:(void (^)(NSURLRequest *))completion
{
CGFloat horEdge = [self _compensationEdges];
CGFloat verEdge = horEdge * _embedSize.width / _embedSize.height;
if (iosMajorVersion() >= 11)
webView.accessibilityIgnoresInvertColors = true;
webView.backgroundColor = [UIColor blackColor];
webView.frame = CGRectMake(0, 0, _maxPlayerSize.width, _maxPlayerSize.height);
webView.transform = CGAffineTransformMakeScale(_embedScale, _embedScale);
webView.center = CGPointMake((_embedSize.width - horEdge * 2.0f) / 2.0f, (_embedSize.height - verEdge * 2.0f) / 2.0f);
if (_controlsView != nil && !_disallowAutoplay)
[self insertSubview:webView belowSubview:_controlsView];
else
[self insertSubview:webView belowSubview:_dimWrapperView];
if (useURL)
{
NSURL *url = [self _embedURL];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSString *referer = [[NSString alloc] initWithFormat:@"%@://%@", [url scheme], [url host]];
[request setValue:referer forHTTPHeaderField:@"Referer"];
if (completion != nil)
completion(request);
}
else
{
if (completion != nil)
completion(nil);
}
}
#pragma mark -
- (void)_openWebPage:(NSURL *)url
{
[[LegacyComponentsGlobals provider] openURLNative:url];
}
- (void)playVideo
{
if (_disallowAutoplay)
_dimWrapperView.hidden = true;
[self _setupAudioSessionIfNeeded];
}
- (void)pauseVideo
{
[self pauseVideo:true];
}
- (void)pauseVideo:(bool)manually
{
_pausedManually = manually;
}
- (void)seekToPosition:(NSTimeInterval)__unused position
{
}
- (void)seekToFractPosition:(CGFloat)position
{
NSTimeInterval timePosition = self.state.duration * position;
[self seekToPosition:timePosition];
}
- (void)enterFullscreen:(NSTimeInterval)duration
{
if (self.requestFullscreen != nil)
self.requestFullscreen(duration);
}
- (void)enterPictureInPicture:(TGEmbedPIPCorner)corner
{
if (self.requestPictureInPicture != nil)
self.requestPictureInPicture(corner);
}
- (void)_pictureInPicturePressed
{
[self enterPictureInPicture:TGEmbedPIPCornerNone];
}
- (SSignal *)stateSignal
{
return _statePipe.signalProducer();
}
- (void)updateState:(TGEmbedPlayerState *)state
{
_state = state;
[_controlsView setState:state];
_statePipe.sink(state);
}
#pragma mark -
- (void)_onPageReady
{
[self setDimmed:false animated:true];
}
- (void)_didBeginPlayback
{
[_controlsView notifyOfPlaybackStart];
[[LegacyComponentsGlobals provider] pausePictureInPicturePlayback];
if (self.onBeganPlaying != nil)
self.onBeganPlaying();
}
- (void)_onPanelAppearance
{
}
- (void)_watermarkAction
{
[self pauseVideo];
}
- (void)_prepareToEnterFullscreen
{
[_controlsView setWatermarkHidden:true];
[_controlsView setHidden:true animated:true];
_interactionView.hidden = false;
}
- (void)_prepareToLeaveFullscreen
{
[_controlsView setWatermarkHidden:false];
[_controlsView setHidden:false animated:true];
_interactionView.hidden = true;
}
#pragma mark -
- (TGEmbedPlayerControlsType)_controlsType
{
return TGEmbedPlayerControlsTypeNone;
}
- (void)_evaluateJS:(NSString *)jsString completion:(void (^)(NSString *))completion
{
if (_wkWebView != nil)
{
[_jsQueue dispatch:^
{
void (^block)(void) = ^
{
[_wkWebView evaluateJavaScript:jsString completionHandler:^(id result, __unused NSError *error)
{
dispatch_semaphore_signal(_sema);
TGDispatchOnMainThread(^
{
if (completion != nil)
completion(result);
});
}];
};
if (iosMajorVersion() >= 11)
TGDispatchOnMainThread(block);
else
block();
dispatch_semaphore_wait(_sema, DISPATCH_TIME_FOREVER);
}];
}
else if (_uiWebView != nil)
{
NSString *result = [_uiWebView stringByEvaluatingJavaScriptFromString:jsString];
if (completion != nil)
completion(result);
}
}
- (NSString *)_embedHTML
{
NSString *path = TGComponentsPathForResource(@"DefaultPlayer", @"html");
NSError *error = nil;
NSString *embedHTMLTemplate = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
if (error != nil)
{
TGLegacyLog(@"[DefaultEmbedPlayer]: Received error rendering template: %@", error);
return nil;
}
NSString *embedHTML = [NSString stringWithFormat:embedHTMLTemplate, [self _embedURL].absoluteString];
return embedHTML;
}
- (NSURL *)_embedURL
{
return [NSURL URLWithString:_webPage.embedUrl];
}
- (NSURL *)_baseURL
{
return [NSURL URLWithString:@"about:blank"];
}
- (void)_setupUserScripts:(WKUserContentController *)__unused contentController
{
NSError *error = nil;
NSString *path = TGComponentsPathForResource(@"DefaultPlayerInject", @"js");
NSString *scriptText = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
if (error != nil)
TGLegacyLog(@"[DefaultEmbedPlayer]: Received error loading inject script: %@", error);
WKUserScript *script = [[WKUserScript alloc] initWithSource:scriptText injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:false];
[contentController addUserScript:script];
}
- (bool)_applyViewportUserScript
{
return true;
}
- (void)_notifyOfCallbackURL:(NSURL *)__unused url
{
}
- (UIView *)_webView
{
if (_wkWebView != nil)
return _wkWebView;
else if (_uiWebView != nil)
return _uiWebView;
return nil;
}
- (bool)_scaleViewToMaxSize
{
return false;
}
- (void)_cleanWebView
{
_wkWebView.navigationDelegate = nil;
[_wkWebView removeFromSuperview];
_wkWebView = nil;
_uiWebView.delegate = nil;
[_uiWebView removeFromSuperview];
_uiWebView = nil;
}
+ (bool)_supportsWebPage:(TGWebPageMediaAttachment *)__unused webPage
{
return true;
}
+ (Class)playerViewClassForWebPage:(TGWebPageMediaAttachment *)webPage onlySpecial:(bool)onlySpecial
{
static dispatch_once_t onceToken;
static NSArray *playerViewClasses;
dispatch_once(&onceToken, ^
{
playerViewClasses = @
[
[TGEmbedYoutubePlayerView class],
[TGEmbedVimeoPlayerView class],
[TGEmbedCoubPlayerView class],
[TGEmbedVKPlayerView class],
[TGEmbedVinePlayerView class],
[TGEmbedInstagramPlayerView class],
[TGEmbedSoundCloudPlayerView class],
[TGEmbedTwitchPlayerView class],
[TGEmbedVideoPlayerView class]
];
});
if (iosMajorVersion() >= 8)
{
for (Class playerViewClass in playerViewClasses)
{
if ([playerViewClass _supportsWebPage:webPage])
{
if (playerViewClass == [TGEmbedVideoPlayerView class] && onlySpecial)
return nil;
return playerViewClass;
}
}
}
if (onlySpecial)
return nil;
return self;
}
- (void)layoutSubviews
{
_dimWrapperView.frame = self.bounds;
_coverView.frame = _dimWrapperView.bounds;
_overlayView.center = CGPointMake(CGRectGetMidX(_dimWrapperView.bounds), CGRectGetMidY(_dimWrapperView.bounds));
_errorLabel.center = _overlayView.center;
_controlsView.frame = self.bounds;
}
+ (TGEmbedPlayerView *)makePlayerViewForWebPage:(TGWebPageMediaAttachment *)webPage thumbnailSignal:(SSignal *)signal {
Class playerClass = [self playerViewClassForWebPage:webPage onlySpecial:false];
if (playerClass != nil) {
return [[playerClass alloc] initWithWebPageAttachment:webPage thumbnailSignal:signal];
} else {
return nil;
}
}
+ (bool)hasNativeSupportForX:(TGWebPageMediaAttachment *)webPage {
static dispatch_once_t onceToken;
static NSArray *playerViewClasses;
dispatch_once(&onceToken, ^
{
playerViewClasses = @
[
[TGEmbedYoutubePlayerView class],
/*[TGEmbedVimeoPlayerView class],
[TGEmbedCoubPlayerView class],
[TGEmbedVKPlayerView class],
[TGEmbedVinePlayerView class],
[TGEmbedInstagramPlayerView class],
[TGEmbedSoundCloudPlayerView class],
[TGEmbedTwitchPlayerView class],
[TGEmbedVideoPlayerView class]*/
];
});
if (iosMajorVersion() >= 8)
{
for (Class playerViewClass in playerViewClasses)
{
if ([playerViewClass _supportsWebPage:webPage])
{
if (playerViewClass == [TGEmbedVideoPlayerView class])
return false;
return true;
}
}
}
return false;
}
@end

View File

@ -1,7 +0,0 @@
#import "TGEmbedPlayerView.h"
@interface TGEmbedSoundCloudPlayerView : TGEmbedPlayerView
+ (NSString *)_soundCloudIdFromText:(NSString *)text;
@end

View File

@ -1,64 +0,0 @@
#import "TGEmbedSoundCloudPlayerView.h"
@implementation TGEmbedSoundCloudPlayerView
- (NSURL *)_embedURL
{
NSString *trackId = [TGEmbedSoundCloudPlayerView _soundCloudIdFromText:_webPage.embedUrl];
NSString *url = [NSString stringWithFormat:@"https://w.soundcloud.com/player/?url=https%%3A%%2F%%2Fapi.soundcloud.com%%2Ftracks%%2F%@&auto_play=true&show_artwork=true&visual=true&liking=false&download=false&sharing=false&buying=false&hide_related=true&show_comments=false&show_user=true&show_reposts=false", trackId];
return [NSURL URLWithString:url];
}
+ (NSString *)_soundCloudIdFromText:(NSString *)text
{
NSMutableArray *prefixes = [NSMutableArray arrayWithArray:@
[
@"http://w.soundcloud.com/player/?url=",
@"https://w.soundcloud.com/player/?url="
]];
NSString *prefix = nil;
for (NSString *p in prefixes)
{
if ([text hasPrefix:p])
{
prefix = p;
break;
}
}
if (prefix == nil)
return nil;
NSString *suffix = [[text substringFromIndex:prefix.length] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSArray *components = [suffix componentsSeparatedByString:@"&"];
if (components.count < 2)
return nil;
NSString *url = components.firstObject;
components = [url componentsSeparatedByString:@"/"];
if (components.count < 1)
return nil;
NSString *identifier = components.lastObject;
for (int i = 0; i < (int)identifier.length; i++)
{
unichar c = [identifier characterAtIndex:i];
if (!(c >= '0' && c <= '9'))
return nil;
}
return identifier;
}
+ (bool)_supportsWebPage:(TGWebPageMediaAttachment *)webPage
{
NSString *url = webPage.embedUrl;
return ([url hasPrefix:@"http://w.soundcloud.com/player/"] || [url hasPrefix:@"https://w.soundcloud.com/player/"]);
}
@end

View File

@ -1,5 +0,0 @@
#import <LegacyComponents/LegacyComponents.h>
@interface TGEmbedTwitchPlayerView : TGEmbedPlayerView
@end

View File

@ -1,107 +0,0 @@
#import "TGEmbedTwitchPlayerView.h"
#import "TGEmbedPlayerState.h"
#import "LegacyComponentsInternal.h"
@interface TGEmbedTwitchPlayerView ()
{
bool _started;
}
@end
@implementation TGEmbedTwitchPlayerView
- (void)playVideo
{
if (!_started)
return;
[self _evaluateJS:@"injectCmd('play')" completion:nil];
TGEmbedPlayerState *newState = [TGEmbedPlayerState stateWithPlaying:true duration:0.0 position:-1.0 downloadProgress:0.0 buffering:false];
[self updateState:newState];
}
- (void)pauseVideo:(bool)manually
{
[super pauseVideo:manually];
[self _evaluateJS:@"injectCmd('play')" completion:nil];
TGEmbedPlayerState *newState = [TGEmbedPlayerState stateWithPlaying:false duration:0.0 position:-1.0 downloadProgress:0.0 buffering:false];
[self updateState:newState];
}
- (void)_onPageReady
{
TGDispatchAfter(0.5, dispatch_get_main_queue(), ^
{
[super _onPageReady];
});
TGEmbedPlayerState *newState = [TGEmbedPlayerState stateWithPlaying:false duration:0.0 position:-1.0 downloadProgress:0.0 buffering:false];
[self updateState:newState];
}
- (TGEmbedPlayerControlsType)_controlsType
{
return TGEmbedPlayerControlsTypeFull;
}
- (void)_notifyOfCallbackURL:(NSURL *)url
{
NSString *action = url.host;
NSString *query = url.query;
NSString *data;
if (query != nil)
data = [query componentsSeparatedByString:@"="][1];
if ([action isEqualToString:@"onPlayback"])
{
if (!_started)
{
_started = true;
[self _didBeginPlayback];
}
TGEmbedPlayerState *newState = [TGEmbedPlayerState stateWithPlaying:true duration:0.0 position:-1.0 downloadProgress:0.0 buffering:false];
[self updateState:newState];
}
}
- (NSString *)_embedHTML
{
NSError *error = nil;
NSString *path = TGComponentsPathForResource(@"TwitchPlayer", @"html");
NSString *embedHTMLTemplate = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
if (error != nil)
{
TGLegacyLog(@"[TwitchEmbedPlayer]: Received error rendering template: %@", error);
return nil;
}
NSString *embedHTML = [NSString stringWithFormat:embedHTMLTemplate, _webPage.embedUrl];
return embedHTML;
}
- (void)_setupUserScripts:(WKUserContentController *)contentController
{
NSError *error = nil;
NSString *path = TGComponentsPathForResource(@"TwitchPlayerInject", @"js");
NSString *scriptText = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
if (error != nil)
TGLegacyLog(@"[TwitchEmbedPlayer]: Received error loading inject script: %@", error);
WKUserScript *script = [[WKUserScript alloc] initWithSource:scriptText injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:false];
[contentController addUserScript:script];
}
+ (bool)_supportsWebPage:(TGWebPageMediaAttachment *)webPage
{
NSString *url = webPage.embedUrl;
return ([url hasPrefix:@"http://player.twitch.tv/"] || [url hasPrefix:@"https://player.twitch.tv/"])
|| ([url hasPrefix:@"http://clips.twitch.tv/"] || [url hasPrefix:@"https://clips.twitch.tv/"]);
}
@end

View File

@ -1,5 +0,0 @@
#import "TGEmbedPlayerView.h"
@interface TGEmbedVKPlayerView : TGEmbedPlayerView
@end

View File

@ -1,288 +0,0 @@
#import "TGEmbedVKPlayerView.h"
#import <SSignalKit/SSignalKit.h>
#import "TGEmbedYoutubePlayerView.h"
#import "TGEmbedVimeoPlayerView.h"
#import "TGEmbedCoubPlayerView.h"
#import "TGEmbedVideoPlayerView.h"
@interface TGEmbedVKPlayerView ()
{
NSString *_url;
TGEmbedPlayerView *_subPlayerView;
SMetaDisposable *_disposable;
}
@end
@implementation TGEmbedVKPlayerView
- (instancetype)initWithWebPageAttachment:(TGWebPageMediaAttachment *)webPage thumbnailSignal:(SSignal *)thumbnailSignal alternateCachePathSignal:(SSignal *)alternateCachePathSignal
{
self = [super initWithWebPageAttachment:webPage thumbnailSignal:thumbnailSignal alternateCachePathSignal:alternateCachePathSignal];
if (self != nil)
{
_url = webPage.embedUrl;
}
return self;
}
- (void)dealloc
{
[_disposable dispose];
}
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
if (_subPlayerView != nil)
_subPlayerView.frame = self.bounds;
}
- (void)setRequestFullscreen:(void (^)(NSTimeInterval))requestFullscreen
{
[super setRequestFullscreen:requestFullscreen];
if (_subPlayerView != nil)
[_subPlayerView setRequestFullscreen:requestFullscreen];
}
- (void)setRequestPictureInPicture:(void (^)(TGEmbedPIPCorner))requestPictureInPicture
{
[super setRequestPictureInPicture:requestPictureInPicture];
if (_subPlayerView != nil)
[_subPlayerView setRequestPictureInPicture:requestPictureInPicture];
}
- (void)_prepareToEnterFullscreen
{
[super _prepareToEnterFullscreen];
if (_subPlayerView != nil)
[_subPlayerView _prepareToEnterFullscreen];
}
- (void)_prepareToLeaveFullscreen
{
[super _prepareToLeaveFullscreen];
if (_subPlayerView != nil)
[_subPlayerView _prepareToLeaveFullscreen];
}
- (void)playVideo
{
[super playVideo];
if (_subPlayerView != nil)
{
[_subPlayerView playVideo];
return;
}
}
- (void)pauseVideo:(bool)manually
{
[super pauseVideo:manually];
if (_subPlayerView != nil)
{
[_subPlayerView pauseVideo:manually];
return;
}
}
- (void)seekToPosition:(NSTimeInterval)position
{
if (_subPlayerView != nil)
{
[_subPlayerView seekToPosition:position];
return;
}
}
- (void)onLockInPlace
{
[super onLockInPlace];
if (_subPlayerView != nil)
{
[_subPlayerView onLockInPlace];
return;
}
}
- (void)setupWithEmbedSize:(CGSize)embedSize
{
[super setupWithEmbedSize:embedSize];
[self initializePlayer];
[self setLoadProgress:0.01f duration:0.01];
}
- (void)_requestSystemPictureInPictureMode
{
if (_subPlayerView != nil)
[_subPlayerView _requestSystemPictureInPictureMode];
else
[super _requestSystemPictureInPictureMode];
}
- (TGEmbedPlayerState *)state
{
if (_subPlayerView != nil)
return [_subPlayerView state];
else
return [super state];
}
- (SSignal *)stateSignal
{
if (_subPlayerView != nil)
return [_subPlayerView stateSignal];
else
return [super stateSignal];
}
- (void)initializePlayer
{
__weak TGEmbedVKPlayerView *weakSelf = self;
SSignal *signal = [[[LegacyComponentsGlobals provider] dataForHttpLocation:_url] map:^NSString *(NSData *data)
{
return [[NSString alloc] initWithData:data encoding:NSWindowsCP1251StringEncoding];
}];
_disposable = [[SMetaDisposable alloc] init];
[_disposable setDisposable:[[signal deliverOn:[SQueue mainQueue]] startWithNext:^(NSString *next)
{
__strong TGEmbedVKPlayerView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
NSRange ytRange = [next rangeOfString:@"youtube.com/embed/"];
if (ytRange.location != NSNotFound)
{
NSString *videoId = [self _getVideoId:next location:ytRange.location + @"youtube.com/embed/".length stopChar:'?'];
if (videoId.length > 0)
{
TGWebPageMediaAttachment *webPage = [[TGWebPageMediaAttachment alloc] init];
webPage.embedUrl = [NSString stringWithFormat:@"https://www.youtube.com/embed/%@", videoId];
[self _setupWithSubPlayerView:[[TGEmbedYoutubePlayerView alloc] initWithWebPageAttachment:webPage]];
}
}
NSRange vimeoRange = [next rangeOfString:@"vimeo.com/video/"];
if (vimeoRange.location != NSNotFound)
{
NSString *videoId = [self _getVideoId:next location:vimeoRange.location + @"vimeo.com/video/".length stopChar:'?'];
if (videoId.length > 0)
{
TGWebPageMediaAttachment *webPage = [[TGWebPageMediaAttachment alloc] init];
webPage.embedUrl = [NSString stringWithFormat:@"https://player.vimeo.com/video/%@", videoId];
[self _setupWithSubPlayerView:[[TGEmbedVimeoPlayerView alloc] initWithWebPageAttachment:webPage]];
}
}
NSRange coubRange = [next rangeOfString:@"coub.com/embed/"];
if (coubRange.location != NSNotFound)
{
NSString *videoId = [self _getVideoId:next location:coubRange.location + @"coub.com/embed/".length stopChar:'"'];
if (videoId.length > 0)
{
TGWebPageMediaAttachment *webPage = [[TGWebPageMediaAttachment alloc] init];
webPage.embedUrl = [NSString stringWithFormat:@"https://coub.com/embed/%@", videoId];
[self _setupWithSubPlayerView:[[TGEmbedCoubPlayerView alloc] initWithWebPageAttachment:webPage]];
}
}
NSRange vkRange = [next rangeOfString:@"<video id="];
NSRange urlRange = [next rangeOfString:@"<source src=\""];
if (vkRange.location != NSNotFound && urlRange.location != NSNotFound)
{
NSString *videoUrl = [self _getVideoId:next location:urlRange.location + @"<source src=\"".length stopChar:'"'];
if (videoUrl.length > 0)
{
TGWebPageMediaAttachment *webPage = [[TGWebPageMediaAttachment alloc] init];
webPage.embedUrl = videoUrl;
[self _setupWithSubPlayerView:[[TGEmbedVideoPlayerView alloc] initWithWebPageAttachment:webPage]];
}
}
}]];
}
- (NSString *)_getVideoId:(NSString *)string location:(NSUInteger)location stopChar:(char)stopChar
{
for (NSUInteger i = location; i < string.length - location; i++)
{
unichar c = [string characterAtIndex:i];
if (c == stopChar)
{
return [string substringWithRange:NSMakeRange(location, i - location)];
}
}
return nil;
}
- (void)_setupWithSubPlayerView:(TGEmbedPlayerView *)playerView
{
self.backgroundColor = [UIColor blackColor];
_subPlayerView = playerView;
_subPlayerView.frame = self.bounds;
[[self _webView].superview insertSubview:playerView aboveSubview:[self _webView]];
[self _cleanWebView];
[self.controlsView removeFromSuperview];
[playerView.dimWrapperView removeFromSuperview];
[playerView setupWithEmbedSize:_embedSize];
playerView.requestFullscreen = [self.requestFullscreen copy];
playerView.requestPictureInPicture = [self.requestPictureInPicture copy];
__weak TGEmbedVKPlayerView *weakSelf = self;
playerView.onBeganLoading = ^
{
__strong TGEmbedVKPlayerView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf setDimmed:true animated:false shouldDelay:false];
};
playerView.onBeganPlaying = ^
{
__strong TGEmbedVKPlayerView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf setDimmed:false animated:true shouldDelay:false];
};
playerView.onRealLoadProgress = ^(CGFloat progress, NSTimeInterval duration)
{
__strong TGEmbedVKPlayerView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf setLoadProgress:progress duration:duration];
};
}
+ (bool)_supportsWebPage:(TGWebPageMediaAttachment *)webPage
{
NSString *url = webPage.embedUrl;
return ([url hasPrefix:@"http://vk.com/video_ext.php"] || [url hasPrefix:@"https://vk.com/video_ext.php"]);
}
@end

View File

@ -1,5 +0,0 @@
#import "TGEmbedPlayerView.h"
@interface TGEmbedVideoPlayerView : TGEmbedPlayerView
@end

View File

@ -1,187 +0,0 @@
#import "TGEmbedVideoPlayerView.h"
#import "TGEmbedPlayerState.h"
#import <AVFoundation/AVFoundation.h>
#import <LegacyComponents/TGModernGalleryVideoView.h>
#import <LegacyComponents/TGTimerTarget.h>
@interface TGEmbedVideoPlayerView ()
{
NSString *_url;
bool _started;
AVPlayer *_player;
TGModernGalleryVideoView *_videoView;
UIImageView *_watermarkView;
NSInteger _playbackTicks;
bool _playingStarted;
}
@end
@implementation TGEmbedVideoPlayerView
- (instancetype)initWithWebPageAttachment:(TGWebPageMediaAttachment *)webPage thumbnailSignal:(SSignal *)thumbnailSignal alternateCachePathSignal:(SSignal *)alternateCachePathSignal
{
self = [super initWithWebPageAttachment:webPage thumbnailSignal:thumbnailSignal alternateCachePathSignal:alternateCachePathSignal];
if (self != nil)
{
_url = webPage.embedUrl;
}
return self;
}
- (void)dealloc
{
[_player.currentItem removeObserver:self forKeyPath:@"status"];
[_player.currentItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
}
- (void)playVideo
{
[_player play];
TGEmbedPlayerState *state = [TGEmbedPlayerState stateWithPlaying:true duration:self.state.duration position:self.state.position downloadProgress:self.state.downloadProgress buffering:self.state.buffering];
[self updateState:state];
}
- (void)pauseVideo:(bool)manually
{
[super pauseVideo:manually];
[_player pause];
TGEmbedPlayerState *state = [TGEmbedPlayerState stateWithPlaying:false duration:self.state.duration position:self.state.position downloadProgress:self.state.downloadProgress buffering:self.state.buffering];
[self updateState:state];
}
- (void)seekToPosition:(NSTimeInterval)position
{
[_player.currentItem seekToTime:CMTimeMake((int64_t)(position * 1000.0), 1000.0)];
TGEmbedPlayerState *newState = [TGEmbedPlayerState stateWithPlaying:self.state.isPlaying duration:self.state.duration position:position downloadProgress:self.state.downloadProgress buffering:self.state.buffering];
[self updateState:newState];
}
- (void)setupWithEmbedSize:(CGSize)embedSize
{
[super setupWithEmbedSize:embedSize];
[self _setupCustomPlayerWithURL:[NSURL URLWithString:_url]];
}
- (TGEmbedPlayerControlsType)_controlsType
{
return TGEmbedPlayerControlsTypeFull;
}
- (void)_onPageReady
{
}
- (void)_didBeginPlayback
{
[super _didBeginPlayback];
[self setDimmed:false animated:true shouldDelay:false];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)__unused object change:(NSDictionary *)__unused change context:(void *)__unused context {
bool playing = self.state.playing;
NSTimeInterval position = self.state.position;
NSTimeInterval duration = self.state.duration;
CGFloat downloadProgress = self.state.downloadProgress;
bool buffering = self.state.buffering;
if ([keyPath isEqualToString:@"status"])
{
if (_player.currentItem.status == AVPlayerItemStatusReadyToPlay)
{
if (duration < DBL_EPSILON)
duration = CMTimeGetSeconds(_player.currentItem.asset.duration);
if (!_started) {
_started = true;
[self setDimmed:true animated:false];
}
}
}
else if ([keyPath isEqualToString:@"loadedTimeRanges"])
{
NSValue *range = _player.currentItem.loadedTimeRanges.firstObject;
CMTime time = CMTimeRangeGetEnd(range.CMTimeRangeValue);
NSTimeInterval availableDuration = CMTimeGetSeconds(time);
if (duration < DBL_EPSILON)
duration = MAX(0.01, CMTimeGetSeconds(_player.currentItem.asset.duration));
downloadProgress = MAX(0.0, MIN(1.0, availableDuration / duration));
}
TGEmbedPlayerState *newState = [TGEmbedPlayerState stateWithPlaying:playing duration:duration position:position downloadProgress:downloadProgress buffering:buffering];
[self updateState:newState];
}
- (void)_setupCustomPlayerWithURL:(NSURL *)url
{
AVPlayerItem *item = [AVPlayerItem playerItemWithURL:url];
AVPlayer *player = [AVPlayer playerWithPlayerItem:item];
_player = player;
[player.currentItem addObserver:self forKeyPath:@"status" options:0 context:nil];
[player.currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:0 context:nil];
UIView *currentView = [self _webView];
TGModernGalleryVideoView *videoView = [[TGModernGalleryVideoView alloc] initWithFrame:currentView.frame player:player];
[currentView.superview insertSubview:videoView aboveSubview:currentView];
[self _cleanWebView];
_videoView = videoView;
__weak TGEmbedVideoPlayerView *weakSelf = self;
[player addPeriodicTimeObserverForInterval:CMTimeMake(1, 10) queue:dispatch_get_main_queue() usingBlock:^(CMTime time)
{
__strong TGEmbedVideoPlayerView *strongSelf = weakSelf;
if (strongSelf != nil)
{
NSTimeInterval position = CMTimeGetSeconds(time);
if (!strongSelf->_playingStarted && position > DBL_EPSILON)
{
strongSelf->_playbackTicks++;
if (strongSelf->_playbackTicks > 2)
{
strongSelf->_playingStarted = true;
[strongSelf _didBeginPlayback];
TGEmbedPlayerState *state = [TGEmbedPlayerState stateWithPlaying:true];
[strongSelf updateState:state];
}
}
TGEmbedPlayerState *state = strongSelf.state;
TGEmbedPlayerState *newState = [TGEmbedPlayerState stateWithPlaying:state.playing duration:state.duration position:position downloadProgress:state.downloadProgress buffering:self.state.buffering];
[strongSelf updateState:newState];
}
}];
[player play];
}
- (UIView *)_webView
{
if (_videoView != nil)
return _videoView;
return [super _webView];
}
+ (bool)_supportsWebPage:(TGWebPageMediaAttachment *)webPage
{
NSString *url = webPage.embedUrl;
NSArray *components = [url componentsSeparatedByString:@"?"];
if (components.count > 1)
url = components.firstObject;
return ([url hasSuffix:@".mp4"] || [url hasSuffix:@".mov"]);
}
@end

View File

@ -1,7 +0,0 @@
#import "TGEmbedPlayerView.h"
@interface TGEmbedVimeoPlayerView : TGEmbedPlayerView
+ (NSString *)_vimeoVideoIdFromText:(NSString *)text;
@end

View File

@ -1,233 +0,0 @@
#import "TGEmbedVimeoPlayerView.h"
#import "TGEmbedPlayerState.h"
#import "LegacyComponentsInternal.h"
NSString *const TGVimeoPlayerCallbackOnReady = @"onReady";
NSString *const TGVimeoPlayerCallbackOnState = @"onState";
@interface TGEmbedVimeoPlayerView ()
{
NSString *_videoId;
bool _started;
bool _initiallyPlayed;
NSInteger _ignorePositionUpdates;
}
@end
@implementation TGEmbedVimeoPlayerView
- (instancetype)initWithWebPageAttachment:(TGWebPageMediaAttachment *)webPage thumbnailSignal:(SSignal *)thumbnailSignal alternateCachePathSignal:(SSignal *)alternateCachePathSignal
{
self = [super initWithWebPageAttachment:webPage thumbnailSignal:thumbnailSignal alternateCachePathSignal:alternateCachePathSignal];
if (self != nil)
{
_videoId = [TGEmbedVimeoPlayerView _vimeoVideoIdFromText:webPage.embedUrl];
}
return self;
}
- (void)playVideo
{
[super playVideo];
if (_initiallyPlayed)
{
[self _evaluateJS:@"player.api('play');" completion:nil];
}
else
{
[self _evaluateJS:@"injectCmd('initialPlay')" completion:nil];
_initiallyPlayed = true;
}
_ignorePositionUpdates = 2;
}
- (void)pauseVideo:(bool)manually
{
[super pauseVideo:manually];
[self _evaluateJS:@"player.api('pause');" completion:nil];
}
- (void)seekToPosition:(NSTimeInterval)position
{
NSString *command = [NSString stringWithFormat:@"player.api('seekTo', %@);", @(position)];
[self _evaluateJS:command completion:nil];
TGEmbedPlayerState *newState = [TGEmbedPlayerState stateWithPlaying:self.state.isPlaying duration:self.state.duration position:position downloadProgress:self.state.downloadProgress buffering:self.state.buffering];
[self updateState:newState];
_ignorePositionUpdates = 2;
}
- (TGEmbedPlayerControlsType)_controlsType
{
return TGEmbedPlayerControlsTypeFull;
}
- (void)_onPageReady
{
}
- (void)_didBeginPlayback
{
[super _didBeginPlayback];
TGDispatchAfter(0.5, dispatch_get_main_queue(), ^
{
[self setDimmed:false animated:true shouldDelay:false];
});
}
- (void)_notifyOfCallbackURL:(NSURL *)url
{
NSString *action = url.host;
NSString *query = url.query;
NSString *data;
if (query != nil)
data = [query componentsSeparatedByString:@"="][1];
if ([action isEqual:TGVimeoPlayerCallbackOnReady])
{
}
else if ([action isEqualToString:TGVimeoPlayerCallbackOnState])
{
NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:false];
NSArray *queryItems = urlComponents.queryItems;
bool playing = self.state.playing;
bool finished = false;
NSTimeInterval position = self.state.position;
NSTimeInterval duration = self.state.duration;
CGFloat downloadProgress = self.state.downloadProgress;
bool buffering = self.state.buffering;
for (NSURLQueryItem *queryItem in queryItems)
{
if ([queryItem.name isEqualToString:@"playback"])
{
playing = ([queryItem.value integerValue] == 1);
finished = ([queryItem.value integerValue] == 2);
}
else if ([queryItem.name isEqualToString:@"position"])
{
if (_ignorePositionUpdates > 0)
_ignorePositionUpdates--;
else
position = [queryItem.value doubleValue];
}
else if ([queryItem.name isEqualToString:@"duration"])
{
duration = [queryItem.value doubleValue];
}
else if ([queryItem.name isEqualToString:@"download"])
{
downloadProgress = [queryItem.value floatValue];
}
}
if (!_started && playing)
{
_started = true;
[self _didBeginPlayback];
}
if (finished)
position = 0.0;
TGEmbedPlayerState *newState = [TGEmbedPlayerState stateWithPlaying:playing duration:duration position:position downloadProgress:downloadProgress buffering:buffering];
[self updateState:newState];
}
}
- (NSString *)_embedHTML
{
NSError *error = nil;
NSString *path = TGComponentsPathForResource(@"VimeoPlayer", @"html");
NSString *embedHTMLTemplate = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
if (error != nil)
{
TGLegacyLog(@"[VimeoEmbedPlayer]: Received error rendering template: %@", error);
return nil;
}
NSString *autoplay = self.disallowAutoplay ? @"false" : @"true";
NSString *embedHTML = [NSString stringWithFormat:embedHTMLTemplate, _videoId, autoplay];
return embedHTML;
}
- (NSURL *)_baseURL
{
return [NSURL URLWithString:@"https://player.vimeo.com/"];
}
- (void)_setupUserScripts:(WKUserContentController *)contentController
{
NSError *error = nil;
NSString *path = TGComponentsPathForResource(@"VimeoPlayerInject", @"js");
NSString *scriptText = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
if (error != nil)
TGLegacyLog(@"[VimeoEmbedPlayer]: Received error loading inject script: %@", error);
WKUserScript *script = [[WKUserScript alloc] initWithSource:scriptText injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:false];
[contentController addUserScript:script];
}
- (bool)_scaleViewToMaxSize
{
return true;
}
+ (NSString *)_vimeoVideoIdFromText:(NSString *)text
{
if ([text hasPrefix:@"http://player.vimeo.com/video/"] || [text hasPrefix:@"https://player.vimeo.com/video/"])
{
NSString *suffix = @"";
NSMutableArray *prefixes = [NSMutableArray arrayWithArray:@
[
@"http://player.vimeo.com/video/",
@"https://player.vimeo.com/video/"
]];
while (suffix.length == 0 && prefixes.count > 0)
{
NSString *prefix = prefixes.firstObject;
if ([text hasPrefix:prefix])
{
suffix = [text substringFromIndex:prefix.length];
break;
}
else
{
[prefixes removeObjectAtIndex:0];
}
}
for (int i = 0; i < (int)suffix.length; i++)
{
unichar c = [suffix characterAtIndex:i];
if (!((c >= '0' && c <= '9')))
break;
}
return suffix;
}
return nil;
}
+ (bool)_supportsWebPage:(TGWebPageMediaAttachment *)webPage
{
NSString *url = webPage.embedUrl;
return ([url hasPrefix:@"http://player.vimeo.com/video/"] || [url hasPrefix:@"https://player.vimeo.com/video/"]);
}
@end

View File

@ -1,8 +0,0 @@
#import "TGEmbedPlayerView.h"
@interface TGEmbedVinePlayerView : TGEmbedPlayerView
+ (NSString *)_vineVideoIdFromText:(NSString *)text;
+ (NSString *)_vineIdFromPermalink:(NSString *)text;
@end

View File

@ -1,360 +0,0 @@
#import "TGEmbedVinePlayerView.h"
#import "TGEmbedPlayerState.h"
#import "LegacyComponentsInternal.h"
#import <AVFoundation/AVFoundation.h>
#import <LegacyComponents/TGModernGalleryVideoView.h>
#import <LegacyComponents/TGTimerTarget.h>
NSString *const TGVinePlayerCallbackOnPlayback = @"onPlayback";
@interface TGEmbedVinePlayerView ()
{
NSString *_videoId;
bool _started;
AVPlayer *_player;
TGModernGalleryVideoView *_videoView;
UIImageView *_watermarkView;
id _playerStartedObserver;
id _playerEndedObserver;
}
@end
@implementation TGEmbedVinePlayerView
- (instancetype)initWithWebPageAttachment:(TGWebPageMediaAttachment *)webPage thumbnailSignal:(SSignal *)thumbnailSignal alternateCachePathSignal:(SSignal *)alternateCachePathSignal
{
self = [super initWithWebPageAttachment:webPage thumbnailSignal:thumbnailSignal alternateCachePathSignal:alternateCachePathSignal];
if (self != nil)
{
_videoId = [TGEmbedVinePlayerView _vineVideoIdFromText:webPage.embedUrl];
self.controlsView.watermarkImage = TGComponentsImageNamed(@"VineWatermark");
self.controlsView.watermarkPrerenderedOpacity = true;
self.controlsView.watermarkOffset = CGPointMake(12.0f, 12.0f);
}
return self;
}
- (void)_watermarkAction
{
[super _watermarkAction];
if (self.onWatermarkAction != nil)
self.onWatermarkAction();
NSString *videoId = _videoId;
NSURL *appUrl = [[NSURL alloc] initWithString:[[NSString alloc] initWithFormat:@"vine://post/%@", videoId]];
if ([[LegacyComponentsGlobals provider] canOpenURL:appUrl])
{
[[LegacyComponentsGlobals provider] openURL:appUrl];
return;
}
NSURL *webUrl = [NSURL URLWithString:[NSString stringWithFormat:@"https://vine.co/v/%@", videoId]];
[[LegacyComponentsGlobals provider] openURL:webUrl];
}
- (void)playVideo
{
[_player play];
TGEmbedPlayerState *state = [TGEmbedPlayerState stateWithPlaying:true];
[self updateState:state];
}
- (void)pauseVideo:(bool)manually
{
[super pauseVideo:manually];
[_player pause];
TGEmbedPlayerState *state = [TGEmbedPlayerState stateWithPlaying:false];
[self updateState:state];
}
- (TGEmbedPlayerControlsType)_controlsType
{
return TGEmbedPlayerControlsTypeSimple;
}
- (void)_onPageReady
{
}
- (void)_didBeginPlayback
{
[super _didBeginPlayback];
[self setDimmed:false animated:true shouldDelay:false];
}
- (void)_notifyOfCallbackURL:(NSURL *)url
{
NSString *action = url.host;
NSString *query = url.query;
NSString *data;
if (query != nil)
{
NSArray *components = [query componentsSeparatedByString:@"="];
if (components.count > 1)
data = [query substringFromIndex:[components.firstObject length] + 1];
}
if ([action isEqual:TGVinePlayerCallbackOnPlayback])
{
if (!_started)
{
_started = true;
[self _didBeginPlayback];
}
}
else if ([action isEqualToString:@"onSrc"] && data != nil)
{
[self _setupCustomPlayerWithURL:[NSURL URLWithString:data]];
}
}
- (void)_setupCustomPlayerWithURL:(NSURL *)url
{
AVPlayerItem *item = [AVPlayerItem playerItemWithURL:url];
AVPlayer *player = [AVPlayer playerWithPlayerItem:item];
_player = player;
UIView *currentView = [self _webView];
TGModernGalleryVideoView *videoView = [[TGModernGalleryVideoView alloc] initWithFrame:currentView.frame player:player];
[currentView.superview insertSubview:videoView aboveSubview:currentView];
[self _cleanWebView];
_videoView = videoView;
__weak TGEmbedVinePlayerView *weakSelf = self;
_playerStartedObserver = [player addBoundaryTimeObserverForTimes:@[[NSValue valueWithCMTime:CMTimeMake(10, 100)]] queue:NULL usingBlock:^
{
__strong TGEmbedVinePlayerView *strongSelf = weakSelf;
if (strongSelf != nil)
{
[strongSelf _didBeginPlayback];
TGEmbedPlayerState *state = [TGEmbedPlayerState stateWithPlaying:true];
[strongSelf updateState:state];
[strongSelf->_player removeTimeObserver:strongSelf->_playerStartedObserver];
strongSelf->_playerStartedObserver = nil;
if (CMTimeGetSeconds(strongSelf->_player.currentItem.duration) > 0)
[strongSelf _setupEndedObserver];
}
}];
[player play];
}
- (void)_setupEndedObserver
{
__weak TGEmbedVinePlayerView *weakSelf = self;
_playerEndedObserver = [_player addBoundaryTimeObserverForTimes:@[[NSValue valueWithCMTime:CMTimeSubtract(_player.currentItem.duration, CMTimeMake(10, 100))]] queue:NULL usingBlock:^
{
__strong TGEmbedVinePlayerView *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf->_player seekToTime:CMTimeMake(5, 100)];
}];
}
- (UIView *)_webView
{
if (_videoView != nil)
return _videoView;
return [super _webView];
}
- (NSString *)_embedHTML
{
NSError *error = nil;
NSString *path = TGComponentsPathForResource(@"VinePlayer", @"html");
NSString *embedHTMLTemplate = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
if (error != nil)
{
TGLegacyLog(@"[VineEmbedPlayer]: Received error rendering template: %@", error);
return nil;
}
NSString *embedHTML = [NSString stringWithFormat:embedHTMLTemplate, _videoId];
return embedHTML;
}
- (NSURL *)_baseURL
{
return [NSURL URLWithString:@"https://vine.co/"];
}
- (void)_setupUserScripts:(WKUserContentController *)contentController
{
NSError *error = nil;
NSString *path = TGComponentsPathForResource(@"VinePlayerInject", @"js");
NSString *scriptText = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
if (error != nil)
TGLegacyLog(@"[VineEmbedPlayer]: Received error loading inject script: %@", error);
WKUserScript *script = [[WKUserScript alloc] initWithSource:scriptText injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:false];
[contentController addUserScript:script];
}
+ (NSString *)_vineVideoIdFromText:(NSString *)text
{
if ([text hasPrefix:@"http://vine.co/v/"] || [text hasPrefix:@"https://vine.co/v/"])
{
NSString *suffix = @"";
NSMutableArray *prefixes = [NSMutableArray arrayWithArray:@
[
@"http://vine.co/v/",
@"https://vine.co/v/"
]];
while (suffix.length == 0 && prefixes.count > 0)
{
NSString *prefix = prefixes.firstObject;
if ([text hasPrefix:prefix])
{
suffix = [text substringFromIndex:prefix.length];
break;
}
else
{
[prefixes removeObjectAtIndex:0];
}
}
int end = -1;
for (int i = 0; i < (int)suffix.length; i++)
{
unichar c = [suffix characterAtIndex:i];
if (c == '/')
{
end = i;
break;
}
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '=' || c == '&' || c == '#'))
return nil;
}
if (end != - 1)
suffix = [suffix substringToIndex:end];
return suffix;
}
return nil;
}
+ (NSString *)_vineIdFromPermalink:(NSString *)text
{
static dispatch_once_t onceToken;
static NSDictionary *map = nil;
dispatch_once(&onceToken, ^
{
map = @
{
@"B" : @"0",
@"u" : @"1",
@"z" : @"2",
@"a" : @"3",
@"W" : @"4",
@"7" : @"5",
@"Z" : @"6",
@"m" : @"7",
@"K" : @"8",
@"A" : @"9",
@"q" : @"a",
@"U" : @"A",
@"b" : @"b",
@"P" : @"B",
@"h" : @"c",
@"x" : @"C",
@"M" : @"d",
@"Q" : @"D",
@"2" : @"E",
@"O" : @"e",
@"0" : @"F",
@"e" : @"f",
@"E" : @"G",
@"i" : @"g",
@"5" : @"h",
@"9" : @"H",
@"J" : @"i",
@"V" : @"I",
@"1" : @"j",
@"Y" : @"J",
@"3" : @"K",
@"n" : @"k",
@"L" : @"L",
@"v" : @"l",
@"l" : @"M",
@"r" : @"m",
@"6" : @"n",
@"g" : @"o",
@"X" : @"p",
@"H" : @"q",
@"w" : @"r",
@"d" : @"s",
@"p" : @"t",
@"D" : @"u",
@"j" : @"v",
@"I" : @"w",
@"T" : @"x",
@"t" : @"y",
@"F" : @"z"
};
});
NSMutableString *shiftedString = [text mutableCopy];
for (NSUInteger i = 0; i < shiftedString.length; i++)
{
NSString *charStr = [shiftedString substringWithRange:NSMakeRange(i, 1)];
NSString *mappedStr = map[charStr];
[shiftedString replaceCharactersInRange:NSMakeRange(i, 1) withString:mappedStr];
}
return [NSString stringWithFormat:@"%lld", [self _convertString:shiftedString fromBase:49]];
}
+ (int64_t)_convertString:(NSString *)string fromBase:(NSInteger)fromBase
{
NSString *base = @"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
NSUInteger limit = string.length;
int64_t res = [base rangeOfString:[string substringWithRange:NSMakeRange(0, 1)]].location;
if (res == NSNotFound)
return 0;
for (NSUInteger i = 1; i < limit; i++)
{
NSInteger a = [base rangeOfString:[string substringWithRange:NSMakeRange(i, 1)]].location;
if (a == NSNotFound)
return 0;
res = fromBase * res + a;
}
return res;
}
+ (bool)_supportsWebPage:(TGWebPageMediaAttachment *)webPage
{
NSString *url = webPage.embedUrl;
return ([url hasPrefix:@"http://vine.co/v/"] || [url hasPrefix:@"https://vine.co/v/"]);
}
@end

View File

@ -1,7 +0,0 @@
#import "TGEmbedPlayerView.h"
@interface TGEmbedYoutubePlayerView : TGEmbedPlayerView
+ (NSString *)_youtubeVideoIdFromText:(NSString *)text originalUrl:(NSString *)originalUrl startTime:(NSTimeInterval *)startTime;
@end

View File

@ -1,473 +0,0 @@
#import "TGEmbedYoutubePlayerView.h"
#import "TGEmbedPlayerState.h"
#import "LegacyComponentsInternal.h"
NSString *const TGYTPlayerCallbackOnReady = @"onReady";
NSString *const TGYTPlayerCallbackOnState = @"onState";
NSString *const TGYTPlayerCallbackOnPlaybackQualityChange = @"onPlaybackQualityChange";
NSString *const TGYTPlayerCallbackOnError = @"onError";
const NSInteger TGYTPlayerStateUnstartedCode = -1;
const NSInteger TGYTPlayerStateEndedCode = 0;
const NSInteger TGYTPlayerStatePlayingCode = 1;
const NSInteger TGYTPlayerStatePausedCode = 2;
const NSInteger TGYTPlayerStateBufferingCode = 3;
@interface TGEmbedYoutubePlayerView ()
{
NSDictionary *_playerParams;
bool _started;
bool _failed;
bool _ready;
bool _playOnReady;
NSInteger _playAfterTicks;
NSInteger _ignorePositionUpdates;
}
@end
@implementation TGEmbedYoutubePlayerView
- (instancetype)initWithWebPageAttachment:(TGWebPageMediaAttachment *)webPage thumbnailSignal:(SSignal *)thumbnailSignal alternateCachePathSignal:(SSignal *)alternateCachePathSignal
{
self = [super initWithWebPageAttachment:webPage thumbnailSignal:thumbnailSignal alternateCachePathSignal:alternateCachePathSignal];
if (self != nil)
{
NSTimeInterval start = 0.0;
NSString *videoId = [TGEmbedYoutubePlayerView _youtubeVideoIdFromText:webPage.embedUrl originalUrl:webPage.url startTime:&start];
_playerParams = @
{
@"videoId": videoId,
@"playerVars": @
{
@"cc_load_policy" : @1,
@"iv_load_policy" : @3,
@"controls" : @0,
@"playsinline" : @1,
@"autohide" : @1,
@"showinfo" : @0,
@"rel" : @0,
@"modestbranding" : @1,
@"start" : @((NSInteger)start)
}
};
self.controlsView.watermarkImage = TGComponentsImageNamed(@"YoutubeWatermark");
self.controlsView.watermarkPosition = TGEmbedPlayerWatermarkPositionBottomRight;
self.controlsView.watermarkOffset = CGPointMake(-12.0f, -12.0f);
}
return self;
}
- (void)_watermarkAction
{
[super _watermarkAction];
if (self.onWatermarkAction != nil)
self.onWatermarkAction();
NSString *videoId = _playerParams[@"videoId"];
NSURL *appUrl = [[NSURL alloc] initWithString:[[NSString alloc] initWithFormat:@"youtube://watch?v=%@", videoId]];
if ([[LegacyComponentsGlobals provider] canOpenURL:appUrl])
{
[[LegacyComponentsGlobals provider] openURL:appUrl];
return;
}
NSURL *webUrl = [NSURL URLWithString:[NSString stringWithFormat:@"https://youtube.com/watch?v=%@", videoId]];
[[LegacyComponentsGlobals provider] openURL:webUrl];
}
- (void)playVideo
{
if (!_ready && self.disallowAutoplay)
{
_playOnReady = true;
_playAfterTicks = 2;
return;
}
[super playVideo];
[self _evaluateJS:@"player.playVideo();" completion:nil];
_ignorePositionUpdates = 2;
}
- (void)pauseVideo:(bool)manually
{
[super pauseVideo:manually];
[self _evaluateJS:@"player.pauseVideo();" completion:nil];
}
- (void)seekToPosition:(NSTimeInterval)position
{
NSString *command = [NSString stringWithFormat:@"player.seekTo(%@, true);", @(position)];
[self _evaluateJS:command completion:nil];
TGEmbedPlayerState *newState = [TGEmbedPlayerState stateWithPlaying:self.state.isPlaying duration:self.state.duration position:position downloadProgress:self.state.downloadProgress buffering:self.state.buffering];
[self updateState:newState];
_ignorePositionUpdates = 2;
}
- (TGEmbedPlayerControlsType)_controlsType
{
return TGEmbedPlayerControlsTypeFull;
}
- (void)_onPageReady
{
}
- (void)_didBeginPlayback
{
[super _didBeginPlayback];
[self setDimmed:false animated:true shouldDelay:false];
}
- (void)_notifyOfCallbackURL:(NSURL *)url
{
NSString *action = url.host;
NSString *query = url.query;
NSString *data;
if (query != nil)
data = [query componentsSeparatedByString:@"="][1];
if ([action isEqualToString:TGYTPlayerCallbackOnState])
{
if (_failed)
return;
NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:false];
NSArray *queryItems = urlComponents.queryItems;
bool failed = _failed;
bool playing = self.state.playing;
bool finished = false;
NSTimeInterval position = self.state.position;
NSTimeInterval duration = self.state.duration;
CGFloat downloadProgress = self.state.downloadProgress;
bool buffering = self.state.buffering;
for (NSURLQueryItem *queryItem in queryItems)
{
if ([queryItem.name isEqualToString:@"playback"])
{
playing = ([queryItem.value integerValue] == TGYTPlayerStatePlayingCode);
finished = ([queryItem.value integerValue] == TGYTPlayerStateEndedCode);
buffering = ([queryItem.value integerValue] == TGYTPlayerStateBufferingCode);
}
else if ([queryItem.name isEqualToString:@"position"])
{
if (_ignorePositionUpdates > 0)
_ignorePositionUpdates--;
else
position = [queryItem.value doubleValue];
}
else if ([queryItem.name isEqualToString:@"duration"])
{
duration = [queryItem.value doubleValue];
}
else if ([queryItem.name isEqualToString:@"download"])
{
downloadProgress = MAX(0.0f, MIN(1.0, [queryItem.value floatValue]));
}
else if ([queryItem.name isEqualToString:@"failed"])
{
failed = [queryItem.value boolValue];
}
}
if (failed && !_failed)
{
_failed = true;
[self setDimmed:false animated:true shouldDelay:false];
[self.controlsView setDisabled];
}
if (playing && !_started)
{
_started = true;
[self _didBeginPlayback];
}
if (finished)
position = 0.0;
TGEmbedPlayerState *newState = [TGEmbedPlayerState stateWithPlaying:playing duration:duration position:position downloadProgress:downloadProgress buffering:buffering];
[self updateState:newState];
if (_playAfterTicks > 0)
{
_playAfterTicks--;
if (_playAfterTicks == 0)
{
_ready = true;
[self playVideo];
}
}
}
else if ([action isEqualToString:TGYTPlayerCallbackOnReady])
{
_ready = true;
if (_playOnReady)
{
_playAfterTicks = 0;
_playOnReady = false;
[self playVideo];
}
if (!self.disallowAutoplay)
{
dispatch_async(dispatch_get_main_queue(), ^
{
[self playVideo];
TGDispatchAfter(2.0, dispatch_get_main_queue(), ^{
if (!_started)
[self playVideo];
});
});
}
}
}
- (NSString *)_embedHTML
{
NSDictionary *playerCallbacks = @
{
@"onReady" : @"onReady",
@"onStateChange" : @"onStateChange",
@"onPlaybackQualityChange" : @"onPlaybackQualityChange",
@"onError" : @"onPlayerError"
};
NSMutableDictionary *playerParams = [[NSMutableDictionary alloc] init];
[playerParams addEntriesFromDictionary:_playerParams];
if (![playerParams objectForKey:@"height"])
[playerParams setValue:@"100%" forKey:@"height"];
if (![playerParams objectForKey:@"width"])
[playerParams setValue:@"100%" forKey:@"width"];
[playerParams setValue:playerCallbacks forKey:@"events"];
if ([playerParams objectForKey:@"playerVars"])
{
NSMutableDictionary *playerVars = [[NSMutableDictionary alloc] init];
[playerVars addEntriesFromDictionary:[playerParams objectForKey:@"playerVars"]];
}
else
{
[playerParams setValue:[[NSDictionary alloc] init] forKey:@"playerVars"];
}
NSError *error = nil;
NSString *path = TGComponentsPathForResource(@"YoutubePlayer", @"html");
NSString *embedHTMLTemplate = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
if (error != nil)
{
TGLegacyLog(@"[YTEmbedPlayer]: Received error rendering template: %@", error);
return nil;
}
NSError *jsonRenderingError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:playerParams options:NSJSONWritingPrettyPrinted error:&jsonRenderingError];
if (jsonRenderingError != nil)
{
NSLog(@"[YTEmbedPlayer]: Attempted configuration of player with invalid playerVars: %@ \tError: %@", playerParams, jsonRenderingError);
return nil;
}
NSString *playerVarsJsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSString *autoplay = self.disallowAutoplay ? @"false" : @"true";
NSString *embedHTML = [NSString stringWithFormat:embedHTMLTemplate, playerVarsJsonString, autoplay];
return embedHTML;
}
- (NSURL *)_baseURL
{
return [NSURL URLWithString:@"https://youtube.com/"];
}
- (void)_setupUserScripts:(WKUserContentController *)contentController
{
NSError *error = nil;
NSString *path = TGComponentsPathForResource(@"YoutubePlayerInject", @"js");
NSString *scriptText = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
if (error != nil)
TGLegacyLog(@"[YTEmbedPlayer]: Received error loading inject script: %@", error);
WKUserScript *script = [[WKUserScript alloc] initWithSource:scriptText injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:false];
[contentController addUserScript:script];
}
- (bool)_scaleViewToMaxSize
{
return true;
}
- (CGFloat)_compensationEdges
{
return 3.0f;
}
+ (NSString *)_youtubeVideoIdFromText:(NSString *)text originalUrl:(NSString *)originalUrl startTime:(NSTimeInterval *)startTime
{
if ([text hasPrefix:@"http://www.youtube.com/watch?v="] || [text hasPrefix:@"https://www.youtube.com/watch?v="] || [text hasPrefix:@"http://m.youtube.com/watch?v="] || [text hasPrefix:@"https://m.youtube.com/watch?v="])
{
NSRange range1 = [text rangeOfString:@"?v="];
bool match = true;
for (NSInteger i = range1.location + range1.length; i < (NSInteger)text.length; i++)
{
unichar c = [text characterAtIndex:i];
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '=' || c == '&' || c == '#'))
{
match = false;
break;
}
}
if (match)
{
NSString *videoId = nil;
NSRange ampRange = [text rangeOfString:@"&"];
NSRange hashRange = [text rangeOfString:@"#"];
if (ampRange.location != NSNotFound || hashRange.location != NSNotFound)
{
NSInteger location = MIN(ampRange.location, hashRange.location);
videoId = [text substringWithRange:NSMakeRange(range1.location + range1.length, location - range1.location - range1.length)];
}
else
videoId = [text substringFromIndex:range1.location + range1.length];
if (videoId.length != 0)
return videoId;
}
}
else if ([text hasPrefix:@"http://youtu.be/"] || [text hasPrefix:@"https://youtu.be/"] || [text hasPrefix:@"http://www.youtube.com/embed/"] || [text hasPrefix:@"https://www.youtube.com/embed/"])
{
NSString *suffix = @"";
NSMutableArray *prefixes = [NSMutableArray arrayWithArray:@
[
@"http://youtu.be/",
@"https://youtu.be/",
@"http://www.youtube.com/embed/",
@"https://www.youtube.com/embed/"
]];
while (suffix.length == 0 && prefixes.count > 0)
{
NSString *prefix = prefixes.firstObject;
if ([text hasPrefix:prefix])
{
suffix = [text substringFromIndex:prefix.length];
break;
}
else
{
[prefixes removeObjectAtIndex:0];
}
}
NSString *queryString = nil;
for (int i = 0; i < (int)suffix.length; i++)
{
unichar c = [suffix characterAtIndex:i];
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '=' || c == '&' || c == '#'))
{
if (c == '?')
{
queryString = [suffix substringFromIndex:i + 1];
suffix = [suffix substringToIndex:i];
break;
}
else
{
return nil;
}
}
}
if (startTime != NULL)
{
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
NSString *queryString = [NSURL URLWithString:originalUrl].query;
for (NSString *param in [queryString componentsSeparatedByString:@"&"])
{
NSArray *components = [param componentsSeparatedByString:@"="];
if (components.count < 2)
continue;
[params setObject:components.lastObject forKey:components.firstObject];
}
NSString *timeParam = params[@"t"];
if (timeParam == nil)
timeParam = params[@"time_continue"];
if (timeParam != nil)
{
NSTimeInterval position = 0.0;
if ([timeParam rangeOfString:@"s"].location != NSNotFound)
{
NSString *value;
NSUInteger location = 0;
for (NSUInteger i = 0; i < timeParam.length; i++)
{
unichar c = [timeParam characterAtIndex:i];
if ((c < '0' || c > '9'))
{
value = [timeParam substringWithRange:NSMakeRange(location, i - location)];
location = i + 1;
switch (c)
{
case 's':
position += value.doubleValue;
break;
case 'm':
position += value.doubleValue * 60.0;
break;
case 'h':
position += value.doubleValue * 3600.0;
break;
default:
break;
}
}
}
}
else
{
position = timeParam.doubleValue;
}
*startTime = position;
}
}
return suffix;
}
return nil;
}
+ (bool)_supportsWebPage:(TGWebPageMediaAttachment *)webPage
{
NSString *url = webPage.embedUrl;
if ([url rangeOfString:@"list"].location != NSNotFound)
return false;
return ([url hasPrefix:@"http://www.youtube.com/watch?v="] || [url hasPrefix:@"https://www.youtube.com/watch?v="] || [url hasPrefix:@"http://m.youtube.com/watch?v="] || [url hasPrefix:@"https://m.youtube.com/watch?v="] || [url hasPrefix:@"http://youtu.be/"] || [url hasPrefix:@"https://youtu.be/"] || [url hasPrefix:@"https://www.youtube.com/embed/"]);
}
@end

View File

@ -1,183 +0,0 @@
#import <LegacyComponents/TGImagePickerController.h>
#import <AssetsLibrary/AssetsLibrary.h>
#import <SSignalKit/SSignalKit.h>
#import "LegacyComponentsInternal.h"
static const char *assetsProcessingQueueSpecific = "assetsProcessingQueue";
static dispatch_queue_t assetsProcessingQueue()
{
static dispatch_queue_t queue = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
queue = dispatch_queue_create("com.tg.assetsqueue", 0);
dispatch_queue_set_specific(queue, assetsProcessingQueueSpecific, (void *)assetsProcessingQueueSpecific, NULL);
});
return queue;
}
void dispatchOnAssetsProcessingQueue(dispatch_block_t block)
{
bool isCurrentQueueAssetsProcessingQueue = false;
isCurrentQueueAssetsProcessingQueue = dispatch_get_specific(assetsProcessingQueueSpecific) != NULL;
if (isCurrentQueueAssetsProcessingQueue)
block();
else
dispatch_async(assetsProcessingQueue(), block);
}
static ALAssetsLibrary *sharedLibrary = nil;
static STimer *sharedLibraryReleaseTimer = nil;
static int sharedLibraryRetainCount = 0;
void sharedAssetsLibraryRetain()
{
dispatchOnAssetsProcessingQueue(^
{
if (sharedLibraryReleaseTimer != nil)
{
[sharedLibraryReleaseTimer invalidate];
sharedLibraryReleaseTimer = nil;
}
if (sharedLibrary == nil)
{
TGLegacyLog(@"Preloading shared assets library");
sharedLibraryRetainCount = 1;
sharedLibrary = [[ALAssetsLibrary alloc] init];
if (iosMajorVersion() == 5)
[sharedLibrary writeImageToSavedPhotosAlbum:nil metadata:nil completionBlock:^(__unused NSURL *assetURL, __unused NSError *error) { }];
[sharedLibrary enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop)
{
if (group != nil)
{
if (stop != NULL)
*stop = true;
[group setAssetsFilter:[ALAssetsFilter allPhotos]];
[group numberOfAssets];
}
} failureBlock:^(__unused NSError *error)
{
TGLegacyLog(@"assets access error");
}];
}
else
sharedLibraryRetainCount++;
});
}
void sharedAssetsLibraryRelease()
{
dispatchOnAssetsProcessingQueue(^
{
sharedLibraryRetainCount--;
if (sharedLibraryRetainCount <= 0)
{
sharedLibraryRetainCount = 0;
if (sharedLibraryReleaseTimer != nil)
{
[sharedLibraryReleaseTimer invalidate];
sharedLibraryReleaseTimer = nil;
}
sharedLibraryReleaseTimer = [[STimer alloc] initWithTimeout:4 repeat:false completion:^
{
sharedLibraryReleaseTimer = nil;
TGLegacyLog(@"Destroyed shared assets library");
sharedLibrary = nil;
} nativeQueue:assetsProcessingQueue()];
[sharedLibraryReleaseTimer start];
}
});
}
@interface TGAssetsLibraryHolder : NSObject
@end
@implementation TGAssetsLibraryHolder
- (void)dealloc
{
sharedAssetsLibraryRelease();
}
@end
@interface TGImagePickerController ()
@end
@implementation TGImagePickerController
+ (id)sharedAssetsLibrary
{
return sharedLibrary;
}
+ (id)preloadLibrary
{
dispatchOnAssetsProcessingQueue(^
{
if ([(id)[ALAssetsLibrary class] respondsToSelector:@selector(authorizationStatus)])
{
if ([ALAssetsLibrary authorizationStatus] != ALAuthorizationStatusAuthorized)
return;
}
sharedAssetsLibraryRetain();
});
TGAssetsLibraryHolder *libraryHolder = [[TGAssetsLibraryHolder alloc] init];
return libraryHolder;
}
+ (void)loadAssetWithUrl:(NSURL *)url completion:(void (^)(ALAsset *asset))completion
{
dispatchOnAssetsProcessingQueue(^
{
if (sharedLibrary != nil)
{
[sharedLibrary assetForURL:url resultBlock:^(ALAsset *asset)
{
if (completion)
completion(asset);
} failureBlock:^(__unused NSError *error)
{
if (completion)
completion(nil);
}];
}
else
{
if (completion)
completion(nil);
}
});
}
+ (void)storeImageAsset:(NSData *)data
{
dispatchOnAssetsProcessingQueue(^
{
ALAssetsLibrary *library = sharedLibrary;
if (library == nil)
library = [[ALAssetsLibrary alloc] init];
[library writeImageDataToSavedPhotosAlbum:data metadata:nil completionBlock:nil];
});
}
@end

View File

@ -1,546 +0,0 @@
#import "TGLegacyCameraController.h"
#import "LegacyComponentsInternal.h"
#import "TGHacks.h"
#import "TGImageUtils.h"
#import <AVFoundation/AVFoundation.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import <CommonCrypto/CommonDigest.h>
#import <LegacyComponents/TGProgressWindow.h>
#import "TGLegacyMediaPickerTipView.h"
#import "TGNavigationBar.h"
#import <LegacyComponents/TGImagePickerController.h>
@interface UINavigationBar (Border)
- (void)setBottomBorderColor:(UIColor *)color;
@end
@interface TGLegacyCameraController () <UINavigationControllerDelegate, UIImagePickerControllerDelegate>
{
TGProgressWindow *_progressWindow;
bool _didShowTip;
id<LegacyComponentsContext> _context;
}
@end
@implementation TGLegacyCameraController
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context {
self = [super init];
if (self != nil) {
_context = context;
}
return self;
}
- (void)setAvatarMode:(bool)avatarMode
{
_avatarMode = avatarMode;
self.allowsEditing = _avatarMode;
}
- (void)loadView
{
[super loadView];
self.delegate = self;
}
- (UIStatusBarStyle)preferredStatusBarStyle
{
if (![_context rootCallStatusBarHidden])
{
return UIStatusBarStyleLightContent;
}
else
{
if ([_context respondsToSelector:@selector(prefersLightStatusBar)])
return [_context prefersLightStatusBar] ? UIStatusBarStyleLightContent : UIStatusBarStyleDefault;
else
return UIStatusBarStyleDefault;
}
}
- (BOOL)prefersStatusBarHidden
{
return false;
}
- (BOOL)shouldAutorotate
{
return false;
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
}
- (void)viewWillAppear:(BOOL)animated
{
if (!_didShowTip && _isInDocumentMode)
{
if (![[[NSUserDefaults standardUserDefaults] objectForKey:@"didShowDocumentPickerTip"] boolValue])
{
[[NSUserDefaults standardUserDefaults] setObject:@true forKey:@"didShowDocumentPickerTip"];
_didShowTip = true;
TGLegacyMediaPickerTipView *tipView = [[TGLegacyMediaPickerTipView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.view.bounds.size.width, self.view.bounds.size.height)];
tipView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:tipView];
}
}
if (self.sourceType == UIImagePickerControllerSourceTypeCamera)
{
if (iosMajorVersion() >= 7 && !_isInDocumentMode)
{
if (animated)
{
[UIView animateWithDuration:0.3 animations:^
{
[_context setApplicationStatusBarAlpha:0.0f];
}];
}
else
[_context setApplicationStatusBarAlpha:0.0f];
}
}
else
{
if ([[LegacyComponentsGlobals provider] respondsToSelector:@selector(navigationBarPallete)])
{
TGNavigationBarPallete *pallete = [[LegacyComponentsGlobals provider] navigationBarPallete];
UINavigationBar *navigationBar = self.navigationBar;
if (iosMajorVersion() >= 7)
{
navigationBar.translucent = false;
navigationBar.barTintColor = pallete.backgroundColor;
navigationBar.tintColor = pallete.tintColor;
navigationBar.titleTextAttributes = @{ NSForegroundColorAttributeName: pallete.titleColor };
navigationBar.bottomBorderColor = pallete.separatorColor;
}
}
}
[super viewWillAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated
{
if (self.sourceType == UIImagePickerControllerSourceTypeCamera)
{
if (iosMajorVersion() >= 7 && !_isInDocumentMode)
{
if (animated)
{
[UIView animateWithDuration:0.3 animations:^
{
[_context setApplicationStatusBarAlpha:1.0f];
}];
}
else
[_context setApplicationStatusBarAlpha:1.0f];
}
}
[super viewWillDisappear:animated];
}
- (void)dealloc
{
if (_progressWindow != nil)
{
[_progressWindow dismiss:true];
_progressWindow = nil;
}
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)__unused picker
{
id<TGLegacyCameraControllerDelegate> delegate = _completionDelegate;
[delegate legacyCameraControllerCompletedWithNoResult];
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType];
NSURL *referenceUrl = [info objectForKey:UIImagePickerControllerReferenceURL];
if ([mediaType isEqualToString:(__bridge NSString *)kUTTypeImage])
{
//if (picker.sourceType == UIImagePickerControllerSourceTypeCamera)
// defaultFlashMode = picker.cameraFlashMode;
if (_avatarMode)
{
CGRect cropRect = [[info objectForKey:UIImagePickerControllerCropRect] CGRectValue];
if (ABS(cropRect.size.width - cropRect.size.height) > FLT_EPSILON)
{
if (cropRect.size.width < cropRect.size.height)
{
cropRect.origin.x -= (cropRect.size.height - cropRect.size.width) / 2;
cropRect.size.width = cropRect.size.height;
}
else
{
cropRect.origin.y -= (cropRect.size.width - cropRect.size.height) / 2;
cropRect.size.height = cropRect.size.width;
}
}
id<TGLegacyCameraControllerDelegate> delegate = _completionDelegate;
UIImage *image = TGFixOrientationAndCrop([info objectForKey:UIImagePickerControllerOriginalImage], cropRect, CGSizeMake(600, 600));
if (image != nil)
[(id<TGImagePickerControllerDelegate>)delegate imagePickerController:nil didFinishPickingWithAssets:@[image]];
return;
}
id<TGLegacyCameraControllerDelegate> delegate = _completionDelegate;
if ([delegate conformsToProtocol:@protocol(TGImagePickerControllerDelegate)] || self.finishedWithImage != nil)
{
if (_isInDocumentMode)
{
NSURL *referenceUrl = info[UIImagePickerControllerReferenceURL];
if (referenceUrl != nil)
{
self.view.userInteractionEnabled = false;
id libraryToken = [TGImagePickerController preloadLibrary];
[TGImagePickerController loadAssetWithUrl:referenceUrl completion:^(ALAsset *asset)
{
if (asset != nil)
{
int64_t randomId = 0;
arc4random_buf(&randomId, sizeof(randomId));
NSString *tempFileName = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"%" PRIx64 ".bin", randomId]];
NSOutputStream *os = [[NSOutputStream alloc] initToFileAtPath:tempFileName append:false];
[os open];
ALAssetRepresentation *representation = asset.defaultRepresentation;
long long size = representation.size;
uint8_t buf[128 * 1024];
for (long long offset = 0; offset < size; offset += 128 * 1024)
{
long long batchSize = MIN(128 * 1024, size - offset);
NSUInteger readBytes = [representation getBytes:buf fromOffset:offset length:(NSUInteger)batchSize error:nil];
[os write:buf maxLength:readBytes];
}
[os close];
NSString *mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)[representation UTI], kUTTagClassMIMEType);
TGDispatchOnMainThread(^
{
[delegate legacyCameraControllerCompletedWithDocument:[NSURL fileURLWithPath:tempFileName] fileName:[representation filename] mimeType:mimeType];
});
}
else
{
TGDispatchOnMainThread(^
{
self.view.userInteractionEnabled = true;
});
}
[libraryToken class];
}];
}
}
else
{
UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
if (picker.sourceType == UIImagePickerControllerSourceTypeCamera && _storeCapturedAssets)
{
@autoreleasepool
{
UIImageWriteToSavedPhotosAlbum(image, nil, nil, NULL);
}
}
if (image != nil)
{
if (delegate != nil)
[(id<TGImagePickerControllerDelegate>)delegate imagePickerController:nil didFinishPickingWithAssets:@[image]];
else if (self.finishedWithImage != nil)
self.finishedWithImage(image);
}
}
}
}
else if ([mediaType isEqualToString:(__bridge NSString *)kUTTypeMovie])
{
id<TGLegacyCameraControllerDelegate> delegate = _completionDelegate;
if ([delegate conformsToProtocol:@protocol(TGImagePickerControllerDelegate)])
{
if (_isInDocumentMode)
{
NSURL *referenceUrl = info[UIImagePickerControllerReferenceURL];
if (referenceUrl != nil)
{
self.view.userInteractionEnabled = false;
id libraryToken = [TGImagePickerController preloadLibrary];
[TGImagePickerController loadAssetWithUrl:referenceUrl completion:^(ALAsset *asset)
{
if (asset != nil)
{
int64_t randomId = 0;
arc4random_buf(&randomId, sizeof(randomId));
NSString *tempFileName = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"%" PRIx64 ".bin", randomId]];
NSOutputStream *os = [[NSOutputStream alloc] initToFileAtPath:tempFileName append:false];
[os open];
ALAssetRepresentation *representation = asset.defaultRepresentation;
long long size = representation.size;
uint8_t buf[128 * 1024];
for (long long offset = 0; offset < size; offset += 128 * 1024)
{
long long batchSize = MIN(128 * 1024, size - offset);
NSUInteger readBytes = [representation getBytes:buf fromOffset:offset length:(NSUInteger)batchSize error:nil];
[os write:buf maxLength:readBytes];
}
[os close];
NSString *mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)[representation UTI], kUTTagClassMIMEType);
TGDispatchOnMainThread(^
{
[delegate legacyCameraControllerCompletedWithDocument:[NSURL fileURLWithPath:tempFileName] fileName:[representation filename] mimeType:mimeType];
});
}
else
{
TGDispatchOnMainThread(^
{
self.view.userInteractionEnabled = true;
});
}
[libraryToken class];
}];
}
}
else
{
_progressWindow = [[TGProgressWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[_progressWindow show:true];
NSURL *mediaUrl = [info objectForKey:UIImagePickerControllerMediaURL];
NSString *assetHash = nil;
if (_storeCapturedAssets && [referenceUrl absoluteString].length != 0)
{
assetHash = [[NSString alloc] initWithFormat:@"%@", [referenceUrl absoluteString]];
TGLegacyLog(@"Video hash: %@", assetHash);
}
bool deleteFile = true;
if (picker.sourceType == UIImagePickerControllerSourceTypeCamera && _storeCapturedAssets)
{
UISaveVideoAtPathToSavedPhotosAlbum(mediaUrl.path, [self class], @selector(video:didFinishSavingWithError:contextInfo:), NULL);
deleteFile = false;
}
NSString *videosPath = [[[LegacyComponentsGlobals provider] dataStoragePath] stringByAppendingPathComponent:@"video"];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
[fileManager createDirectoryAtPath:videosPath withIntermediateDirectories:true attributes:nil error:&error];
NSString *tmpPath = NSTemporaryDirectory();
int64_t fileId = 0;
arc4random_buf(&fileId, sizeof(fileId));
NSString *videoMp4FilePath = [tmpPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%" PRId64 ".mp4", fileId]];
[ActionStageInstance() dispatchOnStageQueue:^
{
NSDictionary *existingData = [_context serverMediaDataForAssetUrl:assetHash];
if (existingData != nil)
{
TGDispatchOnMainThread(^
{
[_progressWindow dismiss:true];
_progressWindow = nil;
id<TGLegacyCameraControllerDelegate> delegate = _completionDelegate;
[delegate legacyCameraControllerCompletedWithExistingMedia:existingData[@"videoAttachment"]];
});
}
else
{
AVAsset *avAsset = [[AVURLAsset alloc] initWithURL:mediaUrl options:nil];
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:AVAssetExportPresetPassthrough];
exportSession.outputURL = [NSURL fileURLWithPath:videoMp4FilePath];
exportSession.outputFileType = AVFileTypeMPEG4;
[exportSession exportAsynchronouslyWithCompletionHandler:^
{
bool endProcessing = false;
bool success = false;
switch ([exportSession status])
{
case AVAssetExportSessionStatusFailed:
NSLog(@"Export failed: %@", [[exportSession error] localizedDescription]);
endProcessing = true;
break;
case AVAssetExportSessionStatusCancelled:
endProcessing = true;
NSLog(@"Export canceled");
break;
case AVAssetExportSessionStatusCompleted:
{
TGLegacyLog(@"Export mp4 completed");
endProcessing = true;
success = true;
break;
}
default:
break;
}
if (endProcessing)
{
if (deleteFile)
[fileManager removeItemAtURL:mediaUrl error:nil];
if (success)
{
AVAsset *mp4Asset = [AVAsset assetWithURL:[NSURL fileURLWithPath:videoMp4FilePath]];
AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:mp4Asset];
imageGenerator.maximumSize = CGSizeMake(800, 800);
imageGenerator.appliesPreferredTrackTransform = true;
NSError *imageError = nil;
CGImageRef imageRef = [imageGenerator copyCGImageAtTime:CMTimeMake(0, mp4Asset.duration.timescale) actualTime:NULL error:&imageError];
UIImage *previewImage = [[UIImage alloc] initWithCGImage:imageRef];
if (imageRef != NULL)
CGImageRelease(imageRef);
if (error == nil && [[mp4Asset tracksWithMediaType:AVMediaTypeVideo] count] > 0)
{
AVAssetTrack *track = [[mp4Asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
CGSize trackNaturalSize = track.naturalSize;
CGSize naturalSize = CGRectApplyAffineTransform(CGRectMake(0, 0, trackNaturalSize.width, trackNaturalSize.height), track.preferredTransform).size;
NSTimeInterval duration = CMTimeGetSeconds(mp4Asset.duration);
NSDictionary *finalFileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:videoMp4FilePath error:nil];
int32_t fileSize = (int32_t)[[finalFileAttributes objectForKey:NSFileSize] intValue];
TGDispatchOnMainThread(^
{
[_progressWindow dismiss:true];
_progressWindow = nil;
id<TGLegacyCameraControllerDelegate> delegate = _completionDelegate;
[delegate legacyCameraControllerCapturedVideoWithTempFilePath:videoMp4FilePath fileSize:fileSize previewImage:previewImage duration:duration dimensions:naturalSize assetUrl:assetHash];
});
}
}
else
{
TGDispatchOnMainThread(^
{
[_progressWindow dismiss:true];
_progressWindow = nil;
id<TGLegacyCameraControllerDelegate> delegate = _completionDelegate;
[delegate legacyCameraControllerCompletedWithNoResult];
});
}
}
}];
}
}];
}
}
}
}
- (NSString *)_dictionaryString:(NSDictionary *)dict
{
NSMutableString *string = [[NSMutableString alloc] init];
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id value, __unused BOOL *stop)
{
if ([key isKindOfClass:[NSString class]])
[string appendString:key];
else if ([key isKindOfClass:[NSNumber class]])
[string appendString:[key description]];
[string appendString:@":"];
if ([value isKindOfClass:[NSString class]])
[string appendString:value];
else if ([value isKindOfClass:[NSNumber class]])
[string appendString:[value description]];
else if ([value isKindOfClass:[NSDictionary class]])
{
[string appendString:@"{"];
[string appendString:[self _dictionaryString:value]];
[string appendString:@"}"];
}
[string appendString:@";"];
}];
return string;
}
+ (void)video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(void *)__unused contextInfo
{
[[NSFileManager defaultManager] removeItemAtPath:videoPath error:nil];
if (error != nil)
TGLegacyLog(@"Video saving error: %@", error);
}
- (void)actionStageActionRequested:(NSString *)action options:(id)options
{
if ([action isEqualToString:@"imageCropResult"])
{
UIImage *image = options;
if ([options isKindOfClass:[UIImage class]])
{
id<TGLegacyCameraControllerDelegate> delegate = _completionDelegate;
if ([delegate conformsToProtocol:@protocol(TGImagePickerControllerDelegate)])
[(id<TGImagePickerControllerDelegate>)delegate imagePickerController:nil didFinishPickingWithAssets:@[image]];
}
}
}
@end
@implementation UINavigationBar (Helper)
- (void)setBottomBorderColor:(UIColor *)color
{
CGRect bottomBorderRect = CGRectMake(0, CGRectGetHeight(self.frame), CGRectGetWidth(self.frame), TGScreenPixel);
UIView *bottomBorder = [[UIView alloc] initWithFrame:bottomBorderRect];
[bottomBorder setBackgroundColor:color];
[self addSubview:bottomBorder];
}
@end

View File

@ -1,5 +0,0 @@
#import <UIKit/UIKit.h>
@interface TGLegacyMediaPickerTipView : UIView
@end

View File

@ -1,98 +0,0 @@
#import "TGLegacyMediaPickerTipView.h"
#import "LegacyComponentsInternal.h"
#import "TGImageUtils.h"
#import "TGFont.h"
#import "TGColor.h"
#import "TGStringUtils.h"
#import <LegacyComponents/TGModernButton.h>
@interface TGLegacyMediaPickerTipView ()
{
UIView *_wrapperView;
UIImageView *_imageView;
UILabel *_titleLabel;
UILabel *_textLabel;
TGModernButton *_doneButton;
}
@end
@implementation TGLegacyMediaPickerTipView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil)
{
self.backgroundColor = [UIColor whiteColor];
_wrapperView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
[self addSubview:_wrapperView];
_imageView = [[UIImageView alloc] initWithImage:TGComponentsImageNamed(@"AttachmentTipIcons")];
[self addSubview:_imageView];
_titleLabel = [[UILabel alloc] init];
_titleLabel.backgroundColor = [UIColor clearColor];
_titleLabel.textColor = UIColorRGB(0x222222);
_titleLabel.font = TGSystemFontOfSize(19.0f + TGRetinaPixel);
_titleLabel.text = TGLocalized(@"ShareFileTip.Title");
[_wrapperView addSubview:_titleLabel];
_textLabel = [[UILabel alloc] init];
_textLabel.backgroundColor = [UIColor clearColor];
_textLabel.textColor = UIColorRGB(0x808080);
_textLabel.font = TGSystemFontOfSize(15.0f + TGRetinaPixel);
NSString *shareTipText = [[NSString alloc] initWithFormat:TGLocalized(@"ShareFileTip.Text"), [TGStringUtils stringForDeviceType]];
_textLabel.attributedText = [shareTipText attributedFormattedStringWithRegularFont:TGSystemFontOfSize(15.0f + TGRetinaPixel) boldFont:TGBoldSystemFontOfSize(15.0f + TGRetinaPixel) lineSpacing:3.0f paragraphSpacing:-1.0f alignment:NSTextAlignmentCenter];
_textLabel.numberOfLines = 0;
_textLabel.lineBreakMode = NSLineBreakByWordWrapping;
[_wrapperView addSubview:_textLabel];
_doneButton = [[TGModernButton alloc] init];
[_doneButton setTitle:TGLocalized(@"ShareFileTip.CloseTip") forState:UIControlStateNormal];
_doneButton.titleLabel.font = TGSystemFontOfSize(18.0f);
[_doneButton setTitleColor:TGAccentColor()];
_doneButton.contentEdgeInsets = UIEdgeInsetsMake(8.0f, 20.0f, 8.0f, 20.0f);
[_doneButton sizeToFit];
[_doneButton addTarget:self action:@selector(doneButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_doneButton];
}
return self;
}
- (void)doneButtonPressed
{
[UIView animateWithDuration:0.4 animations:^
{
self.frame = CGRectMake(0.0f, self.superview.frame.size.height, self.frame.size.width, self.frame.size.height);
} completion:^(__unused BOOL finished)
{
[self removeFromSuperview];
}];
}
- (void)layoutSubviews
{
[super layoutSubviews];
_imageView.frame = CGRectMake((self.frame.size.width - _imageView.frame.size.width) / 2, 0, _imageView.frame.size.width, _imageView.frame.size.height);
CGFloat padding = 22.0f;
CGSize titleSize = [_titleLabel sizeThatFits:CGSizeMake(self.bounds.size.width - padding * 2.0f, CGFLOAT_MAX)];
_titleLabel.frame = CGRectMake(padding, CGRectGetMaxY(_imageView.frame) + 22.0f + TGRetinaPixel, titleSize.width, titleSize.height);
CGSize textSize = [_textLabel sizeThatFits:CGSizeMake(self.bounds.size.width - padding * 2.0f, CGFLOAT_MAX)];
_textLabel.frame = CGRectMake(padding, CGRectGetMaxY(_titleLabel.frame) + 15.0f + TGRetinaPixel, textSize.width, textSize.height);
CGFloat wrapperHeight = CGRectGetMaxY(_textLabel.frame);
_wrapperView.frame = CGRectMake(0, CGFloor((self.frame.size.height - wrapperHeight) / 2) - 30.0f, self.frame.size.width, wrapperHeight);
_doneButton.frame = CGRectMake(CGFloor((self.bounds.size.width - _doneButton.frame.size.width) / 2.0f), self.frame.size.height - _doneButton.frame.size.height - 16.0f + TGRetinaPixel, _doneButton.frame.size.width, _doneButton.frame.size.height);
}
@end

View File

@ -12,11 +12,9 @@
#import "TGAttachmentCarouselItemView.h"
#import <LegacyComponents/TGCameraController.h>
#import "TGLegacyCameraController.h"
#import <LegacyComponents/TGImagePickerController.h>
#import <LegacyComponents/TGMediaAssetsController.h>
@interface TGMediaAvatarMenuMixin () <TGLegacyCameraControllerDelegate>
@interface TGMediaAvatarMenuMixin ()
{
TGViewController *_parentController;
bool _hasSearchButton;
@ -278,13 +276,6 @@
if ([_context currentlyInSplitView])
return;
if ([TGCameraController useLegacyCamera])
{
[self _displayLegacyCamera];
[menuController dismissAnimated:true];
return;
}
TGCameraController *controller = nil;
CGSize screenSize = TGScreenSize();
@ -376,16 +367,6 @@
};
}
- (void)_displayLegacyCamera
{
TGLegacyCameraController *legacyCameraController = [[TGLegacyCameraController alloc] initWithContext:_context];
legacyCameraController.sourceType = UIImagePickerControllerSourceTypeCamera;
legacyCameraController.avatarMode = true;
legacyCameraController.completionDelegate = self;
[_parentController presentViewController:legacyCameraController animated:true completion:nil];
}
- (void)_displayMediaPicker
{
if (![[[LegacyComponentsGlobals provider] accessChecker] checkPhotoAuthorizationStatusForIntent:TGPhotoAccessIntentRead alertDismissCompletion:nil])
@ -531,30 +512,6 @@
}
}
- (void)imagePickerController:(TGImagePickerController *)__unused imagePicker didFinishPickingWithAssets:(NSArray *)assets
{
UIImage *resultImage = nil;
if (assets.count != 0)
{
if ([assets[0] isKindOfClass:[UIImage class]])
resultImage = assets[0];
}
if (self.didFinishWithImage != nil)
self.didFinishWithImage(resultImage);
[_parentController dismissViewControllerAnimated:true completion:nil];
}
- (void)legacyCameraControllerCompletedWithNoResult
{
[_parentController dismissViewControllerAnimated:true completion:nil];
if (self.didDismiss != nil)
self.didDismiss();
}
- (void)_performDelete
{
if (self.didFinishWithDelete != nil)

View File

@ -23,8 +23,6 @@
#import <LegacyComponents/JNWSpringAnimation.h>
#import <LegacyComponents/TGModernGalleryEmbeddedStickersHeaderView.h>
#import <LegacyComponents/TGKeyCommandController.h>
#define TGModernGalleryItemPadding 20.0f
@ -1624,7 +1622,7 @@ static CGFloat transformRotation(CGAffineTransform transform)
{
itemHeaderView.alpha = alpha;
itemHeaderView.hidden = (alpha < FLT_EPSILON);
if (![itemHeaderView isKindOfClass:[TGModernGalleryEmbeddedStickersHeaderView class]] && itemHeaderView.tag != 0xbeef) {
if (itemHeaderView.tag != 0xbeef) {
titleAlpha -= alpha;
}
}

View File

@ -1,46 +0,0 @@
#import "TGModernGalleryEmbeddedStickersHeaderView.h"
#import <LegacyComponents/LegacyComponents.h>
#import <LegacyComponents/TGModernButton.h>
@interface TGModernGalleryEmbeddedStickersHeaderView () {
TGModernButton *_stickerButton;
}
@end
@implementation TGModernGalleryEmbeddedStickersHeaderView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self != nil) {
_stickerButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 50.0f, 44.0f)];
[_stickerButton setImage:TGTintedImage([UIImage imageNamed:@"GalleryEmbeddedStickersIcon"], [UIColor whiteColor]) forState:UIControlStateNormal];
[_stickerButton addTarget:self action:@selector(stickerButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_stickerButton];
}
return self;
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if (!_stickerButton.hidden && CGRectContainsPoint(_stickerButton.frame, point))
return true;
return [super pointInside:point withEvent:event];
}
- (void)layoutSubviews {
[super layoutSubviews];
_stickerButton.frame = CGRectMake(self.frame.size.width + 26.0f, -1.0f, _stickerButton.frame.size.width, _stickerButton.frame.size.height);
}
- (void)stickerButtonPressed {
if (_showEmbeddedStickers) {
_showEmbeddedStickers();
}
}
@end

View File

@ -11,7 +11,6 @@
#import "TGAttachmentCameraView.h"
#import <LegacyComponents/TGCameraController.h>
#import <LegacyComponents/TGLegacyCameraController.h>
@interface TGPassportDocumentPickerDelegate : NSObject <UIDocumentPickerDelegate>
{
@ -270,26 +269,6 @@
}
}
+ (void)_displayLegacyCameraWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController uploadAction:(void (^)(SSignal *, void (^)(void)))uploadAction
{
TGLegacyCameraController *legacyCameraController = [[TGLegacyCameraController alloc] initWithContext:context];
legacyCameraController.sourceType = UIImagePickerControllerSourceTypeCamera;
__weak TGViewController *weakParentController = parentController;
legacyCameraController.finishedWithImage = ^(UIImage *image)
{
TGCameraCapturedPhoto *photo = [[TGCameraCapturedPhoto alloc] initWithImage:image metadata:nil];
uploadAction([TGPassportAttachMenu resultSignalForEditingContext:nil selectionContext:nil currentItem:photo], ^
{
__strong TGViewController *strongParentController = weakParentController;
if (strongParentController != nil)
[strongParentController dismissViewControllerAnimated:true completion:nil];
});
};
[parentController presentViewController:legacyCameraController animated:true completion:nil];
}
+ (void)_displayCameraWithView:(TGAttachmentCameraView *)cameraView menuController:(TGMenuSheetController *)menuController parentController:(TGViewController *)parentController context:(id<LegacyComponentsContext>)context intent:(TGPassportAttachIntent)intent uploadAction:(void (^)(SSignal *, void (^)(void)))uploadAction
{
if (![[[LegacyComponentsGlobals provider] accessChecker] checkCameraAuthorizationStatusForIntent:TGCameraAccessIntentDefault alertDismissCompletion:nil])
@ -297,14 +276,7 @@
if ([context currentlyInSplitView])
return;
if ([TGCameraController useLegacyCamera])
{
[self _displayLegacyCameraWithContext:context parentController:parentController uploadAction:uploadAction];
[menuController dismissAnimated:true];
return;
}
TGCameraController *controller = nil;
CGSize screenSize = TGScreenSize();

View File

@ -88,7 +88,7 @@ const CGFloat TGPhotoAvatarCropButtonsWrapperSize = 61.0f;
[self.view addSubview:_wrapperView];
PGPhotoEditor *photoEditor = self.photoEditor;
_cropView = [[TGPhotoAvatarCropView alloc] initWithOriginalSize:photoEditor.originalSize screenSize:[self referenceViewSize] fullPreviewView:nil];
_cropView = [[TGPhotoAvatarCropView alloc] initWithOriginalSize:photoEditor.originalSize screenSize:[self referenceViewSize] fullPreviewView:nil fullPaintingView:nil];
[_cropView setCropRect:photoEditor.cropRect];
[_cropView setCropOrientation:photoEditor.cropOrientation];
[_cropView setCropMirrored:photoEditor.cropMirrored];

View File

@ -42,12 +42,13 @@ const CGFloat TGPhotoAvatarCropViewCurtainMargin = 200;
CGFloat _currentDiameter;
__weak PGPhotoEditorView *_fullPreviewView;
__weak UIImageView *_fullPaintingView;
}
@end
@implementation TGPhotoAvatarCropView
- (instancetype)initWithOriginalSize:(CGSize)originalSize screenSize:(CGSize)screenSize fullPreviewView:(PGPhotoEditorView *)fullPreviewView
- (instancetype)initWithOriginalSize:(CGSize)originalSize screenSize:(CGSize)screenSize fullPreviewView:(PGPhotoEditorView *)fullPreviewView fullPaintingView:(UIImageView *)fullPaintingView
{
self = [super initWithFrame:CGRectZero];
if (self != nil)
@ -82,7 +83,11 @@ const CGFloat TGPhotoAvatarCropViewCurtainMargin = 200;
CGFloat scale = _imageView.bounds.size.width / fittedSize.width;
_fullPreviewView.transform = CGAffineTransformMakeScale(self.cropMirrored ? -scale : scale, scale);
_fullPreviewView.userInteractionEnabled = false;
_fullPaintingView = fullPaintingView;
_fullPaintingView.frame = _fullPreviewView.frame;
[_wrapperView addSubview:_fullPreviewView];
[_wrapperView addSubview:_fullPaintingView];
_flashView = [[UIView alloc] init];
_flashView.alpha = 0.0;

View File

@ -18,6 +18,7 @@
@property (nonatomic, weak) UIView *dotImageView;
@property (nonatomic, weak) UIView *dotMarkerView;
@property (nonatomic, weak) PGPhotoEditorView *fullPreviewView;
@property (nonatomic, weak) UIImageView *fullPaintingView;
@property (nonatomic, weak) TGMediaPickerGalleryVideoScrubber *scrubberView;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView;

View File

@ -63,10 +63,6 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
return self;
}
- (void)dealloc {
NSLog(@"");
}
- (void)loadView
{
[super loadView];
@ -99,7 +95,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
};
PGPhotoEditor *photoEditor = self.photoEditor;
TGPhotoAvatarCropView *cropView = [[TGPhotoAvatarCropView alloc] initWithOriginalSize:photoEditor.originalSize screenSize:[self referenceViewSize] fullPreviewView:_fullPreviewView];
TGPhotoAvatarCropView *cropView = [[TGPhotoAvatarCropView alloc] initWithOriginalSize:photoEditor.originalSize screenSize:[self referenceViewSize] fullPreviewView:_fullPreviewView fullPaintingView:_fullPaintingView];
_cropView = cropView;
[_cropView setCropRect:photoEditor.cropRect];
[_cropView setCropOrientation:photoEditor.cropOrientation];

View File

@ -69,6 +69,7 @@
TGPhotoToolbarView *_landscapeToolbarView;
TGPhotoEditorPreviewView *_previewView;
PGPhotoEditorView *_fullPreviewView;
UIImageView *_fullPaintingView;
PGPhotoEditor *_photoEditor;
@ -331,10 +332,15 @@
[self updatePreviewView:true];
if ([self presentedForAvatarCreation]) {
_previewView.applyMirror = true;
CGSize fittedSize = TGScaleToSize(_photoEditor.originalSize, CGSizeMake(1024, 1024));
_fullPreviewView = [[PGPhotoEditorView alloc] initWithFrame:CGRectMake(0, 0, fittedSize.width, fittedSize.height)];
_photoEditor.additionalOutputs = @[_fullPreviewView];
[self.view addSubview:_fullPreviewView];
_fullPaintingView = [[UIImageView alloc] init];
_fullPaintingView.frame = _fullPreviewView.frame;
}
_dotMarkerView = [[UIImageView alloc] initWithImage:TGCircleImage(7.0, [TGPhotoEditorInterfaceAssets accentColor])];
@ -1149,6 +1155,10 @@
{
[currentController removeFromParentViewController];
[currentController.view removeFromSuperview];
if ([self presentedForAvatarCreation] && tab == TGPhotoEditorCropTab) {
_previewView.transform = CGAffineTransformIdentity;
}
}];
transitionReferenceFrame = [currentController transitionOutReferenceFrame];
@ -1219,6 +1229,8 @@
{
case TGPhotoEditorCropTab:
{
_fullPaintingView.hidden = false;
[self updatePreviewView:true];
__block UIView *initialBackgroundView = nil;
if ([self presentedForAvatarCreation])
@ -1230,6 +1242,7 @@
cropController.dotImageView = _dotImageView;
cropController.dotMarkerView = _dotMarkerView;
cropController.fullPreviewView = _fullPreviewView;
cropController.fullPaintingView = _fullPaintingView;
cropController.fromCamera = [self presentedFromCamera];
cropController.skipTransitionIn = skipInitialTransition;
if (snapshotImage != nil)
@ -1334,8 +1347,8 @@
if (strongSelf == nil)
return;
if (strongSelf->_currentTabController.finishedTransitionIn != nil)
{
strongSelf->_fullPaintingView.hidden = true;
if (strongSelf->_currentTabController.finishedTransitionIn != nil) {
strongSelf->_currentTabController.finishedTransitionIn();
strongSelf->_currentTabController.finishedTransitionIn = nil;
}
@ -1614,9 +1627,10 @@
- (void)updatePreviewView:(bool)full
{
if (full)
if (full) {
[_previewView setPaintingImageWithData:_photoEditor.paintingData];
_fullPaintingView.image = _photoEditor.paintingData.image;
}
UIImageOrientation cropOrientation = _photoEditor.cropOrientation;
if ([self presentedForAvatarCreation]) {
cropOrientation = UIImageOrientationUp;

View File

@ -13,6 +13,9 @@
@property (nonatomic, copy) void(^touchedUp)(void);
@property (nonatomic, copy) void(^interactionEnded)(void);
@property (nonatomic, assign) bool applyMirror;
@property (nonatomic, readonly) bool isTracking;
@property (nonatomic, assign) bool customTouchDownHandling;

View File

@ -286,10 +286,11 @@
}
CGFloat rotation = TGRotationForOrientation(_cropOrientation);
_paintingContainerView.transform = CGAffineTransformMakeRotation(rotation);
CGAffineTransform transform = CGAffineTransformMakeScale(_cropMirrored && self.applyMirror ? -1.0 : 1.0, 1.0);
_paintingContainerView.transform = CGAffineTransformRotate(transform, rotation);
_paintingContainerView.frame = self.bounds;
CGFloat width = TGOrientationIsSideward(_cropOrientation, NULL) ? self.frame.size.height : self.frame.size.width;
CGFloat width = TGOrientationIsSideward(_cropOrientation, NULL) ? self.bounds.size.height : self.bounds.size.width;
CGFloat ratio = 1.0;
if (_cropRect.size.width > 0.0) {
ratio = width / _cropRect.size.width;

View File

@ -431,68 +431,70 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
- (void)setupCanvas
{
__weak TGPhotoPaintController *weakSelf = self;
_canvasView = [[TGPaintCanvas alloc] initWithFrame:CGRectZero];
_canvasView.pointInsideContainer = ^bool(CGPoint point)
{
__strong TGPhotoPaintController *strongSelf = weakSelf;
if (strongSelf == nil)
return false;
return [strongSelf->_containerView pointInside:[strongSelf->_canvasView convertPoint:point toView:strongSelf->_containerView] withEvent:nil];
};
_canvasView.shouldDraw = ^bool
{
__strong TGPhotoPaintController *strongSelf = weakSelf;
if (strongSelf == nil)
return false;
return ![strongSelf->_entitiesContainerView isTrackingAnyEntityView];
};
_canvasView.shouldDrawOnSingleTap = ^bool
{
__strong TGPhotoPaintController *strongSelf = weakSelf;
if (strongSelf == nil)
return false;
bool rotating = (strongSelf->_rotationGestureRecognizer.state == UIGestureRecognizerStateBegan || strongSelf->_rotationGestureRecognizer.state == UIGestureRecognizerStateChanged);
bool pinching = (strongSelf->_pinchGestureRecognizer.state == UIGestureRecognizerStateBegan || strongSelf->_pinchGestureRecognizer.state == UIGestureRecognizerStateChanged);
if (strongSelf->_currentEntityView != nil && !rotating && !pinching)
if (_canvasView == nil) {
__weak TGPhotoPaintController *weakSelf = self;
_canvasView = [[TGPaintCanvas alloc] initWithFrame:CGRectZero];
_canvasView.pointInsideContainer = ^bool(CGPoint point)
{
[strongSelf selectEntityView:nil];
return false;
}
return true;
};
_canvasView.strokeBegan = ^
{
__strong TGPhotoPaintController *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf selectEntityView:nil];
};
_canvasView.strokeCommited = ^
{
__strong TGPhotoPaintController *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf updateActionsView];
};
_canvasView.hitTest = ^UIView *(CGPoint point, UIEvent *event)
{
__strong TGPhotoPaintController *strongSelf = weakSelf;
if (strongSelf == nil)
return nil;
return [strongSelf->_entitiesContainerView hitTest:[strongSelf->_canvasView convertPoint:point toView:strongSelf->_entitiesContainerView] withEvent:event];
};
_canvasView.cropRect = _photoEditor.cropRect;
_canvasView.cropOrientation = _photoEditor.cropOrientation;
_canvasView.originalSize = _photoEditor.originalSize;
[_canvasView setPainting:_painting];
[_canvasView setBrush:_brushes.firstObject];
[self setCurrentSwatch:_portraitSettingsView.swatch sender:nil];
[_paintingWrapperView addSubview:_canvasView];
__strong TGPhotoPaintController *strongSelf = weakSelf;
if (strongSelf == nil)
return false;
return [strongSelf->_containerView pointInside:[strongSelf->_canvasView convertPoint:point toView:strongSelf->_containerView] withEvent:nil];
};
_canvasView.shouldDraw = ^bool
{
__strong TGPhotoPaintController *strongSelf = weakSelf;
if (strongSelf == nil)
return false;
return ![strongSelf->_entitiesContainerView isTrackingAnyEntityView];
};
_canvasView.shouldDrawOnSingleTap = ^bool
{
__strong TGPhotoPaintController *strongSelf = weakSelf;
if (strongSelf == nil)
return false;
bool rotating = (strongSelf->_rotationGestureRecognizer.state == UIGestureRecognizerStateBegan || strongSelf->_rotationGestureRecognizer.state == UIGestureRecognizerStateChanged);
bool pinching = (strongSelf->_pinchGestureRecognizer.state == UIGestureRecognizerStateBegan || strongSelf->_pinchGestureRecognizer.state == UIGestureRecognizerStateChanged);
if (strongSelf->_currentEntityView != nil && !rotating && !pinching)
{
[strongSelf selectEntityView:nil];
return false;
}
return true;
};
_canvasView.strokeBegan = ^
{
__strong TGPhotoPaintController *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf selectEntityView:nil];
};
_canvasView.strokeCommited = ^
{
__strong TGPhotoPaintController *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf updateActionsView];
};
_canvasView.hitTest = ^UIView *(CGPoint point, UIEvent *event)
{
__strong TGPhotoPaintController *strongSelf = weakSelf;
if (strongSelf == nil)
return nil;
return [strongSelf->_entitiesContainerView hitTest:[strongSelf->_canvasView convertPoint:point toView:strongSelf->_entitiesContainerView] withEvent:event];
};
_canvasView.cropRect = _photoEditor.cropRect;
_canvasView.cropOrientation = _photoEditor.cropOrientation;
_canvasView.originalSize = _photoEditor.originalSize;
[_canvasView setPainting:_painting];
[_canvasView setBrush:_brushes.firstObject];
[self setCurrentSwatch:_portraitSettingsView.swatch sender:nil];
[_paintingWrapperView addSubview:_canvasView];
}
_canvasView.hidden = false;
[self.view setNeedsLayout];
@ -1801,6 +1803,11 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
_portraitSettingsView.layer.shouldRasterize = false;
_landscapeSettingsView.layer.shouldRasterize = false;
}];
if (self.presentedForAvatarCreation) {
_canvasView.hidden = true;
_entitiesContainerView.hidden = true;
}
}
+ (CGRect)photoContainerFrameForParentViewFrame:(CGRect)parentViewFrame toolbarLandscapeSize:(CGFloat)toolbarLandscapeSize orientation:(UIInterfaceOrientation)orientation panelSize:(CGFloat)panelSize hasOnScreenNavigation:(bool)hasOnScreenNavigation
@ -1847,6 +1854,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
}
[self setupCanvas];
_entitiesContainerView.hidden = false;
TGPhotoEditorPreviewView *previewView = _previewView;
[previewView setPaintingHidden:true];

View File

@ -1,7 +0,0 @@
#import <UIKit/UIKit.h>
@interface TGPhotoStickersCollectionLayout : UICollectionViewFlowLayout
- (NSArray *)sectionHeaders;
@end

View File

@ -1,70 +0,0 @@
#import "TGPhotoStickersCollectionLayout.h"
#import "TGPhotoStickersSectionHeader.h"
#import "TGPhotoStickersSectionHeaderView.h"
@interface TGPhotoStickersCollectionLayout ()
{
bool _updatingCollectionItems;
NSArray *_sectionHeaders;
}
@end
@implementation TGPhotoStickersCollectionLayout
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
if (_updatingCollectionItems || itemIndexPath.section != 0)
return [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];
return nil;
}
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
if (_updatingCollectionItems || itemIndexPath.section != 0)
return [super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath];
return [self layoutAttributesForItemAtIndexPath:itemIndexPath];
}
- (void)prepareLayout
{
[super prepareLayout];
NSMutableArray *sectionHeaders = [[NSMutableArray alloc] init];
id<UICollectionViewDataSource> dataSource = self.collectionView.dataSource;
NSUInteger numberOfSections = 1;
if ([dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)])
numberOfSections = [dataSource numberOfSectionsInCollectionView:self.collectionView];
for (NSUInteger i = 0; i < numberOfSections; i++)
{
NSUInteger itemCount = [dataSource collectionView:self.collectionView numberOfItemsInSection:i];
if (itemCount != 0)
{
UICollectionViewLayoutAttributes *firstItemAttributes = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:i]];
UICollectionViewLayoutAttributes *lastItemAttributes = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:itemCount - 1 inSection:i]];
TGPhotoStickersSectionHeader *sectionHeader = [[TGPhotoStickersSectionHeader alloc] init];
sectionHeader.index = i;
sectionHeader.bounds = CGRectMake(0.0f, 0.0f, self.collectionView.bounds.size.width, TGPhotoStickersSectionHeaderHeight);
sectionHeader.floatingFrame = CGRectMake(0.0f, firstItemAttributes.frame.origin.y - sectionHeader.bounds.size.height, sectionHeader.bounds.size.width, CGRectGetMaxY(lastItemAttributes.frame) - (firstItemAttributes.frame.origin.y - sectionHeader.bounds.size.height));
[sectionHeaders addObject:sectionHeader];
}
}
_sectionHeaders = sectionHeaders;
}
- (NSArray *)sectionHeaders
{
return _sectionHeaders;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)__unused newBounds
{
return false;
}
@end

View File

@ -1,17 +0,0 @@
#import <UIKit/UIKit.h>
@class TGPhotoStickersSectionHeader;
@class TGPhotoStickersSectionHeaderView;
@protocol TGPhotoStickersCollectionViewDelegate <UICollectionViewDelegateFlowLayout>
- (void)collectionView:(UICollectionView *)collectionView setupSectionHeaderView:(TGPhotoStickersSectionHeaderView *)sectionHeaderView forSectionHeader:(TGPhotoStickersSectionHeader *)sectionHeader;
@end
@interface TGPhotoStickersCollectionView : UICollectionView
@property (nonatomic, weak) UIView *headersParentView;
@property (nonatomic, strong) UIColor *headerTextColor;
@end

View File

@ -1,121 +0,0 @@
#import "TGPhotoStickersCollectionView.h"
#import "TGPhotoStickersCollectionLayout.h"
#import "TGPhotoStickersSectionHeader.h"
#import "TGPhotoStickersSectionHeaderView.h"
@interface TGPhotoStickersCollectionView ()
{
NSMutableArray *_sectionHeaderViewQueue;
NSMutableArray *_visibleSectionHeaderViews;
}
@end
@implementation TGPhotoStickersCollectionView
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout
{
self = [super initWithFrame:frame collectionViewLayout:layout];
if (self != nil)
{
_sectionHeaderViewQueue = [[NSMutableArray alloc] init];
_visibleSectionHeaderViews = [[NSMutableArray alloc] init];
}
return self;
}
- (void)reloadData
{
for (TGPhotoStickersSectionHeaderView *headerView in _visibleSectionHeaderViews)
{
[self enqueueSectionHeaderView:headerView];
}
[_visibleSectionHeaderViews removeAllObjects];
[super reloadData];
}
- (TGPhotoStickersSectionHeaderView *)dequeueSectionHeaderView
{
TGPhotoStickersSectionHeaderView *headerView = [_sectionHeaderViewQueue lastObject];
if (headerView != nil)
{
[_sectionHeaderViewQueue removeLastObject];
return headerView;
}
else
{
headerView = [[TGPhotoStickersSectionHeaderView alloc] init];
if (self.headerTextColor != nil)
[headerView setTextColor:self.headerTextColor];
return headerView;
}
}
- (void)enqueueSectionHeaderView:(TGPhotoStickersSectionHeaderView *)headerView
{
[headerView removeFromSuperview];
[_sectionHeaderViewQueue addObject:headerView];
}
- (void)layoutSubviews
{
[super layoutSubviews];
CGRect bounds = self.bounds;
UIEdgeInsets insets = self.contentInset;
for (TGPhotoStickersSectionHeader *sectionHeader in [(TGPhotoStickersCollectionLayout *)self.collectionViewLayout sectionHeaders])
{
CGRect headerFloatingBounds = sectionHeader.floatingFrame;
if (CGRectIntersectsRect(bounds, headerFloatingBounds))
{
TGPhotoStickersSectionHeaderView *headerView = nil;
for (TGPhotoStickersSectionHeaderView *visibleHeaderView in _visibleSectionHeaderViews)
{
if (visibleHeaderView.index == sectionHeader.index)
{
headerView = visibleHeaderView;
break;
}
}
if (headerView == nil)
{
headerView = [self dequeueSectionHeaderView];
headerView.index = sectionHeader.index;
id<TGPhotoStickersCollectionViewDelegate> delegate = (id<TGPhotoStickersCollectionViewDelegate>)self.delegate;
[delegate collectionView:self setupSectionHeaderView:headerView forSectionHeader:sectionHeader];
[_visibleSectionHeaderViews addObject:headerView];
[_headersParentView addSubview:headerView];
}
CGRect headerFrame = sectionHeader.bounds;
headerFrame.origin.y = MIN(headerFloatingBounds.origin.y + 8.0f + headerFloatingBounds.size.height - headerFrame.size.height, MAX(headerFloatingBounds.origin.y, bounds.origin.y + insets.top));
headerView.frame = [self convertRect:headerFrame toView:_headersParentView];
[headerView.layer removeAllAnimations];
CGFloat alpha = MAX(0.0f, MIN(1.0f, (headerView.frame.origin.y - 80.0f) / 24.0f));
headerView.alpha = alpha;
}
else
{
NSInteger index = -1;
for (TGPhotoStickersSectionHeaderView *headerView in _visibleSectionHeaderViews)
{
index++;
if (headerView.index == sectionHeader.index)
{
[self enqueueSectionHeaderView:headerView];
[_visibleSectionHeaderViews removeObjectAtIndex:index];
break;
}
}
}
}
}
@end

View File

@ -1,10 +0,0 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface TGPhotoStickersSectionHeader : NSObject
@property (nonatomic) NSInteger index;
@property (nonatomic) CGRect bounds;
@property (nonatomic) CGRect floatingFrame;
@end

View File

@ -1,5 +0,0 @@
#import "TGPhotoStickersSectionHeader.h"
@implementation TGPhotoStickersSectionHeader
@end

View File

@ -1,12 +0,0 @@
#import <UIKit/UIKit.h>
@interface TGPhotoStickersSectionHeaderView : UIView
@property (nonatomic) NSInteger index;
- (void)setTitle:(NSString *)title;
- (void)setTextColor:(UIColor *)color;
@end
extern const CGFloat TGPhotoStickersSectionHeaderHeight;

View File

@ -1,53 +0,0 @@
#import "TGPhotoStickersSectionHeaderView.h"
#import "LegacyComponentsInternal.h"
#import "TGFont.h"
#import "TGImageUtils.h"
const CGFloat TGPhotoStickersSectionHeaderHeight = 56.0f;
@interface TGPhotoStickersSectionHeaderView ()
{
UILabel *_titleLabel;
}
@end
@implementation TGPhotoStickersSectionHeaderView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil)
{
self.backgroundColor = [UIColor clearColor];
_titleLabel = [[UILabel alloc] init];
_titleLabel.backgroundColor = [UIColor clearColor];
_titleLabel.textColor = UIColorRGB(0xafb2b1);
_titleLabel.font = TGSystemFontOfSize(17.0f);
[self addSubview:_titleLabel];
}
return self;
}
- (void)setTitle:(NSString *)title
{
_titleLabel.text = title;
[_titleLabel sizeToFit];
[self setNeedsLayout];
}
- (void)setTextColor:(UIColor *)color
{
_titleLabel.textColor = color;
}
- (void)layoutSubviews
{
[super layoutSubviews];
_titleLabel.frame = (CGRect){{16.0f, TGRetinaFloor((self.bounds.size.height - _titleLabel.frame.size.height) / 2.0f) + 5.0f}, { _titleLabel.frame.size.width, _titleLabel.frame.size.height }};
}
@end

View File

@ -1,23 +0,0 @@
#import "TGPhotoPaintSettingsView.h"
#import <LegacyComponents/LegacyComponentsContext.h>
@class TGViewController;
@class TGDocumentMediaAttachment;
@interface TGPhotoStickersView : UIView <TGPhotoPaintPanelView>
@property (nonatomic, weak) TGViewController *parentViewController;
@property (nonatomic, weak) UIView *outerView;
@property (nonatomic, weak) UIView *targetView;
@property (nonatomic, copy) void (^stickerSelected)(TGDocumentMediaAttachment *, CGPoint, TGPhotoStickersView *, UIView *);
@property (nonatomic, copy) void (^dismissed)(void);
@property (nonatomic, assign) UIEdgeInsets safeAreaInset;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context frame:(CGRect)frame;
- (void)dismissWithSnapshotView:(UIView *)view startPoint:(CGPoint)startPoint targetFrame:(CGRect)targetFrame targetRotation:(CGFloat)targetRotation completion:(void (^)(void))completion;
@end

View File

@ -1,932 +0,0 @@
#import "TGPhotoStickersView.h"
#import "LegacyComponentsContext.h"
#import "LegacyComponentsInternal.h"
#import "TGImageUtils.h"
#import "TGFont.h"
#import "TGColor.h"
#import "TGStickerPack.h"
#import "TGDocumentMediaAttachment.h"
#import <LegacyComponents/TGPaintUtils.h>
#import <LegacyComponents/TGModernButton.h>
#import "TGStickerKeyboardTabPanel.h"
#import "TGPhotoStickersCollectionView.h"
#import "TGPhotoStickersCollectionLayout.h"
#import "TGPhotoStickersSectionHeader.h"
#import "TGPhotoStickersSectionHeaderView.h"
#import "TGStickerCollectionViewCell.h"
#import "TGItemPreviewController.h"
#import "TGStickerItemPreviewView.h"
const CGFloat TGPhotoStickersPreloadInset = 160.0f;
const CGFloat TGPhotoStickersViewMargin = 19.0f;
typedef enum {
TGPhotoStickersViewSectionMasks = 0,
TGPhotoStickersViewSectionGeneric = 1
} TGPhotoStickersViewSection;
@interface TGPhotoStickersView () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UIGestureRecognizerDelegate>
{
id<SDisposable> _stickerPacksDisposable;
TGPhotoStickersViewSection _section;
NSArray<TGStickerPack *> *_genericStickerPacks;
NSArray<TGStickerPack *> *_maskStickerPacks;
NSArray *_recentDocumentsOriginal;
NSArray *_recentDocumentsSorted;
NSArray *_recentStickers;
NSArray *_recentMasks;
NSDictionary *_packReferenceToPack;
bool _ignoreSetSection;
UIView *_dimView;
UIView *_blurView;
UIImageView *_backgroundView;
UIView *_wrapperView;
UISegmentedControl *_segmentedControl;
TGModernButton *_cancelButton;
TGStickerKeyboardTabPanel *_tabPanel;
UIView *_separatorView;
UIView *_collectionWrapperView;
TGPhotoStickersCollectionView *_collectionView;
UICollectionViewFlowLayout *_collectionLayout;
UIView *_headersView;
UIPanGestureRecognizer *_panRecognizer;
CGFloat _masksContentOffset;
CGFloat _stickersContentOffset;
__weak TGItemPreviewController *_previewController;
id<LegacyComponentsContext> _context;
}
@end
@implementation TGPhotoStickersView
@synthesize interfaceOrientation = _interfaceOrientation;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context frame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil)
{
_context = context;
_masksContentOffset = FLT_MAX;
_stickersContentOffset = FLT_MAX;
bool compact = [_context currentSizeClass] == UIUserInterfaceSizeClassCompact;
if (compact)
{
if (iosMajorVersion() >= 8)
{
_blurView = [[UIVisualEffectView alloc] initWithEffect:nil];
}
else
{
_blurView = [[UIToolbar alloc] init];
_blurView.alpha = 0.0f;
((UIToolbar *)_blurView).barStyle = UIBarStyleBlackTranslucent;
}
[self addSubview:_blurView];
}
else
{
_interfaceOrientation = UIInterfaceOrientationPortrait;
_backgroundView = [[UIImageView alloc] init];
_backgroundView.alpha = 0.98f;
_backgroundView.image = [TGTintedImage(TGComponentsImageNamed(@"PaintPopupCenterBackground"), UIColorRGB(0xf7f7f7)) resizableImageWithCapInsets:UIEdgeInsetsMake(32.0f, 32.0f, 32.0f, 32.0f)];
[self addSubview:_backgroundView];
}
_wrapperView = [[UIView alloc] initWithFrame:self.bounds];
_wrapperView.clipsToBounds = true;
[self addSubview:_wrapperView];
_segmentedControl = [[UISegmentedControl alloc] initWithFrame:CGRectMake(0, 0, 0, 29.0f)];
TGStickerKeyboardViewStyle stickersStyle = TGStickerKeyboardViewPaintStyle;
if (compact)
{
_wrapperView.alpha = 0.0f;
_wrapperView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
stickersStyle = TGStickerKeyboardViewPaintDarkStyle;
_cancelButton = [[TGModernButton alloc] init];
_cancelButton.exclusiveTouch = true;
_cancelButton.titleLabel.font = TGSystemFontOfSize(17.0f);
[_cancelButton setTitle:TGLocalized(@"Common.Cancel") forState:UIControlStateNormal];
[_cancelButton setTitleColor:UIColorRGB(0xafb2b1)];
[_cancelButton addTarget:self action:@selector(cancelButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[_cancelButton sizeToFit];
[_wrapperView addSubview:_cancelButton];
[_segmentedControl setBackgroundImage:TGTintedImage(TGComponentsImageNamed(@"ModernSegmentedControlBackground.png"), UIColorRGB(0xafb2b1)) forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[_segmentedControl setBackgroundImage:TGTintedImage(TGComponentsImageNamed(@"ModernSegmentedControlSelected.png"), UIColorRGB(0xafb2b1)) forState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
[_segmentedControl setBackgroundImage:TGTintedImage(TGComponentsImageNamed(@"ModernSegmentedControlSelected.png"), UIColorRGB(0xafb2b1)) forState:UIControlStateSelected | UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
[_segmentedControl setBackgroundImage:TGComponentsImageNamed(@"PaintSegmentedControlHighlighted.png") forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
[_segmentedControl setDividerImage:TGTintedImage(TGComponentsImageNamed(@"ModernSegmentedControlDivider.png"), UIColorRGB(0xafb2b1)) forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[_segmentedControl setTitleTextAttributes:@{UITextAttributeTextColor: UIColorRGB(0xafb2b1), UITextAttributeTextShadowColor: [UIColor clearColor], UITextAttributeFont: TGSystemFontOfSize(13)} forState:UIControlStateNormal];
[_segmentedControl setTitleTextAttributes:@{UITextAttributeTextColor: [UIColor blackColor], UITextAttributeTextShadowColor: [UIColor clearColor], UITextAttributeFont: TGSystemFontOfSize(13)} forState:UIControlStateSelected];
}
else
{
[_segmentedControl setBackgroundImage:TGComponentsImageNamed(@"ModernSegmentedControlBackground.png") forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[_segmentedControl setBackgroundImage:TGComponentsImageNamed(@"ModernSegmentedControlSelected.png") forState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
[_segmentedControl setBackgroundImage:TGComponentsImageNamed(@"ModernSegmentedControlSelected.png") forState:UIControlStateSelected | UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
[_segmentedControl setBackgroundImage:TGComponentsImageNamed(@"ModernSegmentedControlHighlighted.png") forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
[_segmentedControl setDividerImage:TGComponentsImageNamed(@"ModernSegmentedControlDivider.png") forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[_segmentedControl setTitleTextAttributes:@{UITextAttributeTextColor: TGAccentColor(), UITextAttributeTextShadowColor: [UIColor clearColor], UITextAttributeFont: TGSystemFontOfSize(13)} forState:UIControlStateNormal];
[_segmentedControl setTitleTextAttributes:@{UITextAttributeTextColor: [UIColor whiteColor], UITextAttributeTextShadowColor: [UIColor clearColor], UITextAttributeFont: TGSystemFontOfSize(13)} forState:UIControlStateSelected];
}
[_segmentedControl insertSegmentWithTitle:TGLocalized(@"Paint.Masks") atIndex:0 animated:false];
[_segmentedControl insertSegmentWithTitle:TGLocalized(@"Paint.Stickers") atIndex:1 animated:false];
[_segmentedControl setSelectedSegmentIndex:0];
[_segmentedControl addTarget:self action:@selector(segmentedControlChanged) forControlEvents:UIControlEventValueChanged];
[_wrapperView addSubview:_segmentedControl];
__weak TGPhotoStickersView *weakSelf = self;
_tabPanel = [[TGStickerKeyboardTabPanel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, frame.size.width, 45.0f) style:stickersStyle];
_tabPanel.currentStickerPackIndexChanged = ^(NSUInteger index)
{
__strong TGPhotoStickersView *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf scrollToSection:index == 1 ? 0 : index - 2];
};
[_wrapperView addSubview:_tabPanel];
CGFloat thickness = TGScreenPixel;
_separatorView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 0.0f, thickness)];
_separatorView.backgroundColor = UIColorRGB(0xafb2b1);
//[_wrapperView addSubview:_separatorView];
_collectionWrapperView = [[UIView alloc] init];
_collectionWrapperView.clipsToBounds = true;
[_wrapperView addSubview:_collectionWrapperView];
_collectionLayout = [[TGPhotoStickersCollectionLayout alloc] init];
_collectionView = [[TGPhotoStickersCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:_collectionLayout];
if (iosMajorVersion() >= 11)
_collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
_collectionView.delegate = self;
_collectionView.dataSource = self;
_collectionView.backgroundColor = [UIColor clearColor];
_collectionView.opaque = false;
_collectionView.showsHorizontalScrollIndicator = false;
_collectionView.showsVerticalScrollIndicator = false;
_collectionView.alwaysBounceVertical = true;
_collectionView.delaysContentTouches = false;
_collectionView.contentInset = UIEdgeInsetsMake(TGPhotoStickersPreloadInset - TGPhotoStickersSectionHeaderHeight, 0.0f, TGPhotoStickersPreloadInset, 0.0f);
[_collectionView registerClass:[TGStickerCollectionViewCell class] forCellWithReuseIdentifier:@"TGStickerCollectionViewCell"];
if (!compact)
_collectionView.headerTextColor = UIColorRGB(0x787878);
[_collectionWrapperView addSubview:_collectionView];
_headersView = [[UIView alloc] init];
_headersView.userInteractionEnabled = false;
[_wrapperView addSubview:_headersView];
_collectionView.headersParentView = _headersView;
UILongPressGestureRecognizer *tapRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleStickerPress:)];
tapRecognizer.minimumPressDuration = 0.25;
[_collectionView addGestureRecognizer:tapRecognizer];
_panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleStickerPan:)];
_panRecognizer.delegate = self;
_panRecognizer.cancelsTouchesInView = false;
[_collectionView addGestureRecognizer:_panRecognizer];
_stickerPacksDisposable = [[[SSignal combineSignals:@[[[LegacyComponentsGlobals provider] maskStickerPacksSignal], [[LegacyComponentsGlobals provider] stickerPacksSignal], [[LegacyComponentsGlobals provider] recentStickerMasksSignal]]] deliverOn:[SQueue mainQueue]] startWithNext:^(NSArray *masksAndStickers)
{
NSDictionary *masks = masksAndStickers[0];
NSDictionary *stickers = masksAndStickers[1];
NSArray *recentStickers = masksAndStickers[2];
NSMutableArray *filteredPacks = [[NSMutableArray alloc] init];
for (TGStickerPack *pack in stickers[@"packs"])
{
if ([pack.packReference isKindOfClass:[TGStickerPackIdReference class]] && !pack.hidden)
[filteredPacks addObject:pack];
}
NSMutableArray *filteredMaskPacks = [[NSMutableArray alloc] init];
for (TGStickerPack *pack in masks[@"packs"])
{
if ([pack.packReference isKindOfClass:[TGStickerPackIdReference class]] && !pack.hidden)
[filteredMaskPacks addObject:pack];
}
NSArray *sortedStickerPacks = filteredPacks;
NSArray *sortedMaskStickerPacks = filteredMaskPacks;
NSMutableArray *reversed = [[NSMutableArray alloc] init];
for (id item in sortedStickerPacks)
{
[reversed addObject:item];
}
NSMutableArray<TGStickerPack *> *reversedMasks = [[NSMutableArray alloc] init];
for (id item in sortedMaskStickerPacks)
{
[reversedMasks addObject:item];
}
__strong TGPhotoStickersView *strongSelf = weakSelf;
if (strongSelf != nil) {
bool masksAreEqual = true;
if (strongSelf->_maskStickerPacks.count == reversedMasks.count) {
for (int setIndex = 0; setIndex < (int)strongSelf->_maskStickerPacks.count; setIndex++) {
if (strongSelf->_maskStickerPacks[setIndex].documents.count == reversedMasks[setIndex].documents.count) {
for (int documentIndex = 0; documentIndex < (int)_maskStickerPacks[setIndex].documents.count; documentIndex++) {
TGDocumentMediaAttachment *lhsDocument = _maskStickerPacks[setIndex].documents[documentIndex];
TGDocumentMediaAttachment *rhsDocument = reversedMasks[setIndex].documents[documentIndex];
if (![lhsDocument isEqual:rhsDocument]) {
masksAreEqual = false;
break;
}
}
if (!masksAreEqual) {
break;
}
} else {
masksAreEqual = false;
break;
}
}
} else {
masksAreEqual = false;
}
if (![strongSelf->_genericStickerPacks isEqual:reversed] || !masksAreEqual) {
[strongSelf setStickerPacks:reversed maskStickerPacks:reversedMasks recentDocuments:recentStickers];
}
[strongSelf updateCurrentSection];
}
}];
}
return self;
}
- (void)dealloc {
[_stickerPacksDisposable dispose];
}
- (CGSize)sizeThatFits:(CGSize)__unused size
{
return CGSizeMake(375.0f + TGPhotoStickersViewMargin * 2.0f, 568.0f + TGPhotoStickersViewMargin * 2.0f);
}
- (void)setSeparatorHidden:(bool)hidden animated:(bool)animated
{
if ((hidden && _separatorView.alpha < 1.0f - FLT_EPSILON) || (!hidden && _separatorView.alpha > FLT_EPSILON))
return;
if (animated)
{
[UIView animateWithDuration:0.2 animations:^
{
_separatorView.alpha = hidden ? 0.0f : 1.0f;
}];
}
else
{
_separatorView.alpha = hidden ? 0.0f : 1.0f;
}
}
#pragma mark -
- (void)handleStickerPress:(UILongPressGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateBegan)
{
CGPoint point = [recognizer locationInView:_collectionView];
for (NSIndexPath *indexPath in [_collectionView indexPathsForVisibleItems])
{
TGStickerCollectionViewCell *cell = (TGStickerCollectionViewCell *)[_collectionView cellForItemAtIndexPath:indexPath];
if (CGRectContainsPoint(cell.frame, point))
{
TGViewController *parentViewController = _parentViewController;
if (parentViewController != nil)
{
TGStickerItemPreviewView *previewView = [[TGStickerItemPreviewView alloc] initWithContext:_context frame:CGRectZero];
if ((NSInteger)TGScreenSize().height == 736)
previewView.eccentric = false;
TGItemPreviewController *controller = [[TGItemPreviewController alloc] initWithContext:_context parentController:parentViewController previewView:previewView];
_previewController = controller;
__weak TGPhotoStickersView *weakSelf = self;
controller.sourcePointForItem = ^(id item)
{
__strong TGPhotoStickersView *strongSelf = weakSelf;
if (strongSelf == nil)
return CGPointZero;
for (TGStickerCollectionViewCell *cell in strongSelf->_collectionView.visibleCells)
{
if ([cell.documentMedia isEqual:item])
{
NSIndexPath *indexPath = [strongSelf->_collectionView indexPathForCell:cell];
if (indexPath != nil)
return [strongSelf->_collectionView convertPoint:cell.center toView:nil];
}
}
return CGPointZero;
};
TGDocumentMediaAttachment *sticker = [self documentAtIndexPath:indexPath];
TGStickerPack *stickerPack = [self stickerPackAtIndexPath:indexPath];
NSArray *associations = _section == TGPhotoStickersViewSectionGeneric ? stickerPack.stickerAssociations : nil;
[previewView setSticker:sticker associations:associations];
[cell setHighlightedWithBounce:true];
}
break;
}
}
}
else if (recognizer.state == UIGestureRecognizerStateEnded || recognizer.state == UIGestureRecognizerStateCancelled)
{
TGItemPreviewController *controller = _previewController;
[controller dismiss];
for (TGStickerCollectionViewCell *cell in [_collectionView visibleCells])
[cell setHighlightedWithBounce:false];
}
}
- (void)handleStickerPan:(UIPanGestureRecognizer *)gestureRecognizer
{
if (_previewController != nil && gestureRecognizer.state == UIGestureRecognizerStateChanged)
{
TGStickerItemPreviewView *previewView = (TGStickerItemPreviewView *)_previewController.previewView;
CGPoint point = [gestureRecognizer locationInView:_collectionView];
CGPoint relativePoint = [gestureRecognizer locationInView:self];
if (CGRectContainsPoint(CGRectOffset(_collectionView.frame, 0, TGPhotoStickersPreloadInset), relativePoint))
{
for (NSIndexPath *indexPath in [_collectionView indexPathsForVisibleItems])
{
TGStickerCollectionViewCell *cell = (TGStickerCollectionViewCell *)[_collectionView cellForItemAtIndexPath:indexPath];
if (CGRectContainsPoint(cell.frame, point))
{
TGDocumentMediaAttachment *document = [self documentAtIndexPath:indexPath];
TGStickerPack *stickerPack = [self stickerPackAtIndexPath:indexPath];
NSArray *associations = _section == TGPhotoStickersViewSectionGeneric ? stickerPack.stickerAssociations : nil;
if (document != nil)
[previewView setSticker:document associations:associations];
[cell setHighlightedWithBounce:true];
}
else
{
[cell setHighlightedWithBounce:false];
}
}
}
}
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if (gestureRecognizer == _panRecognizer || otherGestureRecognizer == _panRecognizer)
return true;
return false;
}
#pragma mark -
- (void)setStickerPacks:(NSArray *)stickerPacks maskStickerPacks:(NSArray *)maskStickerPacks recentDocuments:(NSArray *)recentDocuments
{
_genericStickerPacks = stickerPacks;
_maskStickerPacks = maskStickerPacks;
_recentDocumentsSorted = recentDocuments;
_recentDocumentsOriginal = recentDocuments;
[self updateRecentDocuments];
[_collectionView reloadData];
[_tabPanel setStickerPacks:_section == TGPhotoStickersViewSectionMasks ? _maskStickerPacks : _genericStickerPacks showRecent:_section == TGPhotoStickersViewSectionMasks ? (_recentMasks.count != 0) : (_recentStickers.count != 0) showFavorite:false showGroup:false showGroupLast:false showGifs:false showTrendingFirst:false showTrendingLast:false];
}
- (void)updateRecentDocuments
{
NSMutableArray *recentStickers = [[NSMutableArray alloc] init];
NSMutableArray *recentMasks = [[NSMutableArray alloc] init];
NSMutableDictionary *packReferenceToPack = [[NSMutableDictionary alloc] init];
for (TGStickerPack *pack in _genericStickerPacks) {
if (pack.packReference != nil) {
packReferenceToPack[pack.packReference] = pack;
}
}
for (TGStickerPack *pack in _maskStickerPacks) {
if (pack.packReference != nil) {
packReferenceToPack[pack.packReference] = pack;
}
}
for (TGDocumentMediaAttachment *document in _recentDocumentsSorted) {
for (id attribute in document.attributes) {
if ([attribute isKindOfClass:[TGDocumentAttributeSticker class]]) {
if (((TGDocumentAttributeSticker *)attribute).packReference != nil) {
TGStickerPack *pack = packReferenceToPack[((TGDocumentAttributeSticker *)attribute).packReference];
if (pack != nil) {
if (pack.isMask) {
[recentMasks addObject:document];
} else {
[recentStickers addObject:document];
}
}
}
break;
}
}
}
if (recentStickers.count > 20) {
[recentStickers removeObjectsInRange:NSMakeRange(20, recentStickers.count - 20)];
}
if (recentMasks.count > 20) {
[recentMasks removeObjectsInRange:NSMakeRange(20, recentMasks.count - 20)];
}
_recentStickers = recentStickers;
_recentMasks = recentMasks;
_packReferenceToPack = packReferenceToPack;
}
#pragma mark -
- (void)cancelButtonPressed
{
[self dismissWithCompletion:nil];
}
- (void)scrollToSection:(NSUInteger)section
{
_ignoreSetSection = false;
[_tabPanel setCurrentStickerPackIndex:section animated:false];
NSArray *recentDocuments = _section == TGPhotoStickersViewSectionMasks ? _recentMasks : _recentStickers;
NSArray *stickerPacks = _section == TGPhotoStickersViewSectionMasks ? _maskStickerPacks : _genericStickerPacks;
if (section == 0)
{
if (recentDocuments.count != 0)
{
_ignoreSetSection = true;
[_collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0] atScrollPosition:UICollectionViewScrollPositionTop animated:true];
}
else
{
_ignoreSetSection = true;
[_collectionView setContentOffset:CGPointMake(0.0f, -_collectionView.contentInset.top) animated:true];
}
}
else
{
if (section == 1 && recentDocuments.count == 0) {
_ignoreSetSection = true;
[_collectionView setContentOffset:CGPointMake(0.0f, -_collectionView.contentInset.top) animated:true];
} else if (((TGStickerPack *)stickerPacks[section - 1]).documents.count != 0) {
UICollectionViewLayoutAttributes *attributes = [_collectionView layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]];
CGFloat verticalOffset = attributes.frame.origin.y - [self collectionView:_collectionView layout:_collectionLayout minimumLineSpacingForSectionAtIndex:section];
CGFloat effectiveInset = 0.0f;
if (verticalOffset < _collectionView.contentOffset.y)
effectiveInset = _collectionView.contentInset.top + TGPhotoStickersSectionHeaderHeight;
else
effectiveInset = TGPhotoStickersPreloadInset;
effectiveInset -= 8.0f;
CGFloat contentOffset = verticalOffset - effectiveInset;
if (contentOffset > _collectionView.contentSize.height - _collectionView.frame.size.height + _collectionView.contentInset.bottom) {
contentOffset = _collectionView.contentSize.height - _collectionView.frame.size.height + _collectionView.contentInset.bottom;
}
_ignoreSetSection = true;
[_collectionView setContentOffset:CGPointMake(0.0f, contentOffset) animated:true];
}
}
}
- (void)updateCurrentSection
{
NSArray *layoutAttributes = [_collectionLayout layoutAttributesForElementsInRect:CGRectMake(0.0f, _collectionView.contentOffset.y - 45.0f + TGPhotoStickersPreloadInset + 7.0f, _collectionView.frame.size.width, _collectionView.frame.size.height - 45.0f - TGPhotoStickersPreloadInset - 7.0f)];
NSInteger minSection = INT_MAX;
for (UICollectionViewLayoutAttributes *attributes in layoutAttributes)
{
minSection = MIN(attributes.indexPath.section, minSection);
}
if (minSection != INT_MAX)
[_tabPanel setCurrentStickerPackIndex:minSection animated:true];
}
#pragma mark -
- (void)present
{
self.userInteractionEnabled = true;
if ([_context currentSizeClass] == UIUserInterfaceSizeClassCompact)
{
void (^changeBlock)(void) = ^
{
if (iosMajorVersion() >= 8)
((UIVisualEffectView *)_blurView).effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
else
_blurView.alpha = 1.0f;
_wrapperView.alpha = 1.0f;
};
[UIView animateWithDuration:0.22 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:changeBlock completion:nil];
}
else
{
self.alpha = 0.0f;
self.layer.rasterizationScale = TGScreenScaling();
self.layer.shouldRasterize = true;
[UIView animateWithDuration:0.2 animations:^
{
self.alpha = 1.0f;
} completion:^(__unused BOOL finished)
{
self.layer.shouldRasterize = false;
}];
}
}
- (void)dismissWithCompletion:(void (^)(void))completion
{
self.userInteractionEnabled = false;
if ([_context currentSizeClass] == UIUserInterfaceSizeClassCompact)
{
void (^changeBlock)(void) = ^
{
if (iosMajorVersion() >= 8)
((UIVisualEffectView *)_blurView).effect = nil;
else
_blurView.alpha = 0.0f;
_wrapperView.alpha = 0.0f;
};
[UIView animateWithDuration:0.22 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:changeBlock completion:^(__unused BOOL finished)
{
if (self.dismissed != nil)
self.dismissed();
if (completion != nil)
completion();
}];
}
else
{
self.layer.rasterizationScale = TGScreenScaling();
self.layer.shouldRasterize = true;
[UIView animateWithDuration:0.2 animations:^
{
self.alpha = 0.0f;
} completion:^(__unused BOOL finished)
{
if (self.dismissed != nil)
self.dismissed();
if (completion != nil)
completion();
}];
}
}
- (void)dismissWithSnapshotView:(UIView *)outSnapshotview startPoint:(CGPoint)startPoint targetFrame:(CGRect)targetFrame targetRotation:(CGFloat)targetRotation completion:(void (^)(void))completion
{
[self dismissWithCompletion:^
{
for (UICollectionViewCell *cell in _collectionView.visibleCells)
cell.hidden = false;
}];
[self.outerView addSubview:outSnapshotview];
outSnapshotview.center = startPoint;
UIView *inSnapshotView = [outSnapshotview snapshotViewAfterScreenUpdates:false];
inSnapshotView.center = [self.outerView convertPoint:startPoint toView:self.targetView];
[self.targetView addSubview:inSnapshotView];
CGAffineTransform inTransform = CGAffineTransformInvert(self.targetView.transform);
inTransform = CGAffineTransformConcat(inTransform, CGAffineTransformInvert(self.targetView.superview.transform));
inSnapshotView.transform = inTransform;
CGFloat targetScale = targetFrame.size.width / outSnapshotview.frame.size.width * 0.985f;
CGAffineTransform targetTransform = CGAffineTransformScale(outSnapshotview.transform, targetScale, targetScale);
targetTransform = CGAffineTransformRotate(targetTransform, targetRotation);
CGAffineTransform middleTransform = CGAffineTransformScale(targetTransform, 1.17f, 1.17f);
[UIView animateWithDuration:0.35 delay:0.0f options:UIViewAnimationOptionCurveEaseInOut animations:^
{
CGPoint targetPoint = TGPaintCenterOfRect(targetFrame);
outSnapshotview.center = targetPoint;
inSnapshotView.center = [self.outerView convertPoint:targetPoint toView:self.targetView];
} completion:nil];
[UIView animateWithDuration:0.2 animations:^
{
outSnapshotview.transform = middleTransform;
inSnapshotView.transform = CGAffineTransformConcat(middleTransform, inTransform);
} completion:^(__unused BOOL finished)
{
[UIView animateWithDuration:0.15 delay:0.0f options:UIViewAnimationOptionCurveEaseOut animations:^
{
outSnapshotview.transform = targetTransform;
inSnapshotView.transform = CGAffineTransformConcat(targetTransform, inTransform);
outSnapshotview.alpha = 0.0f;
} completion:^(__unused BOOL finished)
{
[outSnapshotview removeFromSuperview];
[inSnapshotView removeFromSuperview];
if (completion != nil)
completion();
}];
}];
}
#pragma mark -
- (TGStickerPack *)stickerPackAtIndexPath:(NSIndexPath *)indexPath
{
if (_section == TGPhotoStickersViewSectionMasks) {
if (indexPath.section == 0)
{
TGDocumentMediaAttachment *document = [self documentAtIndexPath:indexPath];
id<TGStickerPackReference> packReference = document.stickerPackReference;
if (packReference != nil) {
return _packReferenceToPack[packReference];
}
}
else
{
return _maskStickerPacks[indexPath.section - 1];
}
} else {
if (indexPath.section == 0)
{
TGDocumentMediaAttachment *document = [self documentAtIndexPath:indexPath];
id<TGStickerPackReference> packReference = document.stickerPackReference;
if (packReference != nil) {
return _packReferenceToPack[packReference];
}
}
else
{
return _genericStickerPacks[indexPath.section - 1];
}
}
return nil;
}
- (TGDocumentMediaAttachment *)documentAtIndexPath:(NSIndexPath *)indexPath
{
if (_section == TGPhotoStickersViewSectionMasks) {
if (indexPath.section == 0)
return _recentMasks[indexPath.item];
else
return ((TGStickerPack *)_maskStickerPacks[indexPath.section - 1]).documents[indexPath.item];
} else {
if (indexPath.section == 0)
return _recentStickers[indexPath.item];
else
return ((TGStickerPack *)_genericStickerPacks[indexPath.section - 1]).documents[indexPath.item];
}
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
TGStickerCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TGStickerCollectionViewCell" forIndexPath:indexPath];
[cell setDocumentMedia:[self documentAtIndexPath:indexPath]];
return cell;
}
- (void)collectionView:(UICollectionView *)__unused collectionView setupSectionHeaderView:(TGPhotoStickersSectionHeaderView *)sectionHeaderView forSectionHeader:(TGPhotoStickersSectionHeader *)sectionHeader
{
NSString *title = TGLocalized(@"Paint.RecentStickers");
if (sectionHeader.index > 0)
{
if (_section == TGPhotoStickersViewSectionMasks) {
TGStickerPack *stickerPack = _maskStickerPacks[sectionHeader.index - 1];
title = stickerPack.title;
} else {
TGStickerPack *stickerPack = _genericStickerPacks[sectionHeader.index - 1];
title = stickerPack.title;
}
}
[sectionHeaderView setTitle:title];
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)__unused collectionView
{
if (_section == TGPhotoStickersViewSectionMasks) {
return 1 + _maskStickerPacks.count;
} else {
return 1 + _genericStickerPacks.count;
}
}
- (NSInteger)collectionView:(UICollectionView *)__unused collectionView numberOfItemsInSection:(NSInteger)section
{
if (_section == TGPhotoStickersViewSectionMasks) {
if (section == 0) {
return (NSInteger)_recentMasks.count;
} else {
return ((TGStickerPack *)_maskStickerPacks[section - 1]).documents.count;
}
} else {
if (section == 0) {
return (NSInteger)_recentStickers.count;
} else {
return ((TGStickerPack *)_genericStickerPacks[section - 1]).documents.count;
}
}
}
- (CGSize)collectionView:(UICollectionView *)__unused collectionView layout:(UICollectionViewLayout*)__unused collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)__unused indexPath
{
return CGSizeMake(62.0f, 62.0f);
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)__unused collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
CGFloat sideInset = (collectionView.frame.size.width < 330.0f) ? 3.0f : 15.0f;
CGFloat bottomInset = (section == [self numberOfSectionsInCollectionView:collectionView] - 1) ? 14.0f : 0.0f;
NSArray *recent = (_section == TGPhotoStickersViewSectionMasks) ? _recentMasks : _recentStickers;
if (section == 0 && recent.count == 0)
return UIEdgeInsetsMake(0, 0, 0, 0);
return UIEdgeInsetsMake(TGPhotoStickersSectionHeaderHeight, sideInset, bottomInset, sideInset);
}
- (CGFloat)collectionView:(UICollectionView *)__unused collectionView layout:(UICollectionViewLayout*)__unused collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)__unused section
{
return 7.0f;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)__unused collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)__unused section
{
return (collectionView.frame.size.width < 330.0f) ? 0.0f : 4.0f;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
TGStickerCollectionViewCell *cell = (TGStickerCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
if ([cell isEnabled])
{
[cell setDisabledTimeout];
if (_section == TGPhotoStickersViewSectionMasks) {
TGDocumentMediaAttachment *document = [self documentAtIndexPath:indexPath];
if (self.stickerSelected != nil)
self.stickerSelected(document, [cell.superview convertPoint:cell.center toView:self.outerView], self, [cell snapshotViewAfterScreenUpdates:false]);
} else {
TGDocumentMediaAttachment *document = [self documentAtIndexPath:indexPath];
if (self.stickerSelected != nil)
self.stickerSelected(document, [cell.superview convertPoint:cell.center toView:self.outerView], self, [cell snapshotViewAfterScreenUpdates:false]);
}
cell.hidden = true;
}
}
#pragma mark -
- (void)scrollViewDidScroll:(UIScrollView *)__unused scrollView
{
if (!_ignoreSetSection)
[self updateCurrentSection];
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)__unused scrollView
{
_ignoreSetSection = false;
[self updateCurrentSection];
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)__unused scrollView
{
_ignoreSetSection = false;
[self updateCurrentSection];
}
#pragma mark -
- (void)setSafeAreaInset:(UIEdgeInsets)safeAreaInset
{
_safeAreaInset = safeAreaInset;
_tabPanel.safeAreaInset = safeAreaInset;
_collectionView.contentInset = UIEdgeInsetsMake(TGPhotoStickersPreloadInset - TGPhotoStickersSectionHeaderHeight, 0.0f, TGPhotoStickersPreloadInset + _safeAreaInset.bottom, 0.0f);
[self setNeedsLayout];
}
- (void)layoutSubviews
{
CGRect bounds = self.bounds;
bool compact = [_context currentSizeClass] == UIUserInterfaceSizeClassCompact;
if (compact)
{
CGRect previousRect = _blurView.frame;
_blurView.frame = self.bounds;
if (!CGRectEqualToRect(previousRect, _blurView.frame))
[_collectionLayout invalidateLayout];
_segmentedControl.frame = CGRectMake(12.0f + _safeAreaInset.left, 12.0f + _safeAreaInset.top, self.frame.size.width - _safeAreaInset.left - _safeAreaInset.right - 17.0f * 2 - _cancelButton.frame.size.width, _segmentedControl.frame.size.height);
}
else
{
_wrapperView.frame = CGRectMake(0.0f, 0.0f, self.bounds.size.width, self.bounds.size.height - TGPhotoStickersViewMargin);
_backgroundView.frame = CGRectMake(TGPhotoStickersViewMargin, TGPhotoStickersViewMargin, self.frame.size.width - TGPhotoStickersViewMargin * 2, self.frame.size.height - TGPhotoStickersViewMargin * 2 + 13.0f);
bounds = CGRectInset(bounds, TGPhotoStickersViewMargin, TGPhotoStickersViewMargin);
_segmentedControl.frame = CGRectMake(bounds.origin.x + 12.0f, bounds.origin.y + 12.0f, bounds.size.width - 24.0f, _segmentedControl.frame.size.height);
}
if (compact)
{
_cancelButton.frame = CGRectMake(bounds.origin.x + bounds.size.width - _cancelButton.frame.size.width - 11.0f - _safeAreaInset.right, bounds.origin.y + 4.0f + _safeAreaInset.top, _cancelButton.frame.size.width, 44.0f);
}
_tabPanel.frame = CGRectMake(bounds.origin.x, bounds.origin.y + 50.0f + _safeAreaInset.top, bounds.size.width, _tabPanel.frame.size.height);
_collectionWrapperView.frame = CGRectMake(bounds.origin.x + _safeAreaInset.left, CGRectGetMaxY(_tabPanel.frame) + TGPhotoStickersSectionHeaderHeight - 8.0f, bounds.size.width - _safeAreaInset.left - _safeAreaInset.right, bounds.size.height - CGRectGetMaxY(_tabPanel.frame) + bounds.origin.y - TGPhotoStickersSectionHeaderHeight + 8.0f);
_collectionView.frame = CGRectMake(0.0f, -TGPhotoStickersPreloadInset + 8.0f, _collectionWrapperView.frame.size.width, _collectionWrapperView.frame.size.height + 2 * TGPhotoStickersPreloadInset);
_headersView.frame = [_collectionWrapperView convertRect:_collectionView.frame toView:_wrapperView];
CGFloat thickness = TGScreenPixel;
_separatorView.frame = CGRectMake(bounds.origin.x, bounds.origin.y + 143.0f - thickness, bounds.size.width, thickness);
}
- (void)segmentedControlChanged
{
int index = (int)_segmentedControl.selectedSegmentIndex;
TGPhotoStickersViewSection section = (TGPhotoStickersViewSection)index;
if (section == TGPhotoStickersViewSectionMasks)
_stickersContentOffset = _collectionView.contentOffset.y;
else
_masksContentOffset = _collectionView.contentOffset.y;
if (section != _section) {
_section = section;
[_tabPanel setStickerPacks:_section == TGPhotoStickersViewSectionMasks ? _maskStickerPacks : _genericStickerPacks showRecent:_section == TGPhotoStickersViewSectionMasks ? (_recentMasks.count != 0) : (_recentStickers.count != 0) showFavorite:false showGroup:false showGroupLast:false showGifs:false showTrendingFirst:false showTrendingLast:false];
[_collectionView reloadData];
[self updateCurrentSection];
CGPoint contentOffset = CGPointMake(0, -_collectionView.contentInset.top);
if (section == TGPhotoStickersViewSectionMasks && fabs(_masksContentOffset - FLT_MAX) > FLT_EPSILON)
contentOffset = CGPointMake(0, _masksContentOffset);
else if (section == TGPhotoStickersViewSectionGeneric && fabs(_stickersContentOffset - FLT_MAX) > FLT_EPSILON)
contentOffset = CGPointMake(0, _stickersContentOffset);
[_collectionView setContentOffset:contentOffset];
}
}
@end

View File

@ -1,13 +0,0 @@
#import <UIKit/UIKit.h>
@class TGDocumentMediaAttachment;
@interface TGStickerCollectionViewCell : UICollectionViewCell
@property (nonatomic, strong) TGDocumentMediaAttachment *documentMedia;
- (void)setDisabledTimeout;
- (bool)isEnabled;
- (void)setHighlightedWithBounce:(bool)highlighted;
@end

View File

@ -1,116 +0,0 @@
#import "TGStickerCollectionViewCell.h"
#import "LegacyComponentsInternal.h"
#import "TGDocumentMediaAttachment.h"
#import "TGImageUtils.h"
#import "TGStringUtils.h"
#import <LegacyComponents/TGImageView.h>
@interface TGStickerCollectionViewCell ()
{
TGImageView *_imageView;
CFAbsoluteTime _disableTime;
bool _highlighted;
}
@end
@implementation TGStickerCollectionViewCell
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil)
{
_imageView = [[TGImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 62.0f, 62.0f)];
_imageView.contentMode = UIViewContentModeScaleAspectFit;
[self.contentView addSubview:_imageView];
if (iosMajorVersion() >= 11)
_imageView.accessibilityIgnoresInvertColors = true;
}
return self;
}
- (void)prepareForReuse
{
[super prepareForReuse];
[_imageView reset];
[_imageView.layer removeAllAnimations];
_imageView.alpha = 1.0f;
_highlighted = false;
_imageView.transform = CGAffineTransformIdentity;
}
- (void)setDocumentMedia:(TGDocumentMediaAttachment *)documentMedia
{
_documentMedia = documentMedia;
NSMutableString *uri = [[NSMutableString alloc] initWithString:@"sticker-preview://?"];
if (documentMedia.documentId != 0)
{
[uri appendFormat:@"documentId=%" PRId64 "", documentMedia.documentId];
TGMediaOriginInfo *originInfo = documentMedia.originInfo ?: [TGMediaOriginInfo mediaOriginInfoForDocumentAttachment:documentMedia];
if (originInfo != nil)
[uri appendFormat:@"&origin_info=%@", [originInfo stringRepresentation]];
}
else
[uri appendFormat:@"localDocumentId=%" PRId64 "", documentMedia.localDocumentId];
[uri appendFormat:@"&accessHash=%" PRId64 "", documentMedia.accessHash];
[uri appendFormat:@"&datacenterId=%" PRId32 "", (int32_t)documentMedia.datacenterId];
NSString *legacyThumbnailUri = [documentMedia.thumbnailInfo imageUrlForLargestSize:NULL];
if (legacyThumbnailUri != nil)
[uri appendFormat:@"&legacyThumbnailUri=%@", [TGStringUtils stringByEscapingForURL:legacyThumbnailUri]];
[uri appendFormat:@"&width=124&height=124"];
[uri appendFormat:@"&dimwidth=%d&dimheight=%d", (int)[documentMedia pictureSize].width, (int)[documentMedia pictureSize].height];
[uri appendFormat:@"&highQuality=1"];
[_imageView loadUri:uri withOptions:nil];
}
- (void)setDisabledTimeout
{
[UIView animateWithDuration:0.1 animations:^{
_imageView.alpha = 0.3f;
} completion:^(BOOL finished)
{
if (finished)
{
[UIView animateWithDuration:1.0 animations:^{
_imageView.alpha = 1.0f;
}];
}
}];
_disableTime = CFAbsoluteTimeGetCurrent();
}
- (bool)isEnabled
{
return CFAbsoluteTimeGetCurrent() > _disableTime + 1.1;
}
- (void)setHighlightedWithBounce:(bool)highlighted
{
if (_highlighted != highlighted)
{
_highlighted = highlighted;
if (iosMajorVersion() >= 8)
{
[UIView animateWithDuration:0.6 delay:0.0 usingSpringWithDamping:0.43f initialSpringVelocity:0.0f options:UIViewAnimationOptionBeginFromCurrentState animations:^
{
if (_highlighted)
_imageView.transform = CGAffineTransformMakeScale(0.8f, 0.8f);
else
_imageView.transform = CGAffineTransformIdentity;
} completion:nil];
}
}
}
@end

View File

@ -1,17 +0,0 @@
#import <LegacyComponents/TGItemMenuSheetPreviewView.h>
@class TGDocumentMediaAttachment;
@class TGStickerPack;
@interface TGStickerItemPreviewView : TGItemMenuSheetPreviewView
@property (nonatomic, readonly) TGStickerPack *stickerPack;
@property (nonatomic, readonly) bool recent;
@property (nonatomic, readonly) CFAbsoluteTime lastFeedbackTime;
- (void)setSticker:(TGDocumentMediaAttachment *)sticker stickerPack:(TGStickerPack *)stickerPack recent:(bool)recent;
- (void)setSticker:(TGDocumentMediaAttachment *)sticker associations:(NSArray *)associations;
- (void)presentActions;
@end

View File

@ -1,258 +0,0 @@
#import "TGStickerItemPreviewView.h"
#import "TGMenuSheetController.h"
#import "LegacyComponentsInternal.h"
#import "LegacyComponentsGlobals.h"
#import "TGStickerPack.h"
#import "TGStickerAssociation.h"
#import "TGImageView.h"
static const CGFloat TGStickersTopMargin = 140.0f;
@interface TGStickerItemPreviewView ()
{
TGDocumentMediaAttachment *_sticker;
TGImageView *_imageView;
UIView *_altWrapperView;
UIImpactFeedbackGenerator *_feedbackGenerator;
}
@end
@implementation TGStickerItemPreviewView
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context frame:(CGRect)frame
{
self = [super initWithContext:context frame:frame];
if (self != nil)
{
self.eccentric = true;
self.dontBlurOnPresentation = true;
[self insertSubview:self.dimView belowSubview:self.wrapperView];
bool isDark = false;
if ([[LegacyComponentsGlobals provider] respondsToSelector:@selector(menuSheetPallete)])
isDark = [[LegacyComponentsGlobals provider] menuSheetPallete].isDark;
self.dimView.backgroundColor = [UIColor colorWithWhite:isDark ? 0.0f : 1.0f alpha:0.7f];
_altWrapperView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 40.0f)];
[self.wrapperView addSubview:_altWrapperView];
_imageView = [[TGImageView alloc] init];
_imageView.expectExtendedEdges = true;
[self.wrapperView addSubview:_imageView];
if (iosMajorVersion() >= 11)
{
_altWrapperView.accessibilityIgnoresInvertColors = true;
_imageView.accessibilityIgnoresInvertColors = true;
}
if (iosMajorVersion() >= 10)
_feedbackGenerator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight];
}
return self;
}
- (void)_didAppear
{
[self addSubview:_containerView];
_altWrapperView.frame = [self.wrapperView convertRect:_altWrapperView.frame fromView:self];
[self addSubview:_altWrapperView];
}
- (void)_willDisappear
{
if (_altWrapperView.superview != self.wrapperView)
{
_altWrapperView.frame = [self convertRect:_altWrapperView.frame toView:self.wrapperView];
[self.wrapperView addSubview:_altWrapperView];
}
}
- (void)presentActions
{
[self presentActions:^
{
CGPoint wrapperCenter = [self _wrapperViewContainerCenter];
self.wrapperView.center = wrapperCenter;
if (self.frame.size.width > self.frame.size.height)
_altWrapperView.alpha = 0.0f;
}];
}
- (CGPoint)_wrapperViewContainerCenter
{
CGRect bounds = self.bounds;
CGFloat y = 0.0f;
if (bounds.size.height > bounds.size.width && self.eccentric)
y = bounds.size.height / 3.0f;
else if (!TGIsPad() && bounds.size.height < bounds.size.width && self.actionsPresented)
y = bounds.size.height / 4.0f;
else
y = bounds.size.height / 2.0f;
return CGPointMake(bounds.size.width / 2.0f, y);
}
- (id)item
{
return _sticker;
}
- (void)setSticker:(TGDocumentMediaAttachment *)sticker stickerPack:(TGStickerPack *)stickerPack recent:(bool)recent
{
_stickerPack = stickerPack;
_recent = recent;
[self setSticker:sticker associations:stickerPack.stickerAssociations];
}
- (void)setSticker:(TGDocumentMediaAttachment *)sticker associations:(NSArray *)associations
{
if (sticker.documentId != _sticker.documentId || sticker.localDocumentId != _sticker.localDocumentId)
{
[_feedbackGenerator impactOccurred];
[_feedbackGenerator prepare];
_lastFeedbackTime = CFAbsoluteTimeGetCurrent();
bool animated = false;
if (iosMajorVersion() >= 7 && _sticker != sticker)
animated = true;
_sticker = sticker;
CGSize imageSize = CGSizeZero;
bool isSticker = false;
for (id attribute in sticker.attributes)
{
if ([attribute isKindOfClass:[TGDocumentAttributeImageSize class]])
imageSize = ((TGDocumentAttributeImageSize *)attribute).size;
else if ([attribute isKindOfClass:[TGDocumentAttributeSticker class]])
isSticker = true;
}
CGSize displaySize = [self displaySizeForSize:imageSize];
NSMutableString *imageUri = [[NSMutableString alloc] init];
[imageUri appendString:@"sticker://?"];
if (_sticker.documentId != 0)
{
[imageUri appendFormat:@"&documentId=%" PRId64, _sticker.documentId];
TGMediaOriginInfo *originInfo = _sticker.originInfo ?: [TGMediaOriginInfo mediaOriginInfoForDocumentAttachment:_sticker];
if (originInfo != nil)
[imageUri appendFormat:@"&origin_info=%@", [originInfo stringRepresentation]];
}
else
{
[imageUri appendFormat:@"&localDocumentId=%" PRId64, _sticker.localDocumentId];
}
[imageUri appendFormat:@"&accessHash=%" PRId64, _sticker.accessHash];
[imageUri appendFormat:@"&datacenterId=%d", (int)_sticker.datacenterId];
[imageUri appendFormat:@"&fileName=%@", [TGStringUtils stringByEscapingForURL:_sticker.fileName]];
[imageUri appendFormat:@"&size=%d", (int)_sticker.size];
[imageUri appendFormat:@"&width=%d&height=%d", (int)displaySize.width, (int)displaySize.height];
[imageUri appendFormat:@"&mime-type=%@", [TGStringUtils stringByEscapingForURL:_sticker.mimeType]];
_imageView.frame = CGRectMake(CGFloor((self.frame.size.width - displaySize.width) / 2.0f), CGFloor((self.frame.size.height - displaySize.height) / 2.0f), displaySize.width, displaySize.height);
[_imageView loadUri:imageUri withOptions:@{}];
NSMutableArray *alts = [[NSMutableArray alloc] init];
for (TGStickerAssociation *association in associations)
{
for (NSNumber *nDocumentId in association.documentIds)
{
if ((int64_t)[nDocumentId longLongValue] == sticker.documentId && [association.key containsSingleEmoji])
{
if ([association.key characterAtIndex:0] == 0x2639)
[alts addObject:@"\u2639\ufe0f"];
else
[alts addObject:association.key];
}
}
if (alts.count == 5)
break;
}
[self updateAltViews:alts animated:animated];
if (_altWrapperView.superview == self.wrapperView)
{
_altWrapperView.frame = CGRectMake(CGFloor(self.frame.size.width - _altWrapperView.frame.size.width) / 2.0f, CGRectGetMidY(self.bounds) - TGStickersTopMargin, _altWrapperView.frame.size.width, _altWrapperView.frame.size.height);
}
if (animated)
{
self.wrapperView.transform = CGAffineTransformMakeScale(0.7f, 0.7f);
[UIView animateWithDuration:0.3 delay:0.0 usingSpringWithDamping:0.72f initialSpringVelocity:0.0f options:0 animations:^
{
self.wrapperView.transform = CGAffineTransformIdentity;
} completion:nil];
}
}
}
- (void)updateAltViews:(NSArray *)alts animated:(bool)animated
{
for (UIView *view in _altWrapperView.subviews)
[view removeFromSuperview];
NSInteger i = 0;
UIView *lastAltView = nil;
for (NSString *alt in alts)
{
UILabel *altView = [[UILabel alloc] initWithFrame:CGRectZero];
altView.backgroundColor = [UIColor clearColor];
altView.font = TGSystemFontOfSize(32);
altView.text = alt;
[altView sizeToFit];
[_altWrapperView addSubview:altView];
altView.frame = CGRectMake(i * 42.0f, 0, altView.frame.size.width, altView.frame.size.height);
i++;
if (animated)
{
altView.transform = CGAffineTransformMakeScale(0.7f, 0.7f);
[UIView animateWithDuration:0.3 delay:0.0 usingSpringWithDamping:0.72f initialSpringVelocity:0.0f options:0 animations:^
{
altView.transform = CGAffineTransformIdentity;
} completion:nil];
}
lastAltView = altView;
}
CGRect frame = _altWrapperView.frame;
frame.size.width = CGRectGetMaxX(lastAltView.frame);
_altWrapperView.frame = frame;
}
- (CGSize)displaySizeForSize:(CGSize)size
{
CGSize maxSize = CGSizeMake(160, 170);
return TGFitSize(CGSizeMake(size.width / 2.0f, size.height / 2.0f), maxSize);
}
- (void)layoutSubviews
{
[super layoutSubviews];
CGPoint wrapperCenter = [self _wrapperViewContainerCenter];
if (_altWrapperView.superview == self)
{
_altWrapperView.frame = CGRectMake(wrapperCenter.x - _altWrapperView.frame.size.width / 2.0f, wrapperCenter.y - TGStickersTopMargin, _altWrapperView.frame.size.width, _altWrapperView.frame.size.height);
}
}
@end

View File

@ -1,20 +0,0 @@
#import <UIKit/UIKit.h>
#import "TGStickerKeyboardTabPanel.h"
@class TGDocumentMediaAttachment;
@interface TGStickerKeyboardTabCell : UICollectionViewCell
- (void)setFavorite;
- (void)setRecent;
- (void)setNone;
- (void)setDocumentMedia:(TGDocumentMediaAttachment *)documentMedia;
- (void)setUrl:(NSString *)avatarUrl peerId:(int64_t)peerId title:(NSString *)title;
- (void)setStyle:(TGStickerKeyboardViewStyle)style;
- (void)setPallete:(TGStickerKeyboardPallete *)pallete;
- (void)setInnerAlpha:(CGFloat)innerAlpha;
@end

View File

@ -1,313 +0,0 @@
#import "TGStickerKeyboardTabCell.h"
#import "LegacyComponentsInternal.h"
#import "TGImageUtils.h"
#import "TGDocumentMediaAttachment.h"
#import "TGStringUtils.h"
#import "TGLetteredAvatarView.h"
#import <LegacyComponents/TGImageView.h>
static void setViewFrame(UIView *view, CGRect frame)
{
CGAffineTransform transform = view.transform;
view.transform = CGAffineTransformIdentity;
if (!CGRectEqualToRect(view.frame, frame))
view.frame = frame;
view.transform = transform;
}
@interface TGStickerKeyboardTabCell ()
{
TGImageView *_imageView;
TGLetteredAvatarView *_avatarView;
TGStickerKeyboardViewStyle _style;
bool _favorite;
bool _recent;
TGStickerKeyboardPallete *_pallete;
}
@end
@implementation TGStickerKeyboardTabCell
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil)
{
_style = TGStickerKeyboardViewDefaultStyle;
self.clipsToBounds = true;
self.selectedBackgroundView = [[UIView alloc] init];
_imageView = [[TGImageView alloc] init];
_imageView.contentMode = UIViewContentModeScaleAspectFit;
[self.contentView addSubview:_imageView];
}
return self;
}
- (void)setPallete:(TGStickerKeyboardPallete *)pallete
{
if (pallete == nil || _pallete == pallete)
return;
_pallete = pallete;
self.selectedBackgroundView.backgroundColor = pallete.selectionColor;
}
- (void)prepareForReuse
{
[super prepareForReuse];
[_imageView reset];
}
- (void)_updateIcon:(UIImage *)image
{
if (_style == TGStickerKeyboardViewPaintDarkStyle)
{
UIColor *color = self.selected ? [UIColor blackColor] : UIColorRGB(0xb4b5b5);
_imageView.image = TGTintedImage(image, color);
if (iosMajorVersion() >= 11)
_imageView.accessibilityIgnoresInvertColors = true;
}
else
{
_imageView.image = image;
if (iosMajorVersion() >= 11)
_imageView.accessibilityIgnoresInvertColors = false;
}
}
- (void)setSelected:(BOOL)selected
{
[super setSelected:selected];
if (_pallete != nil)
{
if (_recent)
[self _updateIcon:_pallete.recentIcon];
else if (_favorite)
[self _updateIcon:_pallete.favoritesIcon];
}
else
{
if (_recent)
[self _updateIcon:TGComponentsImageNamed(@"StickerKeyboardRecentTab.png")];
else if (_favorite)
[self _updateIcon:TGComponentsImageNamed(@"StickerKeyboardFavoriteTab.png")];
}
}
- (void)setFavorite
{
_recent = false;
_favorite = true;
_avatarView.hidden = true;
_imageView.hidden = false;
[_imageView reset];
_imageView.contentMode = UIViewContentModeCenter;
[self _updateIcon:_pallete != nil ? _pallete.favoritesIcon : TGComponentsImageNamed(@"StickerKeyboardFavoriteTab.png")];
}
- (void)setRecent
{
_recent = true;
_favorite = false;
_avatarView.hidden = true;
_imageView.hidden = false;
[_imageView reset];
_imageView.contentMode = UIViewContentModeCenter;
[self _updateIcon:_pallete != nil ? _pallete.recentIcon : TGComponentsImageNamed(@"StickerKeyboardRecentTab.png")];
}
- (void)setNone
{
_recent = false;
_favorite = false;
_avatarView.hidden = true;
_imageView.hidden = false;
[_imageView reset];
_imageView.image = nil;
}
- (void)setDocumentMedia:(TGDocumentMediaAttachment *)documentMedia
{
_recent = false;
_favorite = false;
_avatarView.hidden = true;
_imageView.hidden = false;
_imageView.contentMode = UIViewContentModeScaleAspectFit;
NSMutableString *uri = [[NSMutableString alloc] initWithString:@"sticker-preview://?"];
if (documentMedia.documentId != 0)
{
[uri appendFormat:@"documentId=%" PRId64 "", documentMedia.documentId];
TGMediaOriginInfo *originInfo = documentMedia.originInfo ?: [TGMediaOriginInfo mediaOriginInfoForDocumentAttachment:documentMedia];
if (originInfo != nil)
[uri appendFormat:@"&origin_info=%@", [originInfo stringRepresentation]];
}
else
{
[uri appendFormat:@"localDocumentId=%" PRId64 "", documentMedia.localDocumentId];
}
[uri appendFormat:@"&accessHash=%" PRId64 "", documentMedia.accessHash];
[uri appendFormat:@"&datacenterId=%" PRId32 "", (int32_t)documentMedia.datacenterId];
NSString *legacyThumbnailUri = [documentMedia.thumbnailInfo imageUrlForLargestSize:NULL];
if (legacyThumbnailUri != nil)
[uri appendFormat:@"&legacyThumbnailUri=%@", [TGStringUtils stringByEscapingForURL:legacyThumbnailUri]];
[uri appendFormat:@"&width=33&height=33"];
[uri appendFormat:@"&highQuality=1"];
[_imageView loadUri:uri withOptions:nil];
if (iosMajorVersion() >= 11)
_imageView.accessibilityIgnoresInvertColors = true;
}
- (void)setUrl:(NSString *)avatarUrl peerId:(int64_t)peerId title:(NSString *)title
{
_recent = false;
_favorite = false;
_imageView.contentMode = UIViewContentModeScaleAspectFit;
CGFloat diameter = 32.0f;
static UIImage *placeholder = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
UIGraphicsBeginImageContextWithOptions(CGSizeMake(diameter, diameter), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
//!placeholder
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextFillEllipseInRect(context, CGRectMake(0.0f, 0.0f, diameter, diameter));
CGContextSetStrokeColorWithColor(context, UIColorRGB(0xd9d9d9).CGColor);
CGContextSetLineWidth(context, 1.0f);
CGContextStrokeEllipseInRect(context, CGRectMake(0.5f, 0.5f, diameter - 1.0f, diameter - 1.0f));
placeholder = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
});
if (_avatarView == nil)
{
_avatarView = [[TGLetteredAvatarView alloc] initWithFrame:_imageView.frame];
[_avatarView setSingleFontSize:18.0f doubleFontSize:18.0f useBoldFont:false];
[_imageView.superview addSubview:_avatarView];
}
if (avatarUrl.length != 0)
{
_avatarView.fadeTransitionDuration = 0.3;
if (![avatarUrl isEqualToString:_avatarView.currentUrl])
[_avatarView loadImage:avatarUrl filter:@"circle:32x32" placeholder:placeholder];
}
else
{
[_avatarView loadGroupPlaceholderWithSize:CGSizeMake(diameter, diameter) conversationId:peerId title:title placeholder:placeholder];
}
_avatarView.hidden = false;
_imageView.hidden = true;
}
- (void)setStyle:(TGStickerKeyboardViewStyle)style
{
_style = style;
switch (style)
{
case TGStickerKeyboardViewDarkBlurredStyle:
{
self.selectedBackgroundView.backgroundColor = UIColorRGB(0x393939);
}
break;
case TGStickerKeyboardViewPaintStyle:
{
self.selectedBackgroundView.backgroundColor = UIColorRGB(0xdadada);
self.selectedBackgroundView.layer.cornerRadius = 8.0f;
self.selectedBackgroundView.clipsToBounds = true;
}
break;
case TGStickerKeyboardViewPaintDarkStyle:
{
self.selectedBackgroundView.backgroundColor = UIColorRGBA(0xfbfffe, 0.47f);
self.selectedBackgroundView.layer.cornerRadius = 8.0f;
self.selectedBackgroundView.clipsToBounds = true;
if (_recent)
[self _updateIcon:TGComponentsImageNamed(@"StickerKeyboardRecentTab.png")];
else if (_favorite)
[self _updateIcon:TGComponentsImageNamed(@"StickerKeyboardFavoriteTab.png")];
}
break;
default:
{
self.selectedBackgroundView.backgroundColor = _pallete != nil ? _pallete.selectionColor : UIColorRGB(0xe6e7e9);
self.selectedBackgroundView.layer.cornerRadius = 8.0f;
self.selectedBackgroundView.clipsToBounds = true;
}
break;
}
}
- (void)setInnerAlpha:(CGFloat)innerAlpha
{
CGAffineTransform transform = CGAffineTransformMakeTranslation(0.0f, 36.0f / 2.0f * (1.0f - innerAlpha));
transform = CGAffineTransformScale(transform, innerAlpha, innerAlpha);
_imageView.transform = transform;
_avatarView.transform = transform;
self.selectedBackgroundView.transform = transform;
}
- (void)layoutSubviews
{
[super layoutSubviews];
CGFloat imageSide = 33.0f;
if (_style == TGStickerKeyboardViewDefaultStyle)
{
imageSide = 28.0f;
setViewFrame(_imageView, CGRectMake(CGFloor((self.frame.size.width - imageSide) / 2.0f), 4.0f, imageSide, imageSide));
setViewFrame(_avatarView, CGRectMake(CGFloor((self.frame.size.width - imageSide) / 2.0f), 4.0f, imageSide, imageSide));
setViewFrame(self.selectedBackgroundView, CGRectMake(floor((self.frame.size.width - 36.0f) / 2.0f), 0, 36.0f, 36.0f));
}
else
{
_imageView.frame = CGRectMake(CGFloor((self.frame.size.width - imageSide) / 2.0f), 6.0f, imageSide, imageSide);
_avatarView.frame = _imageView.frame;
if (_style == TGStickerKeyboardViewPaintStyle)
{
self.selectedBackgroundView.frame = CGRectMake(floor((self.frame.size.width - self.frame.size.height) / 2.0f), 0, self.frame.size.height, self.frame.size.height);
}
}
}
@end

View File

@ -1,642 +0,0 @@
#import "TGStickerKeyboardTabPanel.h"
#import "LegacyComponentsInternal.h"
#import "TGStickerKeyboardTabCell.h"
#import "TGStickerKeyboardTabSettingsCell.h"
#import "TGStickerPack.h"
@interface TGStickerKeyboardTabPanel () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
{
TGStickerKeyboardViewStyle _style;
bool _showRecent;
bool _showFavorite;
bool _showGroup;
bool _showGroupLast;
bool _showGifs;
bool _showTrendingFirst;
bool _showTrendingLast;
NSArray *_stickerPacks;
UICollectionView *_collectionView;
UICollectionViewFlowLayout *_collectionLayout;
UIView *_bottomStripe;
NSString *_trendingStickersBadge;
CGFloat _innerAlpha;
NSString *_avatarUrl;
int64_t _peerId;
NSString *_title;
bool _expanded;
}
@end
@implementation TGStickerKeyboardTabPanel
- (instancetype)initWithFrame:(CGRect)frame
{
return [self initWithFrame:frame style:TGStickerKeyboardViewDefaultStyle];
}
- (instancetype)initWithFrame:(CGRect)frame style:(TGStickerKeyboardViewStyle)style
{
self = [super initWithFrame:frame];
if (self != nil)
{
_style = style;
_collectionLayout = [[UICollectionViewFlowLayout alloc] init];
_collectionLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, frame.size.width, frame.size.height) collectionViewLayout:_collectionLayout];
if (iosMajorVersion() >= 11)
_collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
_collectionView.delegate = self;
_collectionView.dataSource = self;
_collectionView.backgroundColor = nil;
_collectionView.opaque = false;
_collectionView.showsHorizontalScrollIndicator = false;
_collectionView.showsVerticalScrollIndicator = false;
_collectionView.contentInset = UIEdgeInsetsZero;
[_collectionView registerClass:[TGStickerKeyboardTabCell class] forCellWithReuseIdentifier:@"TGStickerKeyboardTabCell"];
[_collectionView registerClass:[TGStickerKeyboardTabSettingsCell class] forCellWithReuseIdentifier:@"TGStickerKeyboardTabSettingsCell"];
[self addSubview:_collectionView];
switch (style)
{
case TGStickerKeyboardViewDarkBlurredStyle:
{
self.backgroundColor = UIColorRGB(0x444444);
}
break;
case TGStickerKeyboardViewPaintStyle:
{
self.backgroundColor = [UIColor clearColor];
_collectionView.contentInset = UIEdgeInsetsMake(0.0f, 12.0f, 0.0f, 12.0f);
}
break;
case TGStickerKeyboardViewPaintDarkStyle:
{
self.backgroundColor = [UIColor clearColor];
_collectionView.contentInset = UIEdgeInsetsMake(0.0f, 12.0f, 0.0f, 12.0f);
}
break;
default:
{
self.backgroundColor = UIColorRGB(0xf7f7f7);
CGFloat stripeHeight = TGScreenPixel;
_bottomStripe = [[UIView alloc] initWithFrame:CGRectMake(0.0f, frame.size.height, frame.size.width, stripeHeight)];
_bottomStripe.backgroundColor = UIColorRGB(0xbec2c6);
[self addSubview:_bottomStripe];
}
break;
}
_innerAlpha = 1.0f;
}
return self;
}
- (void)setPallete:(TGStickerKeyboardPallete *)pallete
{
_pallete = pallete;
self.backgroundColor = pallete.backgroundColor;
_bottomStripe.backgroundColor = pallete.separatorColor;
}
- (void)setAvatarUrl:(NSString *)avatarUrl peerId:(int64_t)peerId title:(NSString *)title
{
_avatarUrl = avatarUrl;
_peerId = peerId;
_title = title;
TGStickerKeyboardTabCell *cell = (TGStickerKeyboardTabCell *)[_collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:3]];
[cell setUrl:_avatarUrl peerId:peerId title:title];
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if (_expanded)
return CGRectContainsPoint(CGRectMake(0, -15.0f, self.bounds.size.width, self.bounds.size.height + 15.0f), point);
return [super pointInside:point withEvent:event];
}
- (void)arrowTapped
{
if (self.toggleExpanded != nil)
self.toggleExpanded();
}
- (void)setFrame:(CGRect)frame
{
bool sizeUpdated = !CGSizeEqualToSize(frame.size, self.frame.size);
[super setFrame:frame];
if (sizeUpdated && frame.size.width > FLT_EPSILON && frame.size.height > FLT_EPSILON)
[self layoutForSize:frame.size];
}
- (void)setInnerAlpha:(CGFloat)alpha
{
_innerAlpha = alpha;
_collectionView.alpha = _innerAlpha;
for (TGStickerKeyboardTabCell *cell in _collectionView.visibleCells)
{
if ([cell respondsToSelector:@selector(setInnerAlpha:)])
{
NSIndexPath *indexPath = [_collectionView indexPathForCell:cell];
if (!_expanded || indexPath.row != 0 || !_showGifs)
[cell setInnerAlpha:_innerAlpha];
}
}
CGAffineTransform transform = CGAffineTransformMakeTranslation(0.0f, 36.0f * (1.0f - alpha));
transform = CGAffineTransformScale(transform, alpha, alpha);
}
- (void)setHidden:(bool)hidden animated:(bool)animated
{
if (!hidden && animated && _collectionView.visibleCells.count == 0)
[_collectionView layoutSubviews];
for (UICollectionViewCell *cell in _collectionView.visibleCells)
{
if (animated)
{
[UIView animateWithDuration:0.3 delay:0.0f options:UIViewAnimationOptionBeginFromCurrentState animations:^
{
cell.alpha = hidden ? 0.0f : 1.0f;
} completion:nil];
}
else
{
cell.alpha = hidden ? 0.0f : 1.0f;
}
}
}
- (void)setBounds:(CGRect)bounds
{
bool sizeUpdated = !CGSizeEqualToSize(bounds.size, self.bounds.size);
[super setBounds:bounds];
if (sizeUpdated && bounds.size.width > FLT_EPSILON && bounds.size.height > FLT_EPSILON)
[self layoutForSize:bounds.size];
}
- (void)layoutForSize:(CGSize)size
{
_collectionView.frame = CGRectMake(0.0f, 0.0f, size.width, _collectionView.frame.size.height);
[_collectionLayout invalidateLayout];
CGFloat stripeHeight = TGScreenPixel;
_bottomStripe.frame = CGRectMake(0.0f, size.height, size.width, stripeHeight);
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)__unused collectionView
{
return 5 + ((_style == TGStickerKeyboardViewDefaultStyle) ? 1 : 0);
}
- (NSInteger)collectionView:(UICollectionView *)__unused collectionView numberOfItemsInSection:(NSInteger)__unused section
{
if (section == 0) {
return (_showGifs ? 1 : 0) + (_showTrendingFirst ? 1 : 0);
} else if (section == 1) {
return (_showFavorite ? 1 : 0);
} else if (section == 2) {
return (_showRecent ? 1 : 0);
} else if (section == 3) {
return (_showGroup ? 1 : 0);
} else if (section == 4) {
return _stickerPacks.count;
} else if (section == 5) {
return 1 + (_showGroupLast ? 1 : 0) + (_showTrendingLast ? 1 : 0);
} else {
return 0;
}
}
- (CGSize)collectionView:(UICollectionView *)__unused collectionView layout:(UICollectionViewLayout*)__unused collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)__unused indexPath
{
CGFloat width = 52.0f;
if (_style == TGStickerKeyboardViewDefaultStyle)
width = 48.0f;
return CGSizeMake(width, _collectionView.frame.size.height);
}
- (UIEdgeInsets)collectionView:(UICollectionView *)__unused collectionView layout:(UICollectionViewLayout *)__unused collectionViewLayout insetForSectionAtIndex:(NSInteger)__unused section
{
return UIEdgeInsetsZero;
}
- (CGFloat)collectionView:(UICollectionView *)__unused collectionView layout:(UICollectionViewLayout*)__unused collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)__unused section
{
return 0.0f;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)__unused collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)__unused section
{
return (collectionView.frame.size.width < 330.0f) ? 0.0f : 4.0f;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0) {
TGStickerKeyboardTabSettingsCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TGStickerKeyboardTabSettingsCell" forIndexPath:indexPath];
[cell setPallete:_pallete];
[cell setStyle:_style];
[cell setInnerAlpha:_innerAlpha];
if (indexPath.item == 0 && _showGifs) {
[cell setMode:TGStickerKeyboardTabSettingsCellGifs];
[cell setBadge:nil];
if (_expanded)
[cell setInnerAlpha:0.0f];
} else {
[cell setMode:TGStickerKeyboardTabSettingsCellTrending];
[cell setBadge:_trendingStickersBadge];
}
return cell;
} else if (indexPath.section == 1 || indexPath.section == 2 || indexPath.section == 3 || indexPath.section == 4) {
TGStickerKeyboardTabCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TGStickerKeyboardTabCell" forIndexPath:indexPath];
[cell setPallete:_pallete];
[cell setStyle:_style];
if (indexPath.section == 1) {
if (_showFavorite) {
[cell setFavorite];
} else {
[cell setNone];
}
}
else if (indexPath.section == 2) {
if (_showRecent) {
[cell setRecent];
} else {
[cell setNone];
}
}
else if (indexPath.section == 3) {
if (_showGroup) {
[cell setUrl:_avatarUrl peerId:_peerId title:_title];
} else {
[cell setNone];
}
}
else
{
if (((TGStickerPack *)_stickerPacks[indexPath.item]).documents.count != 0)
[cell setDocumentMedia:((TGStickerPack *)_stickerPacks[indexPath.item]).documents[0]];
else
[cell setNone];
}
[cell setInnerAlpha:_innerAlpha];
return cell;
} else if (indexPath.section == 5) {
if (_showGroupLast && indexPath.row == 0)
{
TGStickerKeyboardTabCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TGStickerKeyboardTabCell" forIndexPath:indexPath];
[cell setPallete:_pallete];
[cell setStyle:_style];
[cell setUrl:_avatarUrl peerId:_peerId title:_title];
[cell setInnerAlpha:_innerAlpha];
return cell;
}
else
{
TGStickerKeyboardTabSettingsCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TGStickerKeyboardTabSettingsCell" forIndexPath:indexPath];
[cell setPallete:_pallete];
[cell setStyle:_style];
if (_showTrendingLast && ((_showGroupLast && indexPath.item == 1) || (!_showGroupLast && indexPath.item == 0))) {
[cell setBadge:_trendingStickersBadge];
[cell setMode:TGStickerKeyboardTabSettingsCellTrending];
cell.pressed = nil;
} else {
[cell setBadge:nil];
[cell setMode:TGStickerKeyboardTabSettingsCellSettings];
cell.pressed = self.openSettings;
}
[cell setInnerAlpha:_innerAlpha];
return cell;
}
} else {
return nil;
}
}
- (void)collectionView:(UICollectionView *)__unused collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
if (iosMajorVersion() < 8)
return;
if (indexPath.section == 0) {
if (indexPath.item == 0 && _showGifs) {
if ([cell isKindOfClass:[TGStickerKeyboardTabSettingsCell class]])
{
TGStickerKeyboardTabSettingsCell *settingsCell = (TGStickerKeyboardTabSettingsCell *)cell;
[settingsCell setInnerAlpha:_expanded && settingsCell.mode == TGStickerKeyboardTabSettingsCellGifs ? 0.0f : 1.0f];
}
}
}
else
{
if ([cell isKindOfClass:[TGStickerKeyboardTabSettingsCell class]])
{
TGStickerKeyboardTabSettingsCell *settingsCell = (TGStickerKeyboardTabSettingsCell *)cell;
[settingsCell setInnerAlpha:_innerAlpha];
}
}
}
- (void)collectionView:(UICollectionView *)__unused collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
if (iosMajorVersion() < 8)
return;
if (indexPath.section == 0) {
if (indexPath.item == 0 && _showGifs) {
if ([cell isKindOfClass:[TGStickerKeyboardTabSettingsCell class]])
{
TGStickerKeyboardTabSettingsCell *settingsCell = (TGStickerKeyboardTabSettingsCell *)cell;
[settingsCell setInnerAlpha:_expanded && settingsCell.mode == TGStickerKeyboardTabSettingsCellGifs ? 0.0f : 1.0f];
}
}
}
}
- (void)updateCellsVisibility
{
if (!_expanded)
return;
for (UICollectionViewCell *cell in _collectionView.visibleCells)
{
if ([cell isKindOfClass:[TGStickerKeyboardTabSettingsCell class]])
{
TGStickerKeyboardTabSettingsCell *settingsCell = (TGStickerKeyboardTabSettingsCell *)cell;
[settingsCell setInnerAlpha:settingsCell.mode == TGStickerKeyboardTabSettingsCellGifs ? 0.0f : 1.0f];
}
else
{
if ([cell isKindOfClass:[TGStickerKeyboardTabCell class]])
{
TGStickerKeyboardTabCell *tabCell = (TGStickerKeyboardTabCell *)cell;
[tabCell setInnerAlpha:1.0f];
}
}
}
}
- (void)collectionView:(UICollectionView *)__unused collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0) {
if (indexPath.item == 0 && _showGifs) {
[self scrollToGifsButton];
} else {
[self scrollToTrendingButton];
}
} else if (indexPath.section == 1 || indexPath.section == 2 || indexPath.section == 3 || indexPath.section == 4) {
if (_currentStickerPackIndexChanged)
_currentStickerPackIndexChanged(indexPath.section - 1 + indexPath.row);
} else if (indexPath.section == 5) {
if (_showGroupLast && indexPath.item == 0) {
if (_currentStickerPackIndexChanged)
_currentStickerPackIndexChanged(_stickerPacks.count + 3);
} else if (_showTrendingLast && (indexPath.item == 0 || indexPath.item == 1)) {
[self scrollToTrendingButton];
}
}
}
- (void)setStickerPacks:(NSArray *)stickerPacks showRecent:(bool)showRecent showFavorite:(bool)showFavorite showGroup:(bool)showGroup showGroupLast:(bool)showGroupLast showGifs:(bool)showGifs showTrendingFirst:(bool)showTrendingFirst showTrendingLast:(bool)showTrendingLast {
_stickerPacks = stickerPacks;
_showRecent = showRecent;
_showFavorite = showFavorite;
_showGroup = showGroup;
_showGroupLast = showGroupLast;
_showGifs = showGifs;
_showTrendingFirst = showTrendingFirst;
_showTrendingLast = showTrendingLast;
[_collectionView reloadData];
}
- (void)setCurrentStickerPackIndex:(NSUInteger)currentStickerPackIndex animated:(bool)animated
{
NSInteger section = 0;
NSInteger row = 0;
if (_style != TGStickerKeyboardViewPaintStyle && _style != TGStickerKeyboardViewPaintDarkStyle)
{
section = currentStickerPackIndex + 1;
if (section >= 4 + _stickerPacks.count)
{
section = 5 + currentStickerPackIndex - _stickerPacks.count - 3;
}
else if (section >= 4)
{
section = 4;
row = currentStickerPackIndex - 3;
}
}
else
{
if (currentStickerPackIndex == 0)
{
section = 2;
row = 0;
}
else
{
section = 4;
row = currentStickerPackIndex - 1;
}
}
NSArray *selectedItems = [_collectionView indexPathsForSelectedItems];
if (selectedItems.count == 1 && ((NSIndexPath *)selectedItems[0]).section == (NSInteger)section && ((NSIndexPath *)selectedItems[0]).row == (NSInteger)row)
return;
UICollectionViewLayoutAttributes *attributes = [_collectionLayout layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:row inSection:section]];
UICollectionViewScrollPosition scrollPosition = UICollectionViewScrollPositionNone;
if (!CGRectContainsRect(_collectionView.bounds, attributes.frame))
{
if (attributes.frame.origin.x < _collectionView.bounds.origin.x + _collectionView.bounds.size.width / 2.0f)
{
scrollPosition = UICollectionViewScrollPositionLeft;
}
else
scrollPosition = UICollectionViewScrollPositionRight;
}
[_collectionView selectItemAtIndexPath:[NSIndexPath indexPathForItem:row inSection:section] animated:animated scrollPosition:scrollPosition];
}
- (void)setCurrentGifsModeSelected {
[self scrollToGifsButton];
}
- (void)setCurrentTrendingModeSelected {
[self scrollToTrendingButton];
}
- (void)scrollToGifsButton {
UICollectionViewLayoutAttributes *attributes = [_collectionLayout layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
UICollectionViewScrollPosition scrollPosition = UICollectionViewScrollPositionNone;
if (!CGRectContainsRect(_collectionView.bounds, attributes.frame))
{
if (attributes.frame.origin.x < _collectionView.bounds.origin.x + _collectionView.bounds.size.width / 2.0f)
{
scrollPosition = UICollectionViewScrollPositionLeft;
}
else
scrollPosition = UICollectionViewScrollPositionRight;
}
[_collectionView selectItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0] animated:false scrollPosition:scrollPosition];
if (_navigateToGifs) {
_navigateToGifs();
}
}
- (void)scrollToTrendingButton {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:_showGifs ? 1 : 0 inSection:0];
if (_showTrendingLast) {
NSInteger item = 0;
if ([self collectionView:_collectionView numberOfItemsInSection:5] > 2)
item = 1;
indexPath = [NSIndexPath indexPathForItem:item inSection:5];
}
if (indexPath.section < [self numberOfSectionsInCollectionView:_collectionView] && indexPath.item < [self collectionView:_collectionView numberOfItemsInSection:indexPath.section]) {
UICollectionViewLayoutAttributes *attributes = [_collectionLayout layoutAttributesForItemAtIndexPath:indexPath];
UICollectionViewScrollPosition scrollPosition = UICollectionViewScrollPositionNone;
if (!CGRectContainsRect(_collectionView.bounds, attributes.frame))
{
if (attributes.frame.origin.x < _collectionView.bounds.origin.x + _collectionView.bounds.size.width / 2.0f)
scrollPosition = UICollectionViewScrollPositionLeft;
else
scrollPosition = UICollectionViewScrollPositionRight;
}
[_collectionView selectItemAtIndexPath:indexPath animated:false scrollPosition:scrollPosition];
if (_showTrendingLast) {
if (_navigateToTrendingLast) {
_navigateToTrendingLast();
}
} else {
if (_navigateToTrendingFirst) {
_navigateToTrendingFirst();
}
}
}
}
- (void)setTrendingStickersBadge:(NSString *)badge {
if (!TGStringCompare(_trendingStickersBadge, badge)) {
_trendingStickersBadge = badge;
for (id cell in [_collectionView visibleCells]) {
if ([cell isKindOfClass:[TGStickerKeyboardTabSettingsCell class]]) {
if (((TGStickerKeyboardTabSettingsCell *)cell).mode == TGStickerKeyboardTabSettingsCellTrending) {
[(TGStickerKeyboardTabSettingsCell *)cell setBadge:badge];
}
}
}
TGStickerKeyboardTabSettingsCell *cell = (TGStickerKeyboardTabSettingsCell *)[_collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:5]];
if (cell != nil) {
[cell setBadge:badge];
}
}
}
- (void)setExpanded:(bool)expanded
{
_expanded = expanded;
[self updateExpanded:expanded];
}
- (void)setSafeAreaInset:(UIEdgeInsets)safeAreaInset
{
_safeAreaInset = safeAreaInset;
UIEdgeInsets initialInset = UIEdgeInsetsZero;
if (_style == TGStickerKeyboardViewPaintStyle || _style == TGStickerKeyboardViewPaintDarkStyle)
initialInset = UIEdgeInsetsMake(0.0f, 12.0f, 0.0f, 12.0f);
if (_expanded)
initialInset = UIEdgeInsetsMake(0.0f, -48.0f, 0.0f, 0.0f);
_collectionView.contentInset = UIEdgeInsetsMake(initialInset.top, initialInset.left + _safeAreaInset.left, initialInset.bottom, initialInset.right + _safeAreaInset.right);
if (!_expanded && _collectionView.contentOffset.x <= -_safeAreaInset.left + 60.0f)
[_collectionView setContentOffset:CGPointMake(-_safeAreaInset.left - initialInset.left, 0.0f)];
else if (_expanded && _collectionView.contentOffset.x <= 60.0f)
[_collectionView setContentOffset:CGPointMake(-_safeAreaInset.left + 48.0f, 0.0f)];
}
- (void)updateExpanded:(bool)expanded
{
if (iosMajorVersion() < 8)
return;
if (!_showGifs)
return;
[UIView animateWithDuration:0.2 animations:^
{
_collectionView.contentInset = expanded ? UIEdgeInsetsMake(0.0f, -48.0f + _safeAreaInset.left, 0.0f, _safeAreaInset.right) : UIEdgeInsetsMake(0.0f, _safeAreaInset.left, 0.0f, _safeAreaInset.right);
if (!expanded && _collectionView.contentOffset.x <= -_safeAreaInset.left + 60.0f)
[_collectionView setContentOffset:CGPointMake(-_safeAreaInset.left, 0.0f)];
TGStickerKeyboardTabSettingsCell *cell = (TGStickerKeyboardTabSettingsCell *)[_collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
if ([cell isKindOfClass:[TGStickerKeyboardTabSettingsCell class]] && _showGifs && !expanded)
[cell setInnerAlpha:1.0f];
} completion:^(BOOL finished)
{
if (expanded && finished)
[self updateCellsVisibility];
}];
}
@end
@implementation TGStickerKeyboardPallete
+ (instancetype)palleteWithBackgroundColor:(UIColor *)backgroundColor separatorColor:(UIColor *)separatorColor selectionColor:(UIColor *)selectionColor gifIcon:(UIImage *)gifIcon trendingIcon:(UIImage *)trendingIcon favoritesIcon:(UIImage *)favoritesIcon recentIcon:(UIImage *)recentIcon settingsIcon:(UIImage *)settingsIcon badge:(UIImage *)badge badgeTextColor:(UIColor *)badgeTextColor
{
TGStickerKeyboardPallete *pallete = [[TGStickerKeyboardPallete alloc] init];
pallete->_backgroundColor = backgroundColor;
pallete->_separatorColor = separatorColor;
pallete->_selectionColor = selectionColor;
pallete->_gifIcon = gifIcon;
pallete->_trendingIcon = trendingIcon;
pallete->_favoritesIcon = favoritesIcon;
pallete->_recentIcon = recentIcon;
pallete->_settingsIcon = settingsIcon;
pallete->_badge = badge;
pallete->_badgeTextColor = badgeTextColor;
return pallete;
}
@end

View File

@ -1,23 +0,0 @@
#import <UIKit/UIKit.h>
#import "TGStickerKeyboardTabPanel.h"
typedef enum {
TGStickerKeyboardTabSettingsCellSettings,
TGStickerKeyboardTabSettingsCellGifs,
TGStickerKeyboardTabSettingsCellTrending
} TGStickerKeyboardTabSettingsCellMode;
@interface TGStickerKeyboardTabSettingsCell : UICollectionViewCell
@property (nonatomic, copy) void (^pressed)();
@property (nonatomic) TGStickerKeyboardTabSettingsCellMode mode;
- (void)setBadge:(NSString *)badge;
- (void)setStyle:(TGStickerKeyboardViewStyle)style;
- (void)setPallete:(TGStickerKeyboardPallete *)pallete;
- (void)setInnerAlpha:(CGFloat)innerAlpha;
@end

View File

@ -1,203 +0,0 @@
#import "TGStickerKeyboardTabSettingsCell.h"
#import "TGStickerKeyboardTabPanel.h"
#import "LegacyComponentsInternal.h"
#import "TGImageUtils.h"
#import "TGFont.h"
#import <LegacyComponents/TGModernButton.h>
static void setViewFrame(UIView *view, CGRect frame)
{
CGAffineTransform transform = view.transform;
view.transform = CGAffineTransformIdentity;
if (!CGRectEqualToRect(view.frame, frame))
view.frame = frame;
view.transform = transform;
}
@interface TGStickerKeyboardTabSettingsCell () {
TGStickerKeyboardViewStyle _style;
TGStickerKeyboardPallete *_pallete;
TGModernButton *_button;
UIView *_wrapperView;
UIImageView *_imageView;
UILabel *_badgeLabel;
UIImageView *_badgeView;
}
@end
@implementation TGStickerKeyboardTabSettingsCell
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self != nil) {
_button = [[TGModernButton alloc] init];
_button.modernHighlight = true;
[_button addTarget:self action:@selector(buttonPressed) forControlEvents:UIControlEventTouchUpInside];
[self.contentView addSubview:_button];
_wrapperView = [[UIView alloc] init];
_wrapperView.userInteractionEnabled = false;
[self.contentView addSubview:_wrapperView];
_imageView = [[UIImageView alloc] init];
_imageView.image = TGComponentsImageNamed(@"StickerKeyboardSettingsIcon.png");
_imageView.userInteractionEnabled = false;
_imageView.contentMode = UIViewContentModeCenter;
[_wrapperView addSubview:_imageView];
self.selectedBackgroundView = [[UIView alloc] init];
self.selectedBackgroundView.backgroundColor = UIColorRGB(0xe6e6e6);
}
return self;
}
- (void)setPallete:(TGStickerKeyboardPallete *)pallete
{
if (pallete == nil || _pallete == pallete)
return;
_pallete = pallete;
self.selectedBackgroundView.backgroundColor = pallete.selectionColor;
_badgeView.image = pallete.badge;
_badgeLabel.textColor = pallete.badgeTextColor;
}
- (void)setMode:(TGStickerKeyboardTabSettingsCellMode)mode {
_mode = mode;
if (mode == TGStickerKeyboardTabSettingsCellSettings) {
_imageView.image = _pallete != nil ? _pallete.settingsIcon : TGComponentsImageNamed(@"StickerKeyboardSettingsIcon.png");
} else if (mode == TGStickerKeyboardTabSettingsCellGifs) {
_imageView.image = _pallete != nil ? _pallete.gifIcon : TGComponentsImageNamed(@"StickerKeyboardGifIcon.png");
} else {
_imageView.image = _pallete != nil ? _pallete.trendingIcon : TGComponentsImageNamed(@"StickerKeyboardTrendingIcon.png");
}
_button.hidden = mode != TGStickerKeyboardTabSettingsCellSettings;
}
- (void)setInnerAlpha:(CGFloat)innerAlpha
{
CGAffineTransform transform = CGAffineTransformMakeTranslation(0.0f, 36.0f / 2.0f * (1.0f - innerAlpha));
transform = CGAffineTransformScale(transform, innerAlpha, innerAlpha);
_wrapperView.transform = transform;
self.selectedBackgroundView.transform = transform;
}
- (void)setStyle:(TGStickerKeyboardViewStyle)style
{
_style = style;
switch (style)
{
case TGStickerKeyboardViewDarkBlurredStyle:
{
self.selectedBackgroundView.backgroundColor = UIColorRGB(0x393939);
}
break;
case TGStickerKeyboardViewPaintStyle:
{
self.selectedBackgroundView.backgroundColor = UIColorRGB(0xdadada);
self.selectedBackgroundView.layer.cornerRadius = 8.0f;
self.selectedBackgroundView.clipsToBounds = true;
}
break;
case TGStickerKeyboardViewPaintDarkStyle:
{
self.selectedBackgroundView.backgroundColor = UIColorRGBA(0xfbfffe, 0.47f);
self.selectedBackgroundView.layer.cornerRadius = 8.0f;
self.selectedBackgroundView.clipsToBounds = true;
}
break;
default:
{
self.selectedBackgroundView.backgroundColor = _pallete != nil ? _pallete.selectionColor : UIColorRGB(0xe6e7e9);
self.selectedBackgroundView.layer.cornerRadius = 8.0f;
self.selectedBackgroundView.clipsToBounds = true;
}
break;
}
}
- (void)layoutSubviews {
[super layoutSubviews];
if (_badgeLabel != nil) {
CGSize labelSize = _badgeLabel.frame.size;
CGFloat badgeWidth = MAX(16.0f, labelSize.width + 6.0);
_badgeView.frame = CGRectMake(self.frame.size.width - badgeWidth - 4.0, 6.0f, badgeWidth, 16.0f);
_badgeLabel.frame = CGRectMake(CGRectGetMinX(_badgeView.frame) + TGRetinaFloor((badgeWidth - labelSize.width) / 2.0f), CGRectGetMinY(_badgeView.frame) + 1.0f, labelSize.width, labelSize.height);
}
if (_style == TGStickerKeyboardViewDefaultStyle)
{
setViewFrame(_wrapperView, CGRectOffset(self.bounds, 0.0f, -3.0f));
setViewFrame(_imageView, self.bounds);
_button.frame = self.bounds;
setViewFrame(self.selectedBackgroundView, CGRectMake(floor((self.frame.size.width - 36.0f) / 2.0f), 0, 36.0f, 36.0f));
}
else
{
_wrapperView.frame = self.bounds;
_button.frame = self.bounds;
_imageView.frame = self.bounds;
}
}
- (void)buttonPressed {
if (_pressed) {
_pressed();
}
}
- (void)setBadge:(NSString *)badge {
if (badge != nil) {
if (_badgeLabel == nil) {
_badgeLabel = [[UILabel alloc] init];
_badgeLabel.font = TGSystemFontOfSize(12.0);
_badgeLabel.backgroundColor = [UIColor clearColor];
_badgeLabel.textColor = _pallete != nil ? _pallete.badgeTextColor : [UIColor whiteColor];
[_wrapperView addSubview:_badgeLabel];
static UIImage *badgeImage = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
UIGraphicsBeginImageContextWithOptions(CGSizeMake(16.0f, 16.0f), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, UIColorRGB(0xff3b30).CGColor);
CGContextFillEllipseInRect(context, CGRectMake(0.0f, 0.0f, 16.0f, 16.0f));
badgeImage = [UIGraphicsGetImageFromCurrentImageContext() stretchableImageWithLeftCapWidth:7.0f topCapHeight:0.0f];
UIGraphicsEndImageContext();
});
_badgeView = [[UIImageView alloc] initWithImage:_pallete != nil ? _pallete.badge : badgeImage];
[_wrapperView addSubview:_badgeView];
[_wrapperView addSubview:_badgeLabel];
}
_badgeLabel.text = badge;
[_badgeLabel sizeToFit];
} else {
[_badgeView removeFromSuperview];
_badgeView = nil;
[_badgeLabel removeFromSuperview];
_badgeLabel = nil;
}
[self setNeedsLayout];
}
@end

View File

@ -1,52 +0,0 @@
import Foundation
import UIKit
import Display
import LegacyComponents
import TelegramPresentationData
import LegacyUI
private final class LegacyImagePickerController: LegacyController, TGLegacyCameraControllerDelegate, TGImagePickerControllerDelegate {
private let completion: (UIImage?) -> Void
init(presentation: LegacyControllerPresentation, theme: PresentationTheme?, completion: @escaping (UIImage?) -> Void) {
self.completion = completion
super.init(presentation: presentation, theme: theme)
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.disablesInteractiveModalDismiss = true
}
func legacyCameraControllerCompletedWithNoResult() {
self.completion(nil)
}
func imagePickerController(_ imagePicker: TGImagePickerController!, didFinishPickingWithAssets assets: [Any]!) {
if let image = assets.first as? UIImage {
self.completion(image)
} else {
self.completion(nil)
}
}
}
func legacyImagePicker(theme: PresentationTheme, completion: @escaping (UIImage?) -> Void) -> ViewController {
let legacyController = LegacyImagePickerController(presentation: .modal(animateIn: true), theme: theme, completion: { image in
completion(image)
})
let imagePickerController = TGLegacyCameraController(context: legacyController.context)!
imagePickerController.sourceType = UIImagePickerController.SourceType.photoLibrary
imagePickerController.completionDelegate = legacyController
legacyController.bind(controller: imagePickerController)
return legacyController
}

View File

@ -646,7 +646,6 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
// }
// |> runOn(Queue.mainQueue())
// |> delay(0.15, queue: Queue.mainQueue())
self.editDisposable.set((fetchMediaData(context: self.context, postbox: self.context.account.postbox, mediaReference: mediaReference)
|> deliverOnMainQueue).start(next: { [weak self] state, isImage in

View File

@ -138,6 +138,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
fileprivate let imageNode: TransformImageNode
private var videoNode: UniversalVideoNode?
private var videoContent: NativeVideoContent?
private var videoStartTimestamp: Double?
fileprivate let _ready = Promise<Void>()
fileprivate let _title = Promise<String>()
@ -217,6 +218,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
fileprivate func setEntry(_ entry: AvatarGalleryEntry, synchronous: Bool) {
let previousRepresentations = self.entry?.representations
let previousVideoRepresentations = self.entry?.videoRepresentations
if self.entry != entry {
self.entry = entry
@ -257,58 +259,32 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
id = image.0.id
}
if let video = entry.videoRepresentations.last, let id = id {
let mediaManager = self.context.sharedContext.mediaManager
let videoFileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
let videoContent = NativeVideoContent(id: .profileVideo(id), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear)
let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .embedded)
videoNode.isUserInteractionEnabled = false
videoNode.isHidden = true
if let _ = video.startTimestamp {
self.playbackStatusDisposable.set((videoNode.status
|> map { status -> Bool in
if let status = status, case .playing = status.status {
return true
} else {
return false
}
}
|> filter { playing in
return playing
}
|> take(1)
|> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self {
Queue.mainQueue().after(0.03) {
strongSelf.videoNode?.isHidden = false
}
}
}))
} else {
self.playbackStatusDisposable.set(nil)
videoNode.isHidden = false
if video != previousVideoRepresentations?.last {
let mediaManager = self.context.sharedContext.mediaManager
let videoFileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
let videoContent = NativeVideoContent(id: .profileVideo(id), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear)
let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .embedded)
videoNode.isUserInteractionEnabled = false
videoNode.isHidden = true
self.videoStartTimestamp = video.startTimestamp
self.videoContent = videoContent
self.videoNode = videoNode
self.playVideoIfCentral()
videoNode.updateLayout(size: largestSize.dimensions.cgSize, transition: .immediate)
self.contentNode.addSubnode(videoNode)
self._ready.set(videoNode.ready)
}
videoNode.canAttachContent = true
if videoNode.hasAttachedContext {
if let startTimestamp = video.startTimestamp {
videoNode.seek(startTimestamp)
}
videoNode.play()
}
videoNode.updateLayout(size: largestSize.dimensions.cgSize, transition: .immediate)
self.videoContent = videoContent
self.videoNode = videoNode
self.contentNode.addSubnode(videoNode)
self._ready.set(videoNode.ready)
} else if let videoNode = self.videoNode {
self.videoContent = nil
self.videoNode = nil
videoNode.removeFromSupernode()
Queue.mainQueue().after(0.1) {
videoNode.removeFromSupernode()
}
}
self.imageNode.frame = self.contentNode.bounds
@ -319,6 +295,67 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
}
}
private func playVideoIfCentral() {
guard let videoNode = self.videoNode, self.isCentral else {
return
}
if let _ = self.videoStartTimestamp {
videoNode.isHidden = true
self.playbackStatusDisposable.set((videoNode.status
|> map { status -> Bool in
if let status = status, case .playing = status.status {
return true
} else {
return false
}
}
|> filter { playing in
return playing
}
|> take(1)
|> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self {
Queue.mainQueue().after(0.03) {
strongSelf.videoNode?.isHidden = false
}
}
}))
} else {
self.playbackStatusDisposable.set(nil)
videoNode.isHidden = false
}
let hadAttachedContent = videoNode.hasAttachedContext
videoNode.canAttachContent = true
if videoNode.hasAttachedContext {
if let startTimestamp = self.videoStartTimestamp, !hadAttachedContent {
videoNode.seek(startTimestamp)
}
videoNode.play()
}
}
var isCentral = false
override func centralityUpdated(isCentral: Bool) {
super.centralityUpdated(isCentral: isCentral)
if self.isCentral != isCentral {
self.isCentral = isCentral
if isCentral {
self.playVideoIfCentral()
} else if let videoNode = self.videoNode {
videoNode.pause()
if let startTimestamp = self.videoStartTimestamp {
videoNode.seek(startTimestamp)
} else {
videoNode.seek(0.0)
}
videoNode.isHidden = true
}
}
}
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {
var transformedFrame = node.0.view.convert(node.0.view.bounds, to: self.contentNode.view)
let transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: self.contentNode.view.superview)

View File

@ -175,6 +175,7 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
let imageNode: TransformImageNode
private var videoNode: UniversalVideoNode?
private var videoContent: NativeVideoContent?
private let playbackStatusDisposable = MetaDisposable()
let isReady = Promise<Bool>()
private var didSetReady: Bool = false
@ -190,6 +191,8 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
}
}
var isCentral: Bool = false
init(context: AccountContext) {
self.context = context
self.imageNode = TransformImageNode()
@ -212,6 +215,19 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
}
}
deinit {
self.playbackStatusDisposable.dispose()
}
func updateTransitionFraction(_ fraction: CGFloat, transition: ContainedViewLayoutTransition) {
if let videoNode = self.videoNode {
if case .immediate = transition, fraction == 1.0 {
return
}
transition.updateAlpha(node: videoNode, alpha: 1.0 - fraction)
}
}
func setup(item: PeerInfoAvatarListItem, synchronous: Bool) {
let representations: [ImageRepresentationWithReference]
let videoRepresentations: [TelegramMediaImage.VideoRepresentation]
@ -240,11 +256,32 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .embedded)
videoNode.isUserInteractionEnabled = false
videoNode.isHidden = true
videoNode.ownsContentNodeUpdated = { [weak self] owns in
if let strongSelf = self {
strongSelf.videoNode?.isHidden = !owns
if let _ = video.startTimestamp {
self.playbackStatusDisposable.set((videoNode.status
|> map { status -> Bool in
if let status = status, case .playing = status.status {
return true
} else {
return false
}
}
|> filter { playing in
return playing
}
|> take(1)
|> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self {
Queue.mainQueue().after(0.03) {
strongSelf.videoNode?.isHidden = false
}
}
}))
} else {
self.playbackStatusDisposable.set(nil)
videoNode.isHidden = false
}
if let startTimestamp = video.startTimestamp {
videoNode.seek(startTimestamp)
}
@ -768,6 +805,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
fileprivate var videoNode: UniversalVideoNode?
private var videoContent: NativeVideoContent?
private var videoStartTimestamp: Double?
var tapped: (() -> Void)?
@ -807,6 +845,15 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
}
}
func updateTransitionFraction(_ fraction: CGFloat, transition: ContainedViewLayoutTransition) {
if let videoNode = self.videoNode {
if case .immediate = transition, fraction == 1.0 {
return
}
transition.updateAlpha(node: videoNode, alpha: 1.0 - fraction)
}
}
func update(peer: Peer?, item: PeerInfoAvatarListItem?, theme: PresentationTheme, avatarSize: CGFloat, isExpanded: Bool) {
if let peer = peer {
var overrideImage: AvatarNodeImageOverride?
@ -857,7 +904,11 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
let update = {
videoNode.canAttachContent = !isExpanded
if videoNode.canAttachContent {
videoNode.seek(0.0)
if let videoStartTimestamp = self.videoStartTimestamp {
videoNode.seek(videoStartTimestamp)
} else {
videoNode.seek(0.0)
}
videoNode.play()
}
}
@ -1042,11 +1093,7 @@ final class PeerInfoAvatarListNode: ASDisplayNode {
func animateAvatarCollapse(transition: ContainedViewLayoutTransition) {
if let currentItemNode = self.listContainerNode.currentItemNode, case .animated = transition {
if let videoNode = self.avatarContainerNode.videoNode {
// videoNode.position = currentItemNode.imageNode.position
// currentItemNode.addSubnode(videoNode)
// let scale = currentItemNode.imageNode.bounds.height / videoNode.bounds.height
// avatarCopyView.layer.transform = CATransform3DMakeScale(scale, scale, scale)
// self.avatarContainerNode.reattachVideoNode()
} else if let unroundedImage = self.avatarContainerNode.avatarNode.unroundedImage {
let avatarCopyView = UIImageView()
avatarCopyView.image = unroundedImage
@ -1925,6 +1972,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
transition.updateAlpha(node: self.expandedBackgroundNode, alpha: backgroundTransitionFraction)
}
self.avatarListNode.avatarContainerNode.updateTransitionFraction(transitionFraction, transition: transition)
self.avatarListNode.listContainerNode.currentItemNode?.updateTransitionFraction(transitionFraction, transition: transition)
self.separatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
let defaultButtonSize: CGFloat = 40.0
@ -1992,6 +2042,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let avatarSize: CGFloat = isModalOverlay ? 200.0 : 100.0
let avatarFrame = CGRect(origin: CGPoint(x: floor((width - avatarSize) / 2.0), y: statusBarHeight + 10.0), size: CGSize(width: avatarSize, height: avatarSize))
let avatarCenter = CGPoint(x: (1.0 - transitionFraction) * avatarFrame.midX + transitionFraction * transitionSourceAvatarFrame.midX, y: (1.0 - transitionFraction) * avatarFrame.midY + transitionFraction * transitionSourceAvatarFrame.midY)
let avatarAlpha = transitionFraction
let titleSize = titleNodeLayout[TitleNodeStateRegular]!.size
let titleExpandedSize = titleNodeLayout[TitleNodeStateExpanded]!.size