mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-10 08:20:16 +00:00
Merge commit '0a2b9ad3d1c7332e8c02144450160ef3a2a5e9ba' into beta
This commit is contained in:
commit
c2fa985bc2
@ -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>
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -1,7 +0,0 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface TGModernGalleryEmbeddedStickersHeaderView : UIView
|
||||
|
||||
@property (nonatomic, copy) void (^showEmbeddedStickers)();
|
||||
|
||||
@end
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -1,5 +0,0 @@
|
||||
#import "TGEmbedPlayerView.h"
|
||||
|
||||
@interface TGEmbedInstagramPlayerView : TGEmbedPlayerView
|
||||
|
||||
@end
|
||||
@ -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
|
||||
@ -1,8 +0,0 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface TGEmbedPIPScrubber : UIView
|
||||
|
||||
@property (nonatomic, assign) CGFloat playProgress;
|
||||
@property (nonatomic, assign) CGFloat downloadProgress;
|
||||
|
||||
@end
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -1,7 +0,0 @@
|
||||
#import "TGEmbedPlayerView.h"
|
||||
|
||||
@interface TGEmbedSoundCloudPlayerView : TGEmbedPlayerView
|
||||
|
||||
+ (NSString *)_soundCloudIdFromText:(NSString *)text;
|
||||
|
||||
@end
|
||||
@ -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
|
||||
@ -1,5 +0,0 @@
|
||||
#import <LegacyComponents/LegacyComponents.h>
|
||||
|
||||
@interface TGEmbedTwitchPlayerView : TGEmbedPlayerView
|
||||
|
||||
@end
|
||||
@ -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
|
||||
@ -1,5 +0,0 @@
|
||||
#import "TGEmbedPlayerView.h"
|
||||
|
||||
@interface TGEmbedVKPlayerView : TGEmbedPlayerView
|
||||
|
||||
@end
|
||||
@ -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
|
||||
@ -1,5 +0,0 @@
|
||||
#import "TGEmbedPlayerView.h"
|
||||
|
||||
@interface TGEmbedVideoPlayerView : TGEmbedPlayerView
|
||||
|
||||
@end
|
||||
@ -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
|
||||
@ -1,7 +0,0 @@
|
||||
#import "TGEmbedPlayerView.h"
|
||||
|
||||
@interface TGEmbedVimeoPlayerView : TGEmbedPlayerView
|
||||
|
||||
+ (NSString *)_vimeoVideoIdFromText:(NSString *)text;
|
||||
|
||||
@end
|
||||
@ -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
|
||||
@ -1,8 +0,0 @@
|
||||
#import "TGEmbedPlayerView.h"
|
||||
|
||||
@interface TGEmbedVinePlayerView : TGEmbedPlayerView
|
||||
|
||||
+ (NSString *)_vineVideoIdFromText:(NSString *)text;
|
||||
+ (NSString *)_vineIdFromPermalink:(NSString *)text;
|
||||
|
||||
@end
|
||||
@ -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
|
||||
@ -1,7 +0,0 @@
|
||||
#import "TGEmbedPlayerView.h"
|
||||
|
||||
@interface TGEmbedYoutubePlayerView : TGEmbedPlayerView
|
||||
|
||||
+ (NSString *)_youtubeVideoIdFromText:(NSString *)text originalUrl:(NSString *)originalUrl startTime:(NSTimeInterval *)startTime;
|
||||
|
||||
@end
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface TGLegacyMediaPickerTipView : UIView
|
||||
|
||||
@end
|
||||
@ -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
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface TGPhotoStickersCollectionLayout : UICollectionViewFlowLayout
|
||||
|
||||
- (NSArray *)sectionHeaders;
|
||||
|
||||
@end
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -1,5 +0,0 @@
|
||||
#import "TGPhotoStickersSectionHeader.h"
|
||||
|
||||
@implementation TGPhotoStickersSectionHeader
|
||||
|
||||
@end
|
||||
@ -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;
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user