VLCPlaylistCollectionViewCell.m 15.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
/*****************************************************************************
 * VLCPlaylistCollectionViewCell.m
 * VLC for iOS
 *****************************************************************************
 * Copyright (c) 2013 VideoLAN. All rights reserved.
 * $Id$
 *
 * Authors: Felix Paul Kühne <fkuehne # videolan.org>
 *          Tamas Timar <ttimar.vlc # gmail.com>
 *          Gleb Pinigin <gpinigin # gmail.com>
11
 *          Carola Nitz <nitz.carola # googlemail.com>
12 13 14
 *
 * Refer to the COPYING file of the official project for license.
 *****************************************************************************/
15 16 17

#import "VLCPlaylistCollectionViewCell.h"
#import "VLCPlaylistViewController.h"
18
#import "VLCThumbnailsCache.h"
19

20 21 22 23 24 25 26 27
@interface VLCPlaylistCollectionViewCell ()
{
    UIImage *_checkboxEmptyImage;
    UIImage *_checkboxImage;
}

@end

28 29
@implementation VLCPlaylistCollectionViewCell

30 31 32 33 34 35 36 37 38 39 40
- (void)awakeFromNib
{
    if (SYSTEM_RUNS_IOS7_OR_LATER) {
        _checkboxEmptyImage = [UIImage imageNamed:@"checkboxEmpty"];
        _checkboxImage = [UIImage imageNamed:@"checkbox"];
    } else {
        _checkboxEmptyImage = [UIImage imageNamed:@"checkbox-legacy-empty"];
        _checkboxImage = [UIImage imageNamed:@"checkbox-legacy"];
    }
}

41 42
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
43
    self.isSelectedView.hidden = !editing;
44 45
    if ([_mediaObject isKindOfClass:[MLLabel class]] || [_mediaObject.labels count] == 0)
        [self shake:editing];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
46
    [self selectionUpdate];
47 48 49
    [self _updatedDisplayedInformationForKeyPath:@"editing"];
}

50 51 52
- (void)selectionUpdate
{
    if (self.selected)
53
        self.isSelectedView.image = _checkboxImage;
54
    else
55
        self.isSelectedView.image = _checkboxEmptyImage;
56 57
}

58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
- (void)shake:(BOOL)shake
{
    if (shake) {
        [UIView animateWithDuration:0.3 animations:^{
            self.contentView.transform = CGAffineTransformMakeScale(0.9f, 0.9f);
        }];
        CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.z"];
        CGFloat shakeAngle = 0.02f;
        animation.values = @[@(-shakeAngle), @(shakeAngle)];
        animation.autoreverses = YES;
        animation.duration = 0.125;
        animation.repeatCount = HUGE_VALF;

        [[self layer] addAnimation:animation forKey:@"shakeAnimation"];
        self.contentView.layer.cornerRadius = 10.0;
        self.contentView.clipsToBounds = YES;
    } else {
        [UIView animateWithDuration:0.3 animations:^{
            self.contentView.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
            self.contentView.layer.cornerRadius = 0.0;
            self.contentView.clipsToBounds = NO;
        }];
        [[self layer] removeAnimationForKey:@"shakeAnimation"];
    }
}

84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    [self _updatedDisplayedInformationForKeyPath:keyPath];
}

- (void)setMediaObject:(MLFile *)mediaObject
{
    if (_mediaObject != mediaObject) {
        [_mediaObject removeObserver:self forKeyPath:@"computedThumbnail"];
        [_mediaObject removeObserver:self forKeyPath:@"lastPosition"];
        [_mediaObject removeObserver:self forKeyPath:@"duration"];
        [_mediaObject removeObserver:self forKeyPath:@"fileSizeInBytes"];
        [_mediaObject removeObserver:self forKeyPath:@"title"];
        [_mediaObject removeObserver:self forKeyPath:@"thumbnailTimeouted"];
        [_mediaObject removeObserver:self forKeyPath:@"unread"];
        [_mediaObject removeObserver:self forKeyPath:@"albumTrackNumber"];
        [_mediaObject removeObserver:self forKeyPath:@"album"];
        [_mediaObject removeObserver:self forKeyPath:@"artist"];
        [_mediaObject removeObserver:self forKeyPath:@"genre"];
        if ([_mediaObject respondsToSelector:@selector(didHide)])
            [(MLFile*)_mediaObject didHide];

        _mediaObject = mediaObject;

        [_mediaObject addObserver:self forKeyPath:@"computedThumbnail" options:0 context:nil];
        [_mediaObject addObserver:self forKeyPath:@"lastPosition" options:0 context:nil];
        [_mediaObject addObserver:self forKeyPath:@"duration" options:0 context:nil];
        [_mediaObject addObserver:self forKeyPath:@"fileSizeInBytes" options:0 context:nil];
        [_mediaObject addObserver:self forKeyPath:@"title" options:0 context:nil];
        [_mediaObject addObserver:self forKeyPath:@"thumbnailTimeouted" options:0 context:nil];
        [_mediaObject addObserver:self forKeyPath:@"unread" options:0 context:nil];
        [_mediaObject addObserver:self forKeyPath:@"albumTrackNumber" options:0 context:nil];
        [_mediaObject addObserver:self forKeyPath:@"album" options:0 context:nil];
        [_mediaObject addObserver:self forKeyPath:@"artist" options:0 context:nil];
        [_mediaObject addObserver:self forKeyPath:@"genre" options:0 context:nil];

        if ([_mediaObject respondsToSelector:@selector(willDisplay)])
            [(MLFile*)_mediaObject willDisplay];
    }

    [self _updatedDisplayedInformationForKeyPath:nil];
}

