diff --git a/examples/ASDKTube/Sample.xcodeproj/project.pbxproj b/examples/ASDKTube/Sample.xcodeproj/project.pbxproj index e9965d62..f808ae18 100644 --- a/examples/ASDKTube/Sample.xcodeproj/project.pbxproj +++ b/examples/ASDKTube/Sample.xcodeproj/project.pbxproj @@ -14,6 +14,11 @@ 5791C5525B690FA54F26ACE8 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A2092CAF5607B3863A3700A2 /* libPods-Sample.a */; }; 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AA19EE274300767484 /* Default-667h@2x.png */; }; 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AB19EE274300767484 /* Default-736h@3x.png */; }; + 8B0768B81CE7AD03002E1453 /* VideoModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B0768B71CE7AD03002E1453 /* VideoModel.m */; }; + 8B0768BC1CE7B091002E1453 /* VideoContentCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B0768BB1CE7B091002E1453 /* VideoContentCell.m */; }; + 8B0768BF1CE7C5A1002E1453 /* WindowWithStatusBarUnderlay.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B0768BE1CE7C5A1002E1453 /* WindowWithStatusBarUnderlay.m */; }; + 8B0768C51CE7C707002E1453 /* Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B0768C41CE7C707002E1453 /* Utilities.m */; }; + 8B0768C91CE7C889002E1453 /* VideoFeedNodeController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B0768C81CE7C889002E1453 /* VideoFeedNodeController.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -27,6 +32,16 @@ 05E2128C19D4DB510098F589 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 6C2C82AA19EE274300767484 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; }; 6C2C82AB19EE274300767484 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; }; + 8B0768B61CE7AD03002E1453 /* VideoModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VideoModel.h; sourceTree = ""; }; + 8B0768B71CE7AD03002E1453 /* VideoModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VideoModel.m; sourceTree = ""; }; + 8B0768BA1CE7B091002E1453 /* VideoContentCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VideoContentCell.h; sourceTree = ""; }; + 8B0768BB1CE7B091002E1453 /* VideoContentCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VideoContentCell.m; sourceTree = ""; }; + 8B0768BD1CE7C5A1002E1453 /* WindowWithStatusBarUnderlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowWithStatusBarUnderlay.h; sourceTree = ""; }; + 8B0768BE1CE7C5A1002E1453 /* WindowWithStatusBarUnderlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WindowWithStatusBarUnderlay.m; sourceTree = ""; }; + 8B0768C31CE7C707002E1453 /* Utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Utilities.h; sourceTree = ""; }; + 8B0768C41CE7C707002E1453 /* Utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Utilities.m; sourceTree = ""; }; + 8B0768C71CE7C889002E1453 /* VideoFeedNodeController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VideoFeedNodeController.h; sourceTree = ""; }; + 8B0768C81CE7C889002E1453 /* VideoFeedNodeController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VideoFeedNodeController.m; sourceTree = ""; }; A2092CAF5607B3863A3700A2 /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; CFD6AA1D30516C27DEE5602B /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; E51646FF8D3676A1D826A5AE /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; @@ -68,11 +83,16 @@ 05E2128319D4DB510098F589 /* Sample */ = { isa = PBXGroup; children = ( + 8B0768C61CE7C85F002E1453 /* Controller */, + 8B0768B91CE7B07E002E1453 /* Nodes */, + 8B0768B51CE7ACE8002E1453 /* Models */, 05E2128819D4DB510098F589 /* AppDelegate.h */, 05E2128919D4DB510098F589 /* AppDelegate.m */, 05E2128B19D4DB510098F589 /* ViewController.h */, 05E2128C19D4DB510098F589 /* ViewController.m */, 05E2128419D4DB510098F589 /* Supporting Files */, + 8B0768BD1CE7C5A1002E1453 /* WindowWithStatusBarUnderlay.h */, + 8B0768BE1CE7C5A1002E1453 /* WindowWithStatusBarUnderlay.m */, ); path = Sample; sourceTree = ""; @@ -106,6 +126,35 @@ name = Pods; sourceTree = ""; }; + 8B0768B51CE7ACE8002E1453 /* Models */ = { + isa = PBXGroup; + children = ( + 8B0768C31CE7C707002E1453 /* Utilities.h */, + 8B0768C41CE7C707002E1453 /* Utilities.m */, + 8B0768B61CE7AD03002E1453 /* VideoModel.h */, + 8B0768B71CE7AD03002E1453 /* VideoModel.m */, + ); + path = Models; + sourceTree = ""; + }; + 8B0768B91CE7B07E002E1453 /* Nodes */ = { + isa = PBXGroup; + children = ( + 8B0768BA1CE7B091002E1453 /* VideoContentCell.h */, + 8B0768BB1CE7B091002E1453 /* VideoContentCell.m */, + ); + path = Nodes; + sourceTree = ""; + }; + 8B0768C61CE7C85F002E1453 /* Controller */ = { + isa = PBXGroup; + children = ( + 8B0768C71CE7C889002E1453 /* VideoFeedNodeController.h */, + 8B0768C81CE7C889002E1453 /* VideoFeedNodeController.m */, + ); + path = Controller; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -228,8 +277,13 @@ buildActionMask = 2147483647; files = ( 05E2128D19D4DB510098F589 /* ViewController.m in Sources */, + 8B0768C91CE7C889002E1453 /* VideoFeedNodeController.m in Sources */, 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */, + 8B0768BC1CE7B091002E1453 /* VideoContentCell.m in Sources */, + 8B0768BF1CE7C5A1002E1453 /* WindowWithStatusBarUnderlay.m in Sources */, 05E2128719D4DB510098F589 /* main.m in Sources */, + 8B0768C51CE7C707002E1453 /* Utilities.m in Sources */, + 8B0768B81CE7AD03002E1453 /* VideoModel.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/examples/ASDKTube/Sample/AppDelegate.m b/examples/ASDKTube/Sample/AppDelegate.m index a8e55947..947d95ca 100644 --- a/examples/ASDKTube/Sample/AppDelegate.m +++ b/examples/ASDKTube/Sample/AppDelegate.m @@ -10,18 +10,36 @@ */ #import "AppDelegate.h" +#import "WindowWithStatusBarUnderlay.h" +#import "Utilities.h" +#import "VideoFeedNodeController.h" -#import "ViewController.h" @implementation AppDelegate -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions -{ - self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; - self.window.backgroundColor = [UIColor whiteColor]; - self.window.rootViewController = [[ViewController alloc] init]; - [self.window makeKeyAndVisible]; +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + + // this UIWindow subclass is neccessary to make the status bar opaque + _window = [[WindowWithStatusBarUnderlay alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + _window.backgroundColor = [UIColor whiteColor]; + + + VideoFeedNodeController *asdkHomeFeedVC = [[VideoFeedNodeController alloc] init]; + UINavigationController *asdkHomeFeedNavCtrl = [[UINavigationController alloc] initWithRootViewController:asdkHomeFeedVC]; + + + _window.rootViewController = asdkHomeFeedNavCtrl; + [_window makeKeyAndVisible]; + + // Nav Bar appearance + NSDictionary *attributes = @{NSForegroundColorAttributeName:[UIColor whiteColor]}; + [[UINavigationBar appearance] setTitleTextAttributes:attributes]; + [[UINavigationBar appearance] setBarTintColor:[UIColor lighOrangeColor]]; + [[UINavigationBar appearance] setTranslucent:NO]; + + [application setStatusBarStyle:UIStatusBarStyleLightContent]; + + return YES; } - @end diff --git a/examples/ASDKTube/Sample/Controller/VideoFeedNodeController.h b/examples/ASDKTube/Sample/Controller/VideoFeedNodeController.h new file mode 100644 index 00000000..28d7758f --- /dev/null +++ b/examples/ASDKTube/Sample/Controller/VideoFeedNodeController.h @@ -0,0 +1,13 @@ +// +// VideoFeedNodeController.h +// Sample +// +// Created by Erekle on 5/15/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@interface VideoFeedNodeController : ASViewController + +@end diff --git a/examples/ASDKTube/Sample/Controller/VideoFeedNodeController.m b/examples/ASDKTube/Sample/Controller/VideoFeedNodeController.m new file mode 100644 index 00000000..fea764e1 --- /dev/null +++ b/examples/ASDKTube/Sample/Controller/VideoFeedNodeController.m @@ -0,0 +1,71 @@ +// +// VideoFeedNodeController.m +// Sample +// +// Created by Erekle on 5/15/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "VideoFeedNodeController.h" +#import +#import "VideoModel.h" +#import "VideoContentCell.h" + +@interface VideoFeedNodeController () + +@end + +@implementation VideoFeedNodeController +{ + ASTableNode *_tableNode; + NSMutableArray *_videoFeedData; +} + +- (instancetype)init +{ + self.navigationItem.title = @"Home"; + _tableNode = [[ASTableNode alloc] init]; + _tableNode.delegate = self; + _tableNode.dataSource = self; + + if (!(self = [super initWithNode:_tableNode])) { + return nil; + } + + return self; +} + +- (void)loadView +{ + [super loadView]; + + [self generateFeedData]; + + [_tableNode.view reloadData]; +} + +- (void)generateFeedData +{ + _videoFeedData = [[NSMutableArray alloc] init]; + + for (int i = 0; i < 30; i++) { + [_videoFeedData addObject:[[VideoModel alloc] init]]; + } +} + +#pragma mark - ASCollectionDelegate - ASCollectionDataSource +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ + return _videoFeedData.count; +} + +- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath +{ + VideoModel *videoObject = [_videoFeedData objectAtIndex:indexPath.row]; + VideoContentCell *cellNode = [[VideoContentCell alloc] initWithVideoObject:videoObject]; + return cellNode; +} +@end diff --git a/examples/ASDKTube/Sample/Info.plist b/examples/ASDKTube/Sample/Info.plist index 35d84282..3b4c6c78 100644 --- a/examples/ASDKTube/Sample/Info.plist +++ b/examples/ASDKTube/Sample/Info.plist @@ -2,6 +2,8 @@ + UIViewControllerBasedStatusBarAppearance + CFBundleDevelopmentRegion en CFBundleExecutable @@ -26,11 +28,11 @@ armv7 + UIStatusBarStyle + UIStatusBarStyleLightContent UISupportedInterfaceOrientations UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight diff --git a/examples/ASDKTube/Sample/Models/Utilities.h b/examples/ASDKTube/Sample/Models/Utilities.h new file mode 100644 index 00000000..8928fd97 --- /dev/null +++ b/examples/ASDKTube/Sample/Models/Utilities.h @@ -0,0 +1,40 @@ +// +// Utilities.h +// ASDKgram +// +// Created by Hannah Troisi on 3/9/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// +#include +@interface UIColor (Additions) + ++ (UIColor *)lighOrangeColor; ++ (UIColor *)darkBlueColor; ++ (UIColor *)lightBlueColor; + +@end + +@interface UIImage (Additions) + ++ (UIImage *)followingButtonStretchableImageForCornerRadius:(CGFloat)cornerRadius following:(BOOL)followingEnabled; ++ (void)downloadImageForURL:(NSURL *)url completion:(void (^)(UIImage *))block; + +- (UIImage *)makeCircularImageWithSize:(CGSize)size; + +@end + +@interface NSString (Additions) + +// returns a user friendly elapsed time such as '50s', '6m' or '3w' ++ (NSString *)elapsedTimeStringSinceDate:(NSString *)uploadDateString; + +@end + +@interface NSAttributedString (Additions) + ++ (NSAttributedString *)attributedStringWithString:(NSString *)string + fontSize:(CGFloat)size + color:(UIColor *)color + firstWordColor:(UIColor *)firstWordColor; + +@end \ No newline at end of file diff --git a/examples/ASDKTube/Sample/Models/Utilities.m b/examples/ASDKTube/Sample/Models/Utilities.m new file mode 100644 index 00000000..79b393c1 --- /dev/null +++ b/examples/ASDKTube/Sample/Models/Utilities.m @@ -0,0 +1,230 @@ +// +// Utilities.m +// ASDKgram +// +// Created by Hannah Troisi on 3/9/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "Utilities.h" + +#define StrokeRoundedImages 0 + +@implementation UIColor (Additions) + ++ (UIColor *)lighOrangeColor +{ + return [UIColor colorWithRed:1 green:0.506 blue:0.384 alpha:1]; +} + ++ (UIColor *)darkBlueColor +{ + return [UIColor colorWithRed:70.0/255.0 green:102.0/255.0 blue:118.0/255.0 alpha:1.0]; +} + ++ (UIColor *)lightBlueColor +{ + return [UIColor colorWithRed:70.0/255.0 green:165.0/255.0 blue:196.0/255.0 alpha:1.0]; +} + +@end + +@implementation UIImage (Additions) + ++ (UIImage *)followingButtonStretchableImageForCornerRadius:(CGFloat)cornerRadius following:(BOOL)followingEnabled +{ + CGSize unstretchedSize = CGSizeMake(2 * cornerRadius + 1, 2 * cornerRadius + 1); + CGRect rect = (CGRect) {CGPointZero, unstretchedSize}; + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:cornerRadius]; + + // create a graphics context for the following status button + UIGraphicsBeginImageContextWithOptions(unstretchedSize, NO, 0); + + [path addClip]; + + if (followingEnabled) { + + [[UIColor whiteColor] setFill]; + [path fill]; + + path.lineWidth = 3; + [[UIColor lightBlueColor] setStroke]; + [path stroke]; + + } else { + + [[UIColor lightBlueColor] setFill]; + [path fill]; + } + + UIImage *followingBtnImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + UIImage *followingBtnImageStretchable = [followingBtnImage stretchableImageWithLeftCapWidth:cornerRadius + topCapHeight:cornerRadius]; + return followingBtnImageStretchable; +} + ++ (void)downloadImageForURL:(NSURL *)url completion:(void (^)(UIImage *))block +{ + static NSCache *simpleImageCache = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + simpleImageCache = [[NSCache alloc] init]; + simpleImageCache.countLimit = 10; + }); + + if (!block) { + return; + } + + // check if image is cached + UIImage *image = [simpleImageCache objectForKey:url]; + if (image) { + dispatch_async(dispatch_get_main_queue(), ^{ + block(image); + }); + } else { + // else download image + NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; + NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { + if (data) { + UIImage *image = [UIImage imageWithData:data]; + dispatch_async(dispatch_get_main_queue(), ^{ + block(image); + }); + } + }]; + [task resume]; + } +} + +- (UIImage *)makeCircularImageWithSize:(CGSize)size +{ + // make a CGRect with the image's size + CGRect circleRect = (CGRect) {CGPointZero, size}; + + // begin the image context since we're not in a drawRect: + UIGraphicsBeginImageContextWithOptions(circleRect.size, NO, 0); + + // create a UIBezierPath circle + UIBezierPath *circle = [UIBezierPath bezierPathWithRoundedRect:circleRect cornerRadius:circleRect.size.width/2]; + + // clip to the circle + [circle addClip]; + + // draw the image in the circleRect *AFTER* the context is clipped + [self drawInRect:circleRect]; + + // create a border (for white background pictures) +#if StrokeRoundedImages + circle.lineWidth = 1; + [[UIColor darkGrayColor] set]; + [circle stroke]; +#endif + + // get an image from the image context + UIImage *roundedImage = UIGraphicsGetImageFromCurrentImageContext(); + + // end the image context since we're not in a drawRect: + UIGraphicsEndImageContext(); + + return roundedImage; +} + +@end + +@implementation NSString (Additions) + +// Returns a user-visible date time string that corresponds to the +// specified RFC 3339 date time string. Note that this does not handle +// all possible RFC 3339 date time strings, just one of the most common +// styles. ++ (NSDate *)userVisibleDateTimeStringForRFC3339DateTimeString:(NSString *)rfc3339DateTimeString +{ + NSDateFormatter * rfc3339DateFormatter; + NSLocale * enUSPOSIXLocale; + + // Convert the RFC 3339 date time string to an NSDate. + + rfc3339DateFormatter = [[NSDateFormatter alloc] init]; + + enUSPOSIXLocale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + + [rfc3339DateFormatter setLocale:enUSPOSIXLocale]; + [rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ssZ'"]; + [rfc3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + + return [rfc3339DateFormatter dateFromString:rfc3339DateTimeString]; +} + ++ (NSString *)elapsedTimeStringSinceDate:(NSString *)uploadDateString +{ + // early return if no post date string + if (!uploadDateString) + { + return @"NO POST DATE"; + } + + NSDate *postDate = [self userVisibleDateTimeStringForRFC3339DateTimeString:uploadDateString]; + + if (!postDate) { + return @"DATE CONVERSION ERROR"; + } + + NSDate *currentDate = [NSDate date]; + + NSCalendar *calendar = [NSCalendar currentCalendar]; + + NSUInteger seconds = [[calendar components:NSCalendarUnitSecond fromDate:postDate toDate:currentDate options:0] second]; + NSUInteger minutes = [[calendar components:NSCalendarUnitMinute fromDate:postDate toDate:currentDate options:0] minute]; + NSUInteger hours = [[calendar components:NSCalendarUnitHour fromDate:postDate toDate:currentDate options:0] hour]; + NSUInteger days = [[calendar components:NSCalendarUnitDay fromDate:postDate toDate:currentDate options:0] day]; + + NSString *elapsedTime; + + if (days > 7) { + elapsedTime = [NSString stringWithFormat:@"%luw", (long)ceil(days/7.0)]; + } else if (days > 0) { + elapsedTime = [NSString stringWithFormat:@"%lud", (long)days]; + } else if (hours > 0) { + elapsedTime = [NSString stringWithFormat:@"%luh", (long)hours]; + } else if (minutes > 0) { + elapsedTime = [NSString stringWithFormat:@"%lum", (long)minutes]; + } else if (seconds > 0) { + elapsedTime = [NSString stringWithFormat:@"%lus", (long)seconds]; + } else if (seconds == 0) { + elapsedTime = @"1s"; + } else { + elapsedTime = @"ERROR"; + } + + return elapsedTime; +} + +@end + +@implementation NSAttributedString (Additions) + ++ (NSAttributedString *)attributedStringWithString:(NSString *)string fontSize:(CGFloat)size + color:(nullable UIColor *)color firstWordColor:(nullable UIColor *)firstWordColor +{ + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init]; + + if (string) { + NSDictionary *attributes = @{NSForegroundColorAttributeName: color ? : [UIColor blackColor], + NSFontAttributeName: [UIFont systemFontOfSize:size]}; + attributedString = [[NSMutableAttributedString alloc] initWithString:string]; + [attributedString addAttributes:attributes range:NSMakeRange(0, string.length)]; + + if (firstWordColor) { + NSRange firstSpaceRange = [string rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]]; + NSRange firstWordRange = NSMakeRange(0, firstSpaceRange.location); + [attributedString addAttribute:NSForegroundColorAttributeName value:firstWordColor range:firstWordRange]; + } + } + + return attributedString; +} + +@end diff --git a/examples/ASDKTube/Sample/Models/VideoModel.h b/examples/ASDKTube/Sample/Models/VideoModel.h new file mode 100644 index 00000000..63e7f482 --- /dev/null +++ b/examples/ASDKTube/Sample/Models/VideoModel.h @@ -0,0 +1,16 @@ +// +// VideoModel.h +// Sample +// +// Created by Erekle on 5/14/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@interface VideoModel : NSObject +@property (nonatomic, strong, readonly) NSString* title; +@property (nonatomic, strong, readonly) NSURL *url; +@property (nonatomic, strong, readonly) NSString *userName; +@property (nonatomic, strong, readonly) NSURL *avatarUrl; +@end diff --git a/examples/ASDKTube/Sample/Models/VideoModel.m b/examples/ASDKTube/Sample/Models/VideoModel.m new file mode 100644 index 00000000..2f233d59 --- /dev/null +++ b/examples/ASDKTube/Sample/Models/VideoModel.m @@ -0,0 +1,27 @@ +// +// VideoModel.m +// Sample +// +// Created by Erekle on 5/14/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "VideoModel.h" + +@implementation VideoModel +- (instancetype)init +{ + self = [super init]; + if (self) { + NSString *videoUrlString = @"https://files.parsetfss.com/8a8a3b0c-619e-4e4d-b1d5-1b5ba9bf2b42/tfss-3045b261-7e93-4492-b7e5-5d6358376c9f-editedLiveAndDie.mov"; + NSString *avatarUrlString = [NSString stringWithFormat:@"https://api.adorable.io/avatars/50/%@",[[NSProcessInfo processInfo] globallyUniqueString]]; + + _title = @"Demo title"; + _url = [NSURL URLWithString:videoUrlString]; + _userName = @"Random User"; + _avatarUrl = [NSURL URLWithString:avatarUrlString]; + } + + return self; +} +@end diff --git a/examples/ASDKTube/Sample/Nodes/VideoContentCell.h b/examples/ASDKTube/Sample/Nodes/VideoContentCell.h new file mode 100644 index 00000000..ca6bb325 --- /dev/null +++ b/examples/ASDKTube/Sample/Nodes/VideoContentCell.h @@ -0,0 +1,14 @@ +// +// VideoContentCell.h +// Sample +// +// Created by Erekle on 5/14/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import +#import "VideoModel.h" + +@interface VideoContentCell : ASCellNode +- (instancetype)initWithVideoObject:(VideoModel *)video; +@end diff --git a/examples/ASDKTube/Sample/Nodes/VideoContentCell.m b/examples/ASDKTube/Sample/Nodes/VideoContentCell.m new file mode 100644 index 00000000..d5db4a84 --- /dev/null +++ b/examples/ASDKTube/Sample/Nodes/VideoContentCell.m @@ -0,0 +1,151 @@ +// +// VideoContentCell.m +// Sample +// +// Created by Erekle on 5/14/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "VideoContentCell.h" +#import "ASVideoPlayerNode.h" +#import "Utilities.h" + +#define AVATAR_IMAGE_HEIGHT 30 +#define HORIZONTAL_BUFFER 10 +#define VERTICAL_BUFFER 5 + +@interface VideoContentCell () + +@end + +@implementation VideoContentCell +{ + VideoModel *_videoModel; + ASTextNode *_titleNode; + ASNetworkImageNode *_avatarNode; + ASVideoPlayerNode *_videoPlayerNode; + ASControlNode *_likeButtonNode; +} + +- (instancetype)initWithVideoObject:(VideoModel *)video +{ + self = [super init]; + if (self) { + + _videoModel = video; + + _titleNode = [[ASTextNode alloc] init]; + _titleNode.attributedText = [[NSAttributedString alloc] initWithString:_videoModel.title attributes:[self titleNodeStringOptions]]; + _titleNode.flexGrow = YES; + [self addSubnode:_titleNode]; + + _avatarNode = [[ASNetworkImageNode alloc] init]; + _avatarNode.URL = _videoModel.avatarUrl; + + [_avatarNode setImageModificationBlock:^UIImage *(UIImage *image) { + CGSize profileImageSize = CGSizeMake(AVATAR_IMAGE_HEIGHT, AVATAR_IMAGE_HEIGHT); + return [image makeCircularImageWithSize:profileImageSize]; + }]; + + [self addSubnode:_avatarNode]; + + _likeButtonNode = [[ASControlNode alloc] init]; + _likeButtonNode.backgroundColor = [UIColor redColor]; + [self addSubnode:_likeButtonNode]; + + _videoPlayerNode = [[ASVideoPlayerNode alloc] initWithUrl:_videoModel.url]; + _videoPlayerNode.delegate = self; + [self addSubnode:_videoPlayerNode]; + } + return self; +} + +- (NSDictionary*)titleNodeStringOptions +{ + return @{ + NSFontAttributeName : [UIFont systemFontOfSize:14.0], + NSForegroundColorAttributeName: [UIColor blackColor] + }; +} + +- (ASLayoutSpec*)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + CGFloat fullWidth = [UIScreen mainScreen].bounds.size.width; + _videoPlayerNode.preferredFrameSize = CGSizeMake(fullWidth, fullWidth * 9 / 16); + _avatarNode.preferredFrameSize = CGSizeMake(AVATAR_IMAGE_HEIGHT, AVATAR_IMAGE_HEIGHT); + _likeButtonNode.preferredFrameSize = CGSizeMake(50.0, 26.0); + + ASStackLayoutSpec *headerStack = [ASStackLayoutSpec horizontalStackLayoutSpec]; + headerStack.spacing = HORIZONTAL_BUFFER; + headerStack.alignItems = ASStackLayoutAlignItemsCenter; + [headerStack setChildren:@[ _avatarNode, _titleNode]]; + + UIEdgeInsets headerInsets = UIEdgeInsetsMake(HORIZONTAL_BUFFER, HORIZONTAL_BUFFER, HORIZONTAL_BUFFER, HORIZONTAL_BUFFER); + ASInsetLayoutSpec *headerInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:headerInsets child:headerStack]; + + ASStackLayoutSpec *bottomControlsStack = [ASStackLayoutSpec horizontalStackLayoutSpec]; + bottomControlsStack.spacing = HORIZONTAL_BUFFER; + bottomControlsStack.alignItems = ASStackLayoutAlignItemsCenter; + [bottomControlsStack setChildren:@[ _likeButtonNode]]; + + UIEdgeInsets bottomControlsInsets = UIEdgeInsetsMake(HORIZONTAL_BUFFER, HORIZONTAL_BUFFER, HORIZONTAL_BUFFER, HORIZONTAL_BUFFER); + ASInsetLayoutSpec *bottomControlsInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:bottomControlsInsets child:bottomControlsStack]; + + + ASStackLayoutSpec *verticalStack = [ASStackLayoutSpec verticalStackLayoutSpec]; + verticalStack.alignItems = ASStackLayoutAlignItemsStretch; + [verticalStack setChildren:@[ headerInset, _videoPlayerNode, bottomControlsInset ]]; + return verticalStack; +} + +#pragma mark - ASVideoPlayerNodeDelegate +- (void)videoPlayerNodeWasTapped:(ASVideoPlayerNode *)videoPlayer +{ + if (_videoPlayerNode.playerState == ASVideoNodePlayerStatePlaying) { + NSLog(@"TRANSITION"); + } else { + [_videoPlayerNode play]; + } +} + +- (NSArray *)videoPlayerNodeNeededControls:(ASVideoPlayerNode *)videoPlayer +{ + return @[ @(ASVideoPlayerNodeControlTypePlaybackButton) ]; +} + +- (ASLayoutSpec *)videoPlayerNodeLayoutSpec:(ASVideoPlayerNode *)videoPlayer forControls:(NSDictionary *)controls forMaximumSize:(CGSize)maxSize +{ + NSMutableArray *bottomControls = [[NSMutableArray alloc] init]; + + ASDisplayNode *playbackButtonNode = controls[@(ASVideoPlayerNodeControlTypePlaybackButton)]; + + if (playbackButtonNode) { + [bottomControls addObject:playbackButtonNode]; + } + + ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; + spacer.flexGrow = YES; + + ASStackLayoutSpec *controlbarSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal + spacing:10.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsCenter + children:bottomControls]; + controlbarSpec.alignSelf = ASStackLayoutAlignSelfStretch; + + UIEdgeInsets insets = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0); + + ASInsetLayoutSpec *controlbarInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:controlbarSpec]; + + controlbarInsetSpec.alignSelf = ASStackLayoutAlignSelfStretch; + + ASStackLayoutSpec *mainVerticalStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical + spacing:0.0 + justifyContent:ASStackLayoutJustifyContentStart + alignItems:ASStackLayoutAlignItemsStart + children:@[ spacer, controlbarInsetSpec ]]; + + + return mainVerticalStack; +} +@end diff --git a/examples/ASDKTube/Sample/ViewController.h b/examples/ASDKTube/Sample/ViewController.h index 8b16b1c3..d4ec993c 100644 --- a/examples/ASDKTube/Sample/ViewController.h +++ b/examples/ASDKTube/Sample/ViewController.h @@ -8,8 +8,8 @@ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + #import -#import @interface ViewController : ASViewController diff --git a/examples/ASDKTube/Sample/ViewController.m b/examples/ASDKTube/Sample/ViewController.m index b1d44e25..adb64991 100644 --- a/examples/ASDKTube/Sample/ViewController.m +++ b/examples/ASDKTube/Sample/ViewController.m @@ -10,31 +10,73 @@ */ #import "ViewController.h" +#import +#import +#import "VideoModel.h" +#import "VideoContentCell.h" -@interface ViewController() +@interface ViewController() @property (nonatomic, strong) ASVideoPlayerNode *videoPlayerNode; @end @implementation ViewController +{ + ASTableNode *_tableNode; + NSMutableArray *_videoFeedData; +} - (instancetype)init { - if (!(self = [super initWithNode:self.videoPlayerNode])) { + _tableNode = [[ASTableNode alloc] init]; + _tableNode.delegate = self; + _tableNode.dataSource = self; + + if (!(self = [super initWithNode:_tableNode])) { return nil; } return self; } +- (void)loadView +{ + [super loadView]; + + _videoFeedData = [[NSMutableArray alloc] initWithObjects:[[VideoModel alloc] init], [[VideoModel alloc] init], nil]; + + [_tableNode.view reloadData]; +} + - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - + //[self.view addSubnode:self.videoPlayerNode]; //[self.videoPlayerNode setNeedsLayout]; } +#pragma mark - ASCollectionDelegate - ASCollectionDataSource +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ + return _videoFeedData.count; +} + +- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath +{ + VideoModel *videoObject = [_videoFeedData objectAtIndex:indexPath.row]; + VideoContentCell *cellNode = [[VideoContentCell alloc] initWithVideoObject:videoObject]; + return cellNode; +} + +//- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath{ +// CGFloat fullWidth = [UIScreen mainScreen].bounds.size.width; +// return ASSizeRangeMake(CGSizeMake(fullWidth, 0.0), CGSizeMake(fullWidth, 400.0)); +//} + - (ASVideoPlayerNode *)videoPlayerNode; { if (_videoPlayerNode) { @@ -57,47 +99,47 @@ } #pragma mark - ASVideoPlayerNodeDelegate -- (NSArray *)videoPlayerNodeNeededControls:(ASVideoPlayerNode *)videoPlayer -{ - return @[ @(ASVideoPlayerNodeControlTypePlaybackButton), - @(ASVideoPlayerNodeControlTypeElapsedText), - @(ASVideoPlayerNodeControlTypeScrubber), - @(ASVideoPlayerNodeControlTypeDurationText) ]; -} - -- (UIColor *)videoPlayerNodeScrubberMaximumTrackTint:(ASVideoPlayerNode *)videoPlayer -{ - return [UIColor colorWithRed:1 green:1 blue:1 alpha:0.3]; -} - -- (UIColor *)videoPlayerNodeScrubberMinimumTrackTint:(ASVideoPlayerNode *)videoPlayer -{ - return [UIColor whiteColor]; -} - -- (UIColor *)videoPlayerNodeScrubberThumbTint:(ASVideoPlayerNode *)videoPlayer -{ - return [UIColor whiteColor]; -} - -- (NSDictionary *)videoPlayerNodeTimeLabelAttributes:(ASVideoPlayerNode *)videoPlayerNode timeLabelType:(ASVideoPlayerNodeControlType)timeLabelType -{ - NSDictionary *options; - - if (timeLabelType == ASVideoPlayerNodeControlTypeElapsedText) { - options = @{ - NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:16.0], - NSForegroundColorAttributeName: [UIColor orangeColor] - }; - } else if (timeLabelType == ASVideoPlayerNodeControlTypeDurationText) { - options = @{ - NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:16.0], - NSForegroundColorAttributeName: [UIColor redColor] - }; - } - - return options; -} +//- (NSArray *)videoPlayerNodeNeededControls:(ASVideoPlayerNode *)videoPlayer +//{ +// return @[ @(ASVideoPlayerNodeControlTypePlaybackButton), +// @(ASVideoPlayerNodeControlTypeElapsedText), +// @(ASVideoPlayerNodeControlTypeScrubber), +// @(ASVideoPlayerNodeControlTypeDurationText) ]; +//} +// +//- (UIColor *)videoPlayerNodeScrubberMaximumTrackTint:(ASVideoPlayerNode *)videoPlayer +//{ +// return [UIColor colorWithRed:1 green:1 blue:1 alpha:0.3]; +//} +// +//- (UIColor *)videoPlayerNodeScrubberMinimumTrackTint:(ASVideoPlayerNode *)videoPlayer +//{ +// return [UIColor whiteColor]; +//} +// +//- (UIColor *)videoPlayerNodeScrubberThumbTint:(ASVideoPlayerNode *)videoPlayer +//{ +// return [UIColor whiteColor]; +//} +// +//- (NSDictionary *)videoPlayerNodeTimeLabelAttributes:(ASVideoPlayerNode *)videoPlayerNode timeLabelType:(ASVideoPlayerNodeControlType)timeLabelType +//{ +// NSDictionary *options; +// +// if (timeLabelType == ASVideoPlayerNodeControlTypeElapsedText) { +// options = @{ +// NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:16.0], +// NSForegroundColorAttributeName: [UIColor orangeColor] +// }; +// } else if (timeLabelType == ASVideoPlayerNodeControlTypeDurationText) { +// options = @{ +// NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:16.0], +// NSForegroundColorAttributeName: [UIColor redColor] +// }; +// } +// +// return options; +//} /*- (ASLayoutSpec *)videoPlayerNodeLayoutSpec:(ASVideoPlayerNode *)videoPlayer forControls:(NSDictionary *)controls diff --git a/examples/ASDKTube/Sample/WindowWithStatusBarUnderlay.h b/examples/ASDKTube/Sample/WindowWithStatusBarUnderlay.h new file mode 100644 index 00000000..43ad2663 --- /dev/null +++ b/examples/ASDKTube/Sample/WindowWithStatusBarUnderlay.h @@ -0,0 +1,13 @@ +// +// WindowWithStatusBarUnderlay.h +// Sample +// +// Created by Hannah Troisi on 4/10/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@interface WindowWithStatusBarUnderlay : UIWindow + +@end diff --git a/examples/ASDKTube/Sample/WindowWithStatusBarUnderlay.m b/examples/ASDKTube/Sample/WindowWithStatusBarUnderlay.m new file mode 100644 index 00000000..d50df6b2 --- /dev/null +++ b/examples/ASDKTube/Sample/WindowWithStatusBarUnderlay.m @@ -0,0 +1,39 @@ +// +// WindowWithStatusBarUnderlay.m +// Sample +// +// Created by Erekle on 5/15/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "WindowWithStatusBarUnderlay.h" +#import "Utilities.h" + +@implementation WindowWithStatusBarUnderlay +{ + UIView *_statusBarOpaqueUnderlayView; +} + +-(instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + _statusBarOpaqueUnderlayView = [[UIView alloc] init]; + _statusBarOpaqueUnderlayView.backgroundColor = [UIColor lighOrangeColor]; + [self addSubview:_statusBarOpaqueUnderlayView]; + } + return self; +} + +-(void)layoutSubviews +{ + [super layoutSubviews]; + + [self bringSubviewToFront:_statusBarOpaqueUnderlayView]; + + CGRect statusBarFrame = CGRectZero; + statusBarFrame.size.width = [[UIScreen mainScreen] bounds].size.width; + statusBarFrame.size.height = [[UIApplication sharedApplication] statusBarFrame].size.height; + _statusBarOpaqueUnderlayView.frame = statusBarFrame; +} +@end diff --git a/examples/ASDKgram/Sample.xcodeproj/project.pbxproj b/examples/ASDKgram/Sample.xcodeproj/project.pbxproj index 9307b60a..4aba4f49 100644 --- a/examples/ASDKgram/Sample.xcodeproj/project.pbxproj +++ b/examples/ASDKgram/Sample.xcodeproj/project.pbxproj @@ -254,12 +254,12 @@ isa = PBXNativeTarget; buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; buildPhases = ( - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + E080B80F89C34A25B3488E26 /* 📦 Check Pods Manifest.lock */, 05E2127D19D4DB510098F589 /* Sources */, 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, - F012A6F39E0149F18F564F50 /* Copy Pods Resources */, - 06770D39D4186D6446B1BDD5 /* Embed Pods Frameworks */, + F012A6F39E0149F18F564F50 /* 📦 Copy Pods Resources */, + 06770D39D4186D6446B1BDD5 /* 📦 Embed Pods Frameworks */, ); buildRules = ( ); @@ -319,14 +319,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 06770D39D4186D6446B1BDD5 /* Embed Pods Frameworks */ = { + 06770D39D4186D6446B1BDD5 /* 📦 Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Embed Pods Frameworks"; + name = "📦 Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -334,14 +334,14 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + E080B80F89C34A25B3488E26 /* 📦 Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Check Pods Manifest.lock"; + name = "📦 Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -349,14 +349,14 @@ shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; - F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + F012A6F39E0149F18F564F50 /* 📦 Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "📦 Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; diff --git a/examples/ASDKgram/Sample.xcworkspace/contents.xcworkspacedata b/examples/ASDKgram/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..7b5a2f30 --- /dev/null +++ b/examples/ASDKgram/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + +