Skip to content
This repository has been archived by the owner on May 19, 2018. It is now read-only.

Commit

Permalink
Basics for Notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
dennisreimann committed Jan 14, 2013
1 parent 6b80d45 commit 06ffbb5
Show file tree
Hide file tree
Showing 41 changed files with 1,135 additions and 103 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -5,10 +5,12 @@
Additions:

* Check GitHub system status on app activation
* Notifications support (push notifications will be added soon)

Changes:

* New icons for public and private state (repos and gists)
* Finetuned the icon. Thanks @benjaminrabe

Bugfixes:

Expand Down
11 changes: 9 additions & 2 deletions Classes/CommitController.m
Expand Up @@ -48,6 +48,7 @@ @implementation CommitController

NSString *const CommitLoadingKeyPath = @"loadingStatus";
NSString *const CommitCommentsLoadingKeyPath = @"comments.loadingStatus";
NSString *const CommitAuthorGravatarKeyPath = @"author.gravatar";

- (id)initWithCommit:(GHCommit *)commit {
self = [super initWithNibName:@"Commit" bundle:nil];
Expand All @@ -61,12 +62,15 @@ - (void)viewDidLoad {
[super viewDidLoad];
[self.commit addObserver:self forKeyPath:CommitLoadingKeyPath options:NSKeyValueObservingOptionNew context:nil];
[self.commit addObserver:self forKeyPath:CommitCommentsLoadingKeyPath options:NSKeyValueObservingOptionNew context:nil];
[self.commit addObserver:self forKeyPath:CommitAuthorGravatarKeyPath options:NSKeyValueObservingOptionNew context:nil];
self.title = [self.commit.commitID substringToIndex:8];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(showActions:)];
// Background
UIColor *background = [UIColor colorWithPatternImage:[UIImage imageNamed:@"HeadBackground90.png"]];
self.tableHeaderView.backgroundColor = background;
self.tableView.tableHeaderView = self.tableHeaderView;
self.gravatarView.layer.cornerRadius = 3;
self.gravatarView.layer.masksToBounds = YES;
}