- (void)_updatedDisplayedInformationForKeyPath:(NSString *)keyPath
{
129 130
    BOOL isFolder = [self.mediaObject isKindOfClass:[MLLabel class]];
    self.thumbnailView.contentMode = isFolder ? UIViewContentModeScaleAspectFit : UIViewContentModeScaleAspectFill;
131 132
    if ([self.mediaObject isKindOfClass:[MLFile class]]) {
        MLFile *mediaObject = self.mediaObject;
133
        [self _configureForMLFile:mediaObject];
134

135
        if (([keyPath isEqualToString:@"computedThumbnail"] || !keyPath) || (!self.thumbnailView.image && [keyPath isEqualToString:@"editing"]))
136
            self.thumbnailView.image = [VLCThumbnailsCache thumbnailForMediaFile:mediaObject];
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
    } else if ([self.mediaObject isKindOfClass:[MLLabel class]]) {
        MLLabel *mediaObject = (MLLabel *)self.mediaObject;
        [self _configureForFolder:mediaObject];

        if (([keyPath isEqualToString:@"computedThumbnail"] || !keyPath) || (!self.thumbnailView.image && [keyPath isEqualToString:@"editing"])) {

            NSUInteger fileNumber = [mediaObject.files count] > 3 ? 3 : [mediaObject.files count];
            if (fileNumber == 0) {
                self.thumbnailView.image = [UIImage imageNamed:@"folderIcon"];
            } else {
                NSArray *files = [mediaObject.files allObjects];
                CGSize frameSize = self.thumbnailView.frame.size;
                UIImage *displayedImage;
                UIGraphicsBeginImageContext(frameSize);
                for (NSUInteger i = 0; i < fileNumber; i++) {
                    MLFile *file =  [files objectAtIndex:i];
                    displayedImage = [VLCThumbnailsCache thumbnailForMediaFile:file];
                    CGContextRef context = UIGraphicsGetCurrentContext();
                    CGFloat imagePartWidth = (frameSize.width / fileNumber);
                    //the rect in which the image should be drawn
                    CGRect clippingRect = CGRectMake(imagePartWidth * i, 0, imagePartWidth, frameSize.height);
                    CGContextSaveGState(context);
                    CGContextClipToRect(context, clippingRect);
                    //take the center of the clippingRect and calculate the offset from the original center
                    CGFloat centerOffset = (imagePartWidth * i + imagePartWidth / 2) - frameSize.width / 2;
                    //shift the rect to draw the middle of the image in the clippingrect
                    CGRect drawingRect = CGRectMake(centerOffset, 0, frameSize.width, frameSize.height);
                    [displayedImage drawInRect:drawingRect];
                    //get rid of the old clippingRect
                    CGContextRestoreGState(context);
                }
                displayedImage = UIGraphicsGetImageFromCurrentImageContext();
                UIGraphicsEndImageContext();
                self.thumbnailView.image = displayedImage;
            }
        }
173 174
    } else if ([self.mediaObject isKindOfClass:[MLAlbum class]]) {
        MLAlbum *mediaObject = (MLAlbum *)self.mediaObject;
175
        [self _configureForAlbum:mediaObject];
176

177 178 179 180
        if ([keyPath isEqualToString:@"computedThumbnail"] || !keyPath || (!self.thumbnailView.image && [keyPath isEqualToString:@"editing"])) {
            MLFile *anyFileFromAnyTrack = [mediaObject.tracks.anyObject files].anyObject;
            self.thumbnailView.image = [VLCThumbnailsCache thumbnailForMediaFile:anyFileFromAnyTrack];
        }
181 182
    } else if ([self.mediaObject isKindOfClass:[MLAlbumTrack class]]) {
        MLAlbumTrack *mediaObject = (MLAlbumTrack *)self.mediaObject;
183
        [self _configureForAlbumTrack:mediaObject];
184

185 186 187 188
        if ([keyPath isEqualToString:@"computedThumbnail"] || !keyPath || (!self.thumbnailView.image && [keyPath isEqualToString:@"editing"])) {
            MLFile *anyFileFromTrack = mediaObject.files.anyObject;
            self.thumbnailView.image = [VLCThumbnailsCache thumbnailForMediaFile:anyFileFromTrack];
        }
189 190
    } else if ([self.mediaObject isKindOfClass:[MLShow class]]) {
        MLShow *mediaObject = (MLShow *)self.mediaObject;
191
        [self _configureForShow:mediaObject];
192

193
        if ([keyPath isEqualToString:@"computedThumbnail"] || !keyPath || (!self.thumbnailView.image && [keyPath isEqualToString:@"editing"])) {
194
            MLFile *anyFileFromAnyEpisode = [mediaObject.episodes.anyObject files].anyObject;
195
            self.thumbnailView.image = [VLCThumbnailsCache thumbnailForMediaFile:anyFileFromAnyEpisode];
196 197 198
        }
    } else if ([self.mediaObject isKindOfClass:[MLShowEpisode class]]) {
        MLShowEpisode *mediaObject = (MLShowEpisode *)self.mediaObject;
199
        [self _configureForShowEpisode:mediaObject];
200

201
        if ([keyPath isEqualToString:@"computedThumbnail"] || !keyPath || (!self.thumbnailView.image && [keyPath isEqualToString:@"editing"])) {
202
            MLFile *anyFileFromEpisode = mediaObject.files.anyObject;
203
            self.thumbnailView.image = [VLCThumbnailsCache thumbnailForMediaFile:anyFileFromEpisode];
204 205 206 207 208 209 210 211
        }
    }

    [self setNeedsDisplay];
}

