1
0
mirror of https://github.com/danog/Telegram.git synced 2024-12-04 10:27:46 +01:00
Telegram/Telegraph/TGAudioMessageViewModel.m

530 lines
18 KiB
Mathematica
Raw Normal View History

2014-07-10 16:11:09 +02:00
/*
* This is the source code of Telegram for iOS v. 1.1
* It is licensed under GNU GPL v. 2 or later.
* You should have received a copy of the license in this archive (see LICENSE).
*
* Copyright Peter Iakovlev, 2013.
*/
#import "TGAudioMessageViewModel.h"
#import "TGImageUtils.h"
#import "TGMessage.h"
#import "TGFont.h"
#import "TGModernViewContext.h"
#import "TGTelegraphConversationMessageAssetsSource.h"
#import "TGModernConversationItem.h"
#import "TGModernButtonViewModel.h"
#import "TGTextMessageBackgroundViewModel.h"
#import "TGModernLabelViewModel.h"
#import "TGModernFlatteningViewModel.h"
#import "TGModernDateViewModel.h"
#import "TGModernColorViewModel.h"
#import "TGAudioSliderViewModel.h"
#import "TGModernTextViewModel.h"
#import "TGAudioSliderView.h"
#import "TGModernViewInlineMediaContext.h"
#import "TGDoubleTapGestureRecognizer.h"
typedef enum {
TGAudioMessageButtonPlay = 0,
TGAudioMessageButtonPause = 1,
TGAudioMessageButtonDownload = 2,
TGAudioMessageButtonCancel = 3
} TGAudioMessageButtonType;
@interface TGAudioMessageViewModel () <TGModernViewInlineMediaContextDelegate, TGAudioSliderViewDelegate>
{
bool _progressVisible;
bool _mediaIsAvailable;
float _progress;
int32_t _duration;
CGPoint _boundOffset;
TGAudioMediaAttachment *_audio;
TGModernButtonViewModel *_playButtonModel;
TGAudioMessageButtonType _playButtonType;
TGAudioSliderViewModel *_sliderModel;
CGFloat _headerHeight;
bool _isPlayerActive;
bool _isPaused;
float _audioPosition;
NSTimeInterval _preciseDuration;
NSTimeInterval _audioPositionTimestamp;
TGModernViewInlineMediaContext *_inlineMediaContext;
}
@end
@implementation TGAudioMessageViewModel
static UIImage *playImageWithColor(UIColor *color)
{
CGFloat radius = 28.0f;
UIGraphicsBeginImageContextWithOptions(CGSizeMake(radius, radius), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillEllipseInRect(context, CGRectMake(0.0f, 0.0f, radius, radius));
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGContextBeginPath(context);
CGContextMoveToPoint(context, 11.0f, 8.0f);
CGContextAddLineToPoint(context, 21.0f, 14.0f);
CGContextAddLineToPoint(context, 11.0f, 20.0f);
CGContextClosePath(context);
CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor);
CGContextFillPath(context);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
static UIImage *pauseImageWithColor(UIColor *color)
{
CGFloat radius = 28.0f;
UIGraphicsBeginImageContextWithOptions(CGSizeMake(radius, radius), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillEllipseInRect(context, CGRectMake(0.0f, 0.0f, radius, radius));
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor);
CGContextFillRect(context, CGRectMake(10.0f, 9.0f, 2.5f, 10.0f));
CGContextFillRect(context, CGRectMake(15.5f, 9.0f, 2.5f, 10.0f));
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
static UIImage *downloadImageWithColor(UIColor *color)
{
CGFloat radius = 28.0f;
UIGraphicsBeginImageContextWithOptions(CGSizeMake(radius, radius), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, color.CGColor);
CGContextSetLineWidth(context, 1.0f);
CGContextStrokeEllipseInRect(context, CGRectMake(0.5f, 0.5f, radius - 1.0f, radius - 1.0f));
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillRect(context, CGRectMake(13.0f, 8.5f, 2.0f, 8.0f));
CGContextBeginPath(context);
CGContextMoveToPoint(context, 10.0f, 16.5f);
CGContextAddLineToPoint(context, 18.0f, 16.5f);
CGContextAddLineToPoint(context, 14.5f, 20.5f);
CGContextAddLineToPoint(context, 13.5f, 20.5f);
CGContextClosePath(context);
CGContextFillPath(context);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
static UIImage *cancelImageWithColor(UIColor *color, bool incoming)
{
CGFloat radius = 28.0f;
UIGraphicsBeginImageContextWithOptions(CGSizeMake(radius, radius), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, color.CGColor);
CGContextSetLineWidth(context, 1.0f);
CGContextStrokeEllipseInRect(context, CGRectMake(0.5f, 0.5f, radius - 1.0f, radius - 1.0f));
UIImage *crossImage = incoming ? [UIImage imageNamed:@"ModernMessageAudioCancel_Incoming.png"] : [UIImage imageNamed:@"ModernMessageAudioCancel_Outgoing.png"];
CGContextDrawImage(context, CGRectMake(CGFloor((radius - crossImage.size.width) / 2), CGFloor((radius - crossImage.size.height) / 2), crossImage.size.width, crossImage.size.height), crossImage.CGImage);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (UIImage *)playImage:(bool)incoming
{
static UIImage *incomingImage = nil;
static UIImage *outgoingImage = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
incomingImage = playImageWithColor(TGAccentColor());
outgoingImage = playImageWithColor(UIColorRGB(0x3fc33b));
});
return incoming ? incomingImage : outgoingImage;
}
- (UIImage *)pauseImage:(bool)incoming
{
static UIImage *incomingImage = nil;
static UIImage *outgoingImage = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
incomingImage = pauseImageWithColor(TGAccentColor());
outgoingImage = pauseImageWithColor(UIColorRGB(0x3fc33b));
});
return incoming ? incomingImage : outgoingImage;
}
- (UIImage *)downloadImage:(bool)incoming
{
static UIImage *incomingImage = nil;
static UIImage *outgoingImage = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
incomingImage = downloadImageWithColor(TGAccentColor());
outgoingImage = downloadImageWithColor(UIColorRGB(0x3fc33b));
});
return incoming ? incomingImage : outgoingImage;
}
- (UIImage *)cancelImage:(bool)incoming
{
static UIImage *incomingImage = nil;
static UIImage *outgoingImage = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
incomingImage = cancelImageWithColor(TGAccentColor(), true);
outgoingImage = cancelImageWithColor(UIColorRGB(0x3fc33b), false);
});
return incoming ? incomingImage : outgoingImage;
}
- (instancetype)initWithMessage:(TGMessage *)message audio:(TGAudioMediaAttachment *)audio author:(TGUser *)author context:(TGModernViewContext *)context
{
self = [super initWithMessage:message author:author context:context];
if (self != nil)
{
_audio = audio;
_playButtonModel = [[TGModernButtonViewModel alloc] init];
[_playButtonModel setBackgroundImage:[self downloadImage:_incoming]];
_playButtonModel.modernHighlight = true;
_playButtonModel.skipDrawInContext = true;
[self updateButtonText:false];
[self addSubmodel:_playButtonModel];
_duration = audio.duration;
_isPaused = true;
_sliderModel = [[TGAudioSliderViewModel alloc] init];
_sliderModel.incoming = _incoming;
_sliderModel.duration = _duration;
_sliderModel.audioDurationText = [[NSString alloc] initWithFormat:@"%d:%02d", (int)audio.duration / 60, (int)audio.duration % 60];
[self addSubmodel:_sliderModel];
}
return self;
}
- (void)updateButtonText:(bool)animated
{
TGAudioMessageButtonType playButtonType = TGAudioMessageButtonPlay;
_sliderModel.progressMode = _progressVisible || !_mediaIsAvailable;
if (_progressVisible)
{
playButtonType = TGAudioMessageButtonCancel;
[_sliderModel setAudioPosition:_progress animated:animated timestamp:DBL_MAX isPlaying:false];
_sliderModel.audioDurationText = [[NSString alloc] initWithFormat:@"%d:%02d", (int)_audio.duration / 60, (int)_audio.duration % 60];
_sliderModel.manualPositionAdjustmentEnabled = false;
}
else
{
if (_mediaIsAvailable)
playButtonType = _isPaused ? TGAudioMessageButtonPlay : TGAudioMessageButtonPause;
else
playButtonType = TGAudioMessageButtonDownload;
_sliderModel.viewUserInteractionDisabled = !_isPlayerActive;
[_sliderModel setPreciseDuration:_preciseDuration];
bool isPlaying = _isPlayerActive && !_isPaused;
[_sliderModel setAudioPosition:_audioPosition animated:animated timestamp:isPlaying ? _audioPositionTimestamp : DBL_MAX isPlaying:isPlaying];
int currentDuration = _isPlayerActive ? (int)(_duration * _audioPosition) : (int)_duration;
_sliderModel.audioDurationText = [[NSString alloc] initWithFormat:@"%d:%02d", (int)currentDuration / 60, (int)currentDuration % 60];
_sliderModel.manualPositionAdjustmentEnabled = _isPlayerActive;
}
if (_playButtonType != playButtonType)
{
_playButtonType = playButtonType;
switch (_playButtonType)
{
case TGAudioMessageButtonPlay:
[_playButtonModel setBackgroundImage:[self playImage:_incoming]];
break;
case TGAudioMessageButtonPause:
[_playButtonModel setBackgroundImage:[self pauseImage:_incoming]];
break;
case TGAudioMessageButtonDownload:
[_playButtonModel setBackgroundImage:[self downloadImage:_incoming]];
break;
case TGAudioMessageButtonCancel:
[_playButtonModel setBackgroundImage:[self cancelImage:_incoming]];
break;
default:
break;
}
}
return;
NSMutableString *text = [[NSMutableString alloc] init];
if (_progressVisible)
{
if (_deliveryState == TGMessageDeliveryStatePending)
[text appendFormat:@"Uploading %d%%", (int)(_progress * 100)];
else
[text appendFormat:@"Downloading %d%%", (int)(_progress * 100)];
}
else
{
if (_mediaIsAvailable)
[text appendFormat:@"Play %" PRId32 "s", _audio.duration];
else
[text appendFormat:@"Download %" PRId32 "kB", _audio.fileSize / 1024];
}
[_playButtonModel setPossibleTitles:@[text]];
if (self.frame.size.width > FLT_EPSILON)
{
[_playButtonModel layoutForContainerSize:CGSizeMake(200.0f, 0.0f)];
CGRect playButtonFrame = _playButtonModel.frame;
playButtonFrame.origin = CGPointMake(_backgroundModel.frame.origin.x + 4.0f, 4.0f);
_playButtonModel.frame = playButtonFrame;
if ([_playButtonModel boundView] != nil)
[_playButtonModel boundView].frame = CGRectOffset([_playButtonModel boundView].frame, _boundOffset.x, _boundOffset.y);
}
}
- (void)updateMessage:(TGMessage *)message viewStorage:(TGModernViewStorage *)viewStorage
{
[super updateMessage:message viewStorage:viewStorage];
[self updateButtonText:false];
}
- (void)updateMediaAvailability:(bool)mediaIsAvailable viewStorage:(TGModernViewStorage *)__unused viewStorage
{
[super updateMediaAvailability:mediaIsAvailable viewStorage:viewStorage];
_mediaIsAvailable = mediaIsAvailable;
[self updateButtonText:false];
}
- (void)updateProgress:(bool)progressVisible progress:(float)progress viewStorage:(TGModernViewStorage *)viewStorage
{
[super updateProgress:progressVisible progress:progress viewStorage:viewStorage];
_progressVisible = progressVisible;
_progress = progress;
[self updateButtonText:_progress < 0.01f ? false : true];
}
- (void)updateInlineMediaContext
{
bool isPlayerActive = false;
bool isPaused = true;
float audioPosition = 0.0f;
NSTimeInterval playbackPositionTimestamp = DBL_MAX;
_inlineMediaContext = [_context inlineMediaContext:_mid];
if (_inlineMediaContext != nil)
{
_inlineMediaContext.delegate = self;
isPlayerActive = true;
isPaused = [_inlineMediaContext isPaused];
audioPosition = [_inlineMediaContext playbackPosition:&playbackPositionTimestamp];
}
else
{
isPlayerActive = false;
isPaused = true;
audioPosition = 0.0f;
}
if (_isPlayerActive != isPlayerActive || _isPaused != isPaused || ABS(audioPosition - _audioPosition) > FLT_EPSILON || ABS(playbackPositionTimestamp - _audioPositionTimestamp) > DBL_EPSILON)
{
_isPlayerActive = isPlayerActive;
_isPaused = isPaused;
_audioPosition = audioPosition;
_audioPositionTimestamp = playbackPositionTimestamp;
[self updateButtonText:false];
}
}
- (void)bindSpecialViewsToContainer:(UIView *)container viewStorage:(TGModernViewStorage *)viewStorage atItemPosition:(CGPoint)itemPosition
{
_boundOffset = itemPosition;
[super bindSpecialViewsToContainer:container viewStorage:viewStorage atItemPosition:itemPosition];
[_playButtonModel bindViewToContainer:container viewStorage:viewStorage];
[_playButtonModel boundView].frame = CGRectOffset([_playButtonModel boundView].frame, itemPosition.x, itemPosition.y);
[_sliderModel bindViewToContainer:container viewStorage:viewStorage];
[_sliderModel boundView].frame = CGRectOffset([_sliderModel boundView].frame, itemPosition.x, itemPosition.y);
}
- (void)bindViewToContainer:(UIView *)container viewStorage:(TGModernViewStorage *)viewStorage
{
_boundOffset = CGPointZero;
[self updateInlineMediaContext];
[self updateEditingState:nil viewStorage:nil animationDelay:-1.0];
[super bindViewToContainer:container viewStorage:viewStorage];
[(UIButton *)[_playButtonModel boundView] addTarget:self action:@selector(playButtonPressed) forControlEvents:UIControlEventTouchUpInside];
((TGAudioSliderView *)[_sliderModel boundView]).delegate = self;
}
- (void)unbindView:(TGModernViewStorage *)viewStorage
{
[(UIButton *)[_playButtonModel boundView] removeTarget:self action:@selector(playButtonPressed) forControlEvents:UIControlEventTouchUpInside];
((TGAudioSliderView *)[_sliderModel boundView]).delegate = nil;
[_inlineMediaContext removeDelegate:self];
[super unbindView:viewStorage];
}
- (void)playButtonPressed
{
if (_playButtonType == TGAudioMessageButtonCancel)
[_context.companionHandle requestAction:@"mediaProgressCancelRequested" options:@{@"mid": @(_mid)}];
else
{
if (_mediaIsAvailable)
{
if (_inlineMediaContext != nil)
{
if (_isPaused)
[_inlineMediaContext play];
else
[_inlineMediaContext pause];
}
else if (!_isPlayerActive)
[_context.companionHandle requestAction:@"openMediaRequested" options:@{@"mid": @(_mid)}];
else
[_inlineMediaContext pause];
}
else
[_context.companionHandle requestAction:@"mediaDownloadRequested" options:@{@"mid": @(_mid)}];
}
}
- (void)layoutContentForHeaderHeight:(CGFloat)headerHeight
{
_headerHeight = headerHeight;
}
- (CGSize)contentSizeForContainerSize:(CGSize)__unused containerSize needsContentsUpdate:(bool *)__unused needsContentsUpdate
{
return CGSizeMake(MAX(160, MIN(205, _duration * 30)), 41);
}
- (void)layoutForContainerSize:(CGSize)containerSize
{
[super layoutForContainerSize:containerSize];
_playButtonModel.frame = CGRectMake(_backgroundModel.frame.origin.x + (_incoming ? 14.0f : 9.0f), _headerHeight + _backgroundModel.frame.origin.y + 11.0f, 28.0f, 28.0f);
CGFloat trackOriginX = CGRectGetMaxX(_playButtonModel.frame) + 5.0f;
CGRect sliderFrame = CGRectMake(trackOriginX, _playButtonModel.frame.origin.y + 7.0f, CGRectGetMaxX(_backgroundModel.frame) - trackOriginX - 13.0f + (_incoming ? 5.0f : 0.0f), 14.0f);
_sliderModel.frame = sliderFrame;
}
- (void)inlineMediaPlaybackStateUpdated:(bool)isPaused playbackPosition:(float)playbackPosition timestamp:(MTAbsoluteTime)timestamp preciseDuration:(NSTimeInterval)preciseDuration
{
_isPaused = isPaused;
_audioPosition = playbackPosition;
_audioPositionTimestamp = timestamp;
_preciseDuration = preciseDuration;
[self updateButtonText:false];
}
- (void)audioSliderViewDidBeginPositionAdjustment:(TGAudioSliderView *)__unused audioSliderView
{
[_inlineMediaContext pause];
}
- (void)audioSliderViewDidEndPositionAdjustment:(TGAudioSliderView *)__unused audioSliderView atPosition:(float)position
{
[_inlineMediaContext play:position];
}
- (void)audioSliderViewDidCancelPositionAdjustment:(TGAudioSliderView *)__unused audioSliderView
{
[_inlineMediaContext play];
}
- (int)gestureRecognizer:(TGDoubleTapGestureRecognizer *)__unused recognizer shouldFailTap:(CGPoint)__unused point
{
return 3;
}
- (void)messageDoubleTapGesture:(TGDoubleTapGestureRecognizer *)recognizer
{
if (recognizer.state != UIGestureRecognizerStateBegan)
{
if (recognizer.state == UIGestureRecognizerStateRecognized)
{
CGPoint point = [recognizer locationInView:[_contentModel boundView]];
if (recognizer.longTapped)
[_context.companionHandle requestAction:@"messageSelectionRequested" options:@{@"mid": @(_mid)}];
else if (recognizer.doubleTapped)
[_context.companionHandle requestAction:@"messageSelectionRequested" options:@{@"mid": @(_mid)}];
else if (_forwardedHeaderModel && CGRectContainsPoint(_forwardedHeaderModel.frame, point))
[_context.companionHandle requestAction:@"userAvatarTapped" options:@{@"uid": @(_forwardedUid)}];
else if (!_isPlayerActive || _isPaused)
[self playButtonPressed];
}
}
}
@end