- (void)viewWillAppear:(BOOL)animated {
Expand All @@ -76,6 +80,7 @@ - (void)viewWillAppear:(BOOL)animated {
}

- (void)dealloc {
[self.commit removeObserver:self forKeyPath:CommitAuthorGravatarKeyPath];
[self.commit removeObserver:self forKeyPath:CommitCommentsLoadingKeyPath];
[self.commit removeObserver:self forKeyPath:CommitLoadingKeyPath];
}
Expand All @@ -89,7 +94,7 @@ - (GHUser *)currentUser {
- (void)displayCommit {
self.titleLabel.text = self.commit.message;
self.dateLabel.text = [self.commit.committedDate prettyDate];
self.gravatarView.image = self.commit.author.gravatar;
if (self.commit.author.gravatar) self.gravatarView.image = self.commit.author.gravatar;
[self.repoCell setContentText:self.commit.repository.repoId];
[self.authorCell setContentText:self.commit.author.login];
[self.committerCell setContentText:self.commit.committer.login];
Expand Down Expand Up @@ -127,7 +132,9 @@ - (IBAction)addComment:(id)sender {
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:CommitLoadingKeyPath]) {
if ([keyPath isEqualToString:CommitAuthorGravatarKeyPath]) {
self.gravatarView.image = self.commit.author.gravatar;
} else if ([keyPath isEqualToString:CommitLoadingKeyPath]) {
if (self.commit.isLoaded) {
[self displayCommit];
} else if (self.commit.error) {
Expand Down
2 changes: 1 addition & 1 deletion Classes/EventCell.m
Expand Up @@ -115,7 +115,7 @@ - (void)markAsNew {
- (void)markAsRead {
UIColor *normalColor = [UIColor whiteColor];
[self setCustomBackgroundColor:normalColor];
self.event.read = YES;
[self.event markAsRead];
}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
Expand Down
2 changes: 1 addition & 1 deletion Classes/EventsController.m
Expand Up @@ -69,7 +69,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
[self.tableView reloadData];
} else if (self.events.error) {
[self.tableView.pullToRefreshView stopAnimating];
[iOctocat reportLoadingError:@"Could not load the feed."];
[iOctocat reportLoadingError:@"Could not load the feed"];
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions Classes/GHAccount.m
Expand Up @@ -6,6 +6,7 @@
#import "GHRepositories.h"
#import "GHOrganization.h"
#import "GHOrganizations.h"
#import "GHNotifications.h"
#import "iOctocat.h"
#import "NSString+Extensions.h"
#import "NSDictionary+Extensions.h"
Expand Down Expand Up @@ -45,6 +46,7 @@ - (id)initWithDict:(NSDictionary *)dict {
self.user.starredGists.resourcePath = kUserAuthenticatedGistsStarredFormat;
self.user.starredRepositories.resourcePath = kUserAuthenticatedStarredReposFormat;
self.user.watchedRepositories.resourcePath = kUserAuthenticatedWatchedReposFormat;
self.user.notifications = [[GHNotifications alloc] initWithPath:kNotificationsPath];
[self.user addObserver:self forKeyPath:OrgsLoadingKeyPath options:NSKeyValueObservingOptionNew context:nil];
}
return self;
Expand Down
3 changes: 2 additions & 1 deletion Classes/GHEvent.h
Expand Up @@ -24,8 +24,9 @@
@property(nonatomic,strong)NSString *content;
@property(nonatomic,readonly)NSString *extendedEventType;
@property(nonatomic,readonly)BOOL isCommentEvent;
@property(nonatomic,readwrite)BOOL read;
@property(nonatomic,readonly)BOOL read;

- (id)initWithDict:(NSDictionary *)dict;
- (void)setValues:(id)dict;
- (void)markAsRead;
@end
9 changes: 9 additions & 0 deletions Classes/GHEvent.m
Expand Up @@ -15,6 +15,11 @@
#import "NSDictionary+Extensions.h"


@interface GHEvent ()
@property(nonatomic,readwrite)BOOL read;
@end


@implementation GHEvent

- (id)initWithDict:(NSDictionary *)dict {
Expand All @@ -39,6 +44,10 @@ - (NSString *)extendedEventType {
return self.eventType;
}

- (void)markAsRead {
self.read = YES;
}

- (BOOL)isCommentEvent {
return [self.eventType hasSuffix:@"CommentEvent"];
}
Expand Down
2 changes: 1 addition & 1 deletion Classes/GHEvents.m
Expand Up @@ -15,7 +15,7 @@ - (void)setValues:(id)values {
for (NSDictionary *dict in values) {
GHEvent *event = [[GHEvent alloc] initWithDict:dict];
if ([event.date compare:self.lastUpdate] != NSOrderedDescending) {
event.read = YES;
[event markAsRead];
}
[self addObject:event];
}
Expand Down
7 changes: 5 additions & 2 deletions Classes/GHNotification.h
Expand Up @@ -10,7 +10,10 @@
@property(nonatomic,strong)NSDate *updatedAtDate;
@property(nonatomic,strong)NSDate *lastReadAtDate;
@property(nonatomic,strong)NSString *title;
@property(nonatomic,readwrite)BOOL isUnread;
@property(nonatomic,strong)NSString *subjectType;
@property(nonatomic,strong)GHResource *subject;
@property(nonatomic,readonly)BOOL read;

- (id)initWithId:(NSString *)gistId;
- (id)initWithDict:(NSDictionary *)dict;
- (void)markAsRead;
@end
39 changes: 36 additions & 3 deletions Classes/GHNotification.m
@@ -1,27 +1,60 @@
#import "GHResource.h"
#import "GHRepository.h"
#import "GHNotification.h"
#import "GHPullRequest.h"
#import "GHIssue.h"
#import "GHCommit.h"
#import "NSDictionary+Extensions.h"


@interface GHNotification ()
@property(nonatomic,readwrite)BOOL read;
@end


@implementation GHNotification

- (id)initWithId:(NSString *)notificationId {
- (id)initWithDict:(NSDictionary *)dict {
self = [super init];
if (self) {
self.notificationId = notificationId;
self.read = NO;
[self setValues:dict];
}
return self;
}

- (void)markAsRead {
self.read = YES;
}

#pragma mark Loading

- (void)setValues:(id)dict {
NSDictionary *repoDict = [dict safeDictForKey:@"repository"];
NSString *owner = [repoDict safeStringForKeyPath:@"owner.login"];
NSString *name = [repoDict safeStringForKey:@"name"];
NSURL *subjectURL = [dict safeURLForKeyPath:@"subject.url"];
self.notificationId = [dict safeStringForKey:@"id"];
self.updatedAtDate = [dict safeDateForKey:@"updated_at"];
self.lastReadAtDate = [dict safeDateForKey:@"last_read_at"];
self.isUnread = [dict safeBoolForKey:@"unread"];
self.read = [dict safeBoolForKey:@"unread"] ? ![dict safeBoolForKey:@"unread"] : NO;
self.title = [dict safeStringForKeyPath:@"subject.title"];
self.subjectType = [dict safeStringForKeyPath:@"subject.type"];
self.repository = [[GHRepository alloc] initWithOwner:owner andName:name];
[self.repository setValues:repoDict];
if ([self.subjectType isEqualToString:@"PullRequest"]) {
self.subject = [[GHPullRequest alloc] initWithRepository:self.repository];
NSInteger num = [[subjectURL lastPathComponent] intValue];
[(GHPullRequest *)self.subject setNum:num];
} else if ([self.subjectType isEqualToString:@"Issue"]) {
self.subject = [[GHIssue alloc] initWithRepository:self.repository];
NSInteger num = [[subjectURL lastPathComponent] intValue];
[(GHIssue *)self.subject setNum:num];
} else if ([self.subjectType isEqualToString:@"Commit"]) {
NSString *sha = [subjectURL lastPathComponent];
self.subject = [[GHCommit alloc] initWithRepository:self.repository andCommitID:sha];
}
if (self.subject) self.subject.resourcePath = subjectURL.path;
}

@end
3 changes: 3 additions & 0 deletions Classes/GHNotifications.h
Expand Up @@ -3,4 +3,7 @@


@interface GHNotifications : GHCollection
@property(nonatomic,strong)NSDate *lastUpdate;
@property(nonatomic,strong)NSMutableDictionary *byRepository;
@property(nonatomic,readwrite)NSInteger pollInterval;
@end
21 changes: 18 additions & 3 deletions Classes/GHNotifications.m
@@ -1,17 +1,32 @@
#import "AFHTTPRequestOperation.h"
#import "GHNotifications.h"
#import "GHNotification.h"
#import "GHRepository.h"
#import "NSDictionary+Extensions.h"


@implementation GHNotifications

- (void)setValues:(id)values {
self.items = [NSMutableArray array];
self.byRepository = [NSMutableDictionary dictionary];
for (id dict in values) {
GHNotification *resource = [[GHNotification alloc] initWithId:[dict safeStringForKey:@"id"]];
[resource setValues:dict];
[self addObject:resource];
GHNotification *notification = [[GHNotification alloc] initWithDict:dict];
if ([notification.lastReadAtDate compare:self.lastUpdate] != NSOrderedDescending) {
[notification markAsRead];
}
[self addObject:notification];
if (!self.byRepository[notification.repository.repoId]) {
self.byRepository[notification.repository.repoId] = [NSMutableArray array];
}
[self.byRepository[notification.repository.repoId] addObject:notification];
}
self.lastUpdate = [NSDate date];
}

- (void)setHeaderValues:(NSDictionary *)values {
[super setHeaderValues:values];
self.pollInterval = [values safeIntegerForKey:@"X-Poll-Interval"];
}

@end
8 changes: 7 additions & 1 deletion Classes/GHPullRequest.m
Expand Up @@ -19,6 +19,8 @@ @interface GHPullRequest ()

@implementation GHPullRequest

@synthesize resourcePath = _resourcePath;

- (id)initWithRepository:(GHRepository *)repo {
self = [super init];
if (self) {
Expand Down Expand Up @@ -46,7 +48,11 @@ - (BOOL)isClosed {
// Dynamic resourcePath, because it depends on the
// num which isn't always available in advance
- (NSString *)resourcePath {
return [NSString stringWithFormat:kPullRequestFormat, self.repository.owner, self.repository.name, self.num];
if (_resourcePath) {
return _resourcePath;
} else {
return [NSString stringWithFormat:kPullRequestFormat, self.repository.owner, self.repository.name, self.num];
}
}

#pragma mark Loading
Expand Down
1 change: 1 addition & 0 deletions Classes/GHResource.h
Expand Up @@ -23,6 +23,7 @@ typedef enum {
- (void)needsReload;
- (void)markAsLoaded;
- (void)saveValues:(NSDictionary *)values withPath:(NSString *)path andMethod:(NSString *)method useResult:(void (^)(id response))useResult;
- (void)setHeaderValues:(NSDictionary *)values;
- (void)setValues:(id)response;
- (NSString *)resourceContentType;
@end
28 changes: 17 additions & 11 deletions Classes/GHResource.m
Expand Up @@ -23,9 +23,6 @@ - (id)initWithPath:(NSString *)path {
return self;
}

- (void)setValues:(id)response {
}

- (void)needsReload {
self.loadingStatus = GHResourceStatusNotProcessed;
}
Expand All @@ -42,25 +39,34 @@ - (GHApiClient *)apiClient {
return [iOctocat sharedInstance].currentAccount.apiClient;
}

- (void)setHeaderValues:(NSDictionary *)values {
}

- (void)setValues:(id)response {
}

#pragma mark Loading

- (void)loadData {
if (self.isLoading) return;
self.error = nil;
self.loadingStatus = GHResourceStatusProcessing;
// Send the request
D3JLog(@"%@: Loading %@", self.class, self.resourcePath);
D3JLog(@"\n%@: Loading %@", self.class, self.resourcePath);
[self.apiClient setDefaultHeader:@"Accept" value:self.resourceContentType];
[self.apiClient getPath:self.resourcePath parameters:nil
success:^(AFHTTPRequestOperation *operation, id response) {
D3JLog(@"%@: Loading %@ finished: %@", self.class, self.resourcePath, response);
NSDictionary *headers = operation.response.allHeaderFields;
D3JLog(@"\n%@: Loading %@ finished:\n%@\n\nHeaders:\n%@", self.class, self.resourcePath, response, headers);
[self setHeaderValues:headers];
[self setValues:response];
self.loadingStatus = GHResourceStatusProcessed;
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
DJLog(@"%@: Loading %@ failed: %@", self.class, self.resourcePath, error);
self.error = error;
self.loadingStatus = GHResourceStatusNotProcessed;
NSDictionary *headers = operation.response.allHeaderFields;
DJLog(@"\n%@: Loading %@ failed:\n%@\n\nHeaders:\n%@", self.class, self.resourcePath, error, headers);
self.error = error;
self.loadingStatus = GHResourceStatusNotProcessed;
}
];
}
Expand All @@ -72,20 +78,20 @@ - (void)saveValues:(NSDictionary *)values withPath:(NSString *)path andMethod:(N
self.error = nil;
self.savingStatus = GHResourceStatusProcessing;
// Send the request
D3JLog(@"%@: Saving %@ (%@)\n\n%@", self.class, path, method, values);
D3JLog(@"\n%@: Saving %@ (%@)\n\n%@", self.class, path, method, values);
NSMutableURLRequest *request = [self.apiClient requestWithMethod:method
path:path
parameters:values];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id json) {
D3JLog(@"%@: Saving %@ finished: %@", self.class, path, json);
D3JLog(@"\n%@: Saving %@ finished:\n%@", self.class, path, json);
if (useResult) {
useResult(json);
}
self.savingStatus = GHResourceStatusProcessed;
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id json) {
DJLog(@"%@: Saving %@ failed: %@", self.class, path, error);
DJLog(@"\n%@: Saving %@ failed:\n%@", self.class, path, error);
self.error = error;
self.savingStatus = GHResourceStatusNotProcessed;
}
Expand Down
3 changes: 2 additions & 1 deletion Classes/GHUser.h
@@ -1,7 +1,7 @@
#import "GHResource.h"


@class GHUsers, GHOrganizations, GHRepositories, GHRepository, GHEvents, GHGists, GHGist;
@class GHUsers, GHOrganizations, GHNotifications, GHRepositories, GHRepository, GHEvents, GHGists, GHGist;

@interface GHUser : GHResource
@property(nonatomic,strong)NSString *name;
Expand All @@ -13,6 +13,7 @@
@property(nonatomic,strong)NSURL *blogURL;
@property(nonatomic,strong)NSURL *htmlURL;
@property(nonatomic,strong)UIImage *gravatar;
@property(nonatomic,strong)GHNotifications *notifications;
@property(nonatomic,strong)GHOrganizations *organizations;
@property(nonatomic,strong)GHRepositories *repositories;
@property(nonatomic,strong)GHRepositories *starredRepositories;
Expand Down
1 change: 1 addition & 0 deletions Classes/GHUser.m
Expand Up @@ -7,6 +7,7 @@
#import "GHGist.h"
#import "GHGists.h"
#import "GHResource.h"
#import "GHNotifications.h"
#import "IOCAvatarLoader.h"
#import "IOCAvatarCache.h"
#import "NSString+Extensions.h"
Expand Down

0 comments on commit 06ffbb5

Please sign in to comment.