#pragma mark - presentation

212
- (void)_configureForShow:(MLShow *)show
213 214 215
{
    self.titleLabel.text = show.name;
    NSUInteger count = show.episodes.count;
216 217 218 219
    NSString *string = @"";
    if (show.releaseYear)
        string = [NSString stringWithFormat:@"%@ — ", show.releaseYear];
    self.subtitleLabel.text = [string stringByAppendingString:[NSString stringWithFormat:(count > 1) ? NSLocalizedString(@"LIBRARY_EPISODES", @"") : NSLocalizedString(@"LIBRARY_SINGLE_EPISODE", @""), count, show.unreadEpisodes.count]];
220 221 222 223
    self.mediaIsUnreadView.hidden = YES;
    self.progressView.hidden = YES;
}

224
- (void)_configureForAlbumTrack:(MLAlbumTrack *)albumTrack
225 226 227
{
    MLFile *anyFileFromTrack = albumTrack.files.anyObject;

228
    self.subtitleLabel.text = [NSString stringWithFormat:@"%@ — %@ — %@", albumTrack.artist, [NSString stringWithFormat:NSLocalizedString(@"LIBRARY_TRACK_N", @""), albumTrack.trackNumber.intValue], [VLCTime timeWithNumber:[anyFileFromTrack duration]]];
229
    self.titleLabel.text = albumTrack.title;
230

231
    [self _showPositionOfItem:anyFileFromTrack];
232 233
}

234
- (void)_configureForShowEpisode:(MLShowEpisode *)showEpisode
235 236
{
    self.titleLabel.text = showEpisode.name;
237 238

    MLFile *anyFileFromEpisode = showEpisode.files.anyObject;
239
    if (self.titleLabel.text.length < 1) {
240
        self.titleLabel.text = [NSString stringWithFormat:@"S%02dE%02d", showEpisode.seasonNumber.intValue, showEpisode.episodeNumber.intValue];
241 242
        self.subtitleLabel.text = [NSString stringWithFormat:@"%@", [VLCTime timeWithNumber:[anyFileFromEpisode duration]]];
    } else
243
        self.subtitleLabel.text = [NSString stringWithFormat:@"S%02dE%02d — %@", showEpisode.seasonNumber.intValue, showEpisode.episodeNumber.intValue, [VLCTime timeWithNumber:[anyFileFromEpisode duration]]];
244
    [self _showPositionOfItem:anyFileFromEpisode];
245 246
}

247 248 249 250 251
- (void)_configureForAlbum:(MLAlbum *)album
{
    self.titleLabel.text = album.name;
    MLAlbumTrack *anyTrack = [album.tracks anyObject];
    NSUInteger count = album.tracks.count;
252 253 254 255
    NSMutableString *string = [[NSMutableString alloc] init];
    if (anyTrack) {
        [string appendString:anyTrack.artist];
        [string appendString:@" — "];
256
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
257 258 259
    [string appendString:[NSString stringWithFormat:(count > 1) ? NSLocalizedString(@"LIBRARY_TRACKS", @"") : NSLocalizedString(@"LIBRARY_SINGLE_TRACK", @""), count]];
    if (album.releaseYear)
        [string appendFormat:@" — %@", album.releaseYear];
260
    self.subtitleLabel.text = string;
261 262 263 264
    self.mediaIsUnreadView.hidden = YES;
    self.progressView.hidden = YES;
}

265 266 267 268 269 270 271 272 273
- (void)_configureForFolder:(MLLabel *)label
{
    self.titleLabel.text = label.name;
    NSUInteger count = label.files.count;
    self.subtitleLabel.text = [NSString stringWithFormat:(count == 1) ? NSLocalizedString(@"LIBRARY_TRACKS", @"") : NSLocalizedString(@"LIBRARY_SINGLE_TRACK", @""), count];
    self.mediaIsUnreadView.hidden = YES;
    self.progressView.hidden = YES;
}

274
- (void)_configureForMLFile:(MLFile *)mediaFile
275
{
276
    if (mediaFile.isAlbumTrack) {
277 278 279 280 281 282
        NSString *string = @"";
        if (mediaFile.albumTrack.artist)
            string = [NSString stringWithFormat:@"%@ — ", mediaFile.albumTrack.artist];
        else if (mediaFile.albumTrack.album.name)
            string = [NSString stringWithFormat:@"%@ — ", mediaFile.albumTrack.artist];
        self.titleLabel.text = [string stringByAppendingString:(mediaFile.albumTrack.title.length > 1) ? mediaFile.albumTrack.title : mediaFile.title];
283 284 285 286 287 288
    } else
        self.titleLabel.text = mediaFile.title;

    VLCPlaylistViewController *delegate = (VLCPlaylistViewController*)self.collectionView.delegate;

    if (delegate.isEditing)
289
        self.subtitleLabel.text = [NSString stringWithFormat:@"%@ — %@", [VLCTime timeWithNumber:[mediaFile duration]], [NSByteCountFormatter stringFromByteCount:[mediaFile fileSizeInBytes] countStyle:NSByteCountFormatterCountStyleFile]];
290 291 292 293 294 295 296 297 298
    else {
        self.subtitleLabel.text = [NSString stringWithFormat:@"%@", [VLCTime timeWithNumber:[mediaFile duration]]];
        if (mediaFile.videoTrack) {
            NSString *width = [[mediaFile videoTrack] valueForKey:@"width"];
            NSString *height = [[mediaFile videoTrack] valueForKey:@"height"];
            if (width.intValue > 0 && height.intValue > 0)
                self.subtitleLabel.text = [self.subtitleLabel.text stringByAppendingFormat:@" — %@x%@", width, height];
        }
    }
299 300 301 302 303 304
    [self _showPositionOfItem:mediaFile];
}

- (void)_showPositionOfItem:(MLFile *)mediaItem
{
    CGFloat position = mediaItem.lastPosition.floatValue;
305 306 307

    if (SYSTEM_RUNS_IOS7_OR_LATER) {
        CGFloat duration = mediaItem.duration.floatValue;
308
        if (position > .05f && position < .95f && (duration * position - duration) < -60000) {
309 310 311 312 313 314 315 316 317 318 319 320 321
            [(UITextView*)self.mediaIsUnreadView setText:[NSString stringWithFormat:NSLocalizedString(@"LIBRARY_MINUTES_LEFT", @""), [[VLCTime timeWithInt:(duration * position - duration)] minuteStringValue]]];
            self.mediaIsUnreadView.hidden = NO;
        } else if (mediaItem.unread.intValue) {
            [(UILabel *)self.mediaIsUnreadView setText:[NSLocalizedString(@"NEW", @"") capitalizedStringWithLocale:[NSLocale currentLocale]]];
            self.mediaIsUnreadView.hidden = NO;
        } else
            self.mediaIsUnreadView.hidden = YES;
    } else {
        self.progressView.progress = position;
        self.progressView.hidden = ((position < .1f) || (position > .95f)) ? YES : NO;
        [self.progressView setNeedsDisplay];
        self.mediaIsUnreadView.hidden = !mediaItem.unread.intValue;
    }
322 323 324
}

@end