From 9d78e7d654c51c22ce4be87efef5bc1f8d5a2d08 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Thu, 31 Oct 2019 16:16:48 +0300 Subject: Fix authentication --- Juick.xcodeproj/project.pbxproj | 12 +- Juick/API.h | 39 +++ Juick/API.m | 314 ++++++++++++++++++++++ Juick/APIClient.h | 36 --- Juick/APIClient.m | 312 --------------------- Juick/AppDelegate.h | 5 +- Juick/AppDelegate.m | 15 +- Juick/Main.storyboard | 113 ++++---- Juick/Model/Message.m | 1 - Juick/Model/User.h | 1 + Juick/Model/User.m | 2 +- Juick/Supporting Files/Juick-Prefix.pch | 3 +- Juick/Supporting Files/main.m | 2 - Juick/ViewControllers/ChatViewController.m | 6 +- Juick/ViewControllers/DialogsViewController.m | 4 +- Juick/ViewControllers/DiscoverViewController.m | 3 +- Juick/ViewControllers/DiscussionsController.m | 5 +- Juick/ViewControllers/FeedViewController.m | 16 +- Juick/ViewControllers/JuickNavigationController.h | 4 +- Juick/ViewControllers/JuickNavigationController.m | 35 ++- Juick/ViewControllers/LoginViewController.h | 7 +- Juick/ViewControllers/LoginViewController.m | 77 ++++-- Juick/ViewControllers/MessagesViewController.m | 8 +- Juick/ViewControllers/NewPostViewController.m | 8 +- Juick/ViewControllers/ThreadViewController.m | 8 +- Juick/Views/BubbleMessageCell.m | 3 +- Juick/Views/ContentLoadingCell.xib | 6 +- Juick/Views/ConversationCell.m | 3 +- Juick/Views/MessageCell.m | 7 +- Juick/Views/MessageInputView.xib | 6 +- 30 files changed, 557 insertions(+), 504 deletions(-) create mode 100644 Juick/API.h create mode 100644 Juick/API.m delete mode 100644 Juick/APIClient.h delete mode 100644 Juick/APIClient.m diff --git a/Juick.xcodeproj/project.pbxproj b/Juick.xcodeproj/project.pbxproj index f6b10ec..c43b8fd 100644 --- a/Juick.xcodeproj/project.pbxproj +++ b/Juick.xcodeproj/project.pbxproj @@ -62,7 +62,7 @@ 77E35A82189A5B5A00B2D216 /* LoginViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 77E35A81189A5B5A00B2D216 /* LoginViewController.m */; }; 77E61A5B1FD467FC00B4E304 /* QuoteView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 77E61A5A1FD467FC00B4E304 /* QuoteView.xib */; }; 77E61A5E1FD4682B00B4E304 /* QuoteView.m in Sources */ = {isa = PBXBuildFile; fileRef = 77E61A5D1FD4682B00B4E304 /* QuoteView.m */; }; - 77FCADDF1D6A50DA00CBA649 /* APIClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 77FCADDE1D6A50DA00CBA649 /* APIClient.m */; }; + 77FCADDF1D6A50DA00CBA649 /* API.m in Sources */ = {isa = PBXBuildFile; fileRef = 77FCADDE1D6A50DA00CBA649 /* API.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -187,8 +187,8 @@ 77E61A5A1FD467FC00B4E304 /* QuoteView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = QuoteView.xib; sourceTree = ""; }; 77E61A5C1FD4682B00B4E304 /* QuoteView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QuoteView.h; sourceTree = ""; }; 77E61A5D1FD4682B00B4E304 /* QuoteView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QuoteView.m; sourceTree = ""; }; - 77FCADDE1D6A50DA00CBA649 /* APIClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = APIClient.m; sourceTree = ""; }; - 77FCADE01D6A50EC00CBA649 /* APIClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = APIClient.h; sourceTree = ""; }; + 77FCADDE1D6A50DA00CBA649 /* API.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = API.m; sourceTree = ""; }; + 77FCADE01D6A50EC00CBA649 /* API.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = API.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -269,8 +269,8 @@ 77317BB2181BBE8500D60005 /* Supporting Files */, 774DD45F1D735E0300C7F290 /* ViewControllers */, 77FFC0151D5FD13C003BD81A /* Views */, - 77FCADE01D6A50EC00CBA649 /* APIClient.h */, - 77FCADDE1D6A50DA00CBA649 /* APIClient.m */, + 77FCADE01D6A50EC00CBA649 /* API.h */, + 77FCADDE1D6A50DA00CBA649 /* API.m */, 77317BBA181BBE8500D60005 /* AppDelegate.h */, 77317BBB181BBE8500D60005 /* AppDelegate.m */, 77317BC6181BBE8500D60005 /* Images.xcassets */, @@ -549,7 +549,7 @@ 77E61A5E1FD4682B00B4E304 /* QuoteView.m in Sources */, 77317BBC181BBE8500D60005 /* AppDelegate.m in Sources */, 776C41C11FD3FF6E0063B82E /* FeedViewController.m in Sources */, - 77FCADDF1D6A50DA00CBA649 /* APIClient.m in Sources */, + 77FCADDF1D6A50DA00CBA649 /* API.m in Sources */, 77A0954A181F1F25002BDECD /* Message.m in Sources */, 773E63A0204BDF16008B8F8D /* ChatViewController.m in Sources */, 773E639D204BD0F2008B8F8D /* Chat.m in Sources */, diff --git a/Juick/API.h b/Juick/API.h new file mode 100644 index 0000000..fcf9fb1 --- /dev/null +++ b/Juick/API.h @@ -0,0 +1,39 @@ +// +// APIClient.h +// Juick +// +// Created by Vitaly Takmazov on 22/08/16. +// Copyright © 2016 com.juick. All rights reserved. +// +#import +#import "Message.h" +#import "DeviceRegistration.h" + +@protocol JuickAuthorizationDelegate +-(void) unauthorized; +@end + +@interface API : NSObject + +@property (nonatomic, strong) id delegate; + +-(void) pullNextFromPath:(NSString *)path params:(NSDictionary *)params callback:(void(^)(NSArray*, NSError *))callback; +-(void) postMessage:(NSString *)text result:(void(^)(Message *, NSError *))callback; +-(void) postReplyToThread:(NSNumber *)mid inReplyTo:(NSNumber *)rid text:(NSString *)text result:(void(^)(Message *, NSError *))callback; +-(void) postPMToUser:(NSString *)uname text:(NSString *)text result:(void(^)(NSError *))callback; +-(void) fetchChats:(void(^)(NSArray *, NSError *))callback; +-(void) fetchChatWithUser:(NSString *)uname callback:(void(^)(NSArray *, NSError *))callback; +-(void) authenticateWithUser:(NSString *)username password:(NSString *)password callback:(void(^)(BOOL))completed; +-(void) signout; +-(void) me:(void(^)(User *, NSError *))callback; +-(void) refreshDeviceRegistration:(DeviceRegistration *)registrationData callback:(void(^)(BOOL))callback; +-(void) getUserByName:(NSString *) name callback:(void(^)(User *))callback; +-(void) fetchImageWithURL:(NSURL *) url callback:(void(^)(NSData *))callback; +@property (nonatomic, strong) User * currentUser; + ++(NSString *) messagesUrl; ++(NSString *) threadUrl; ++(NSString *) feedUrl; ++(NSString *) discussionsUrl; + +@end diff --git a/Juick/API.m b/Juick/API.m new file mode 100644 index 0000000..4f57dc6 --- /dev/null +++ b/Juick/API.m @@ -0,0 +1,314 @@ +// +// API.m +// Juick +// +// Created by Vitaly Takmazov on 22/08/16. +// Copyright © 2016 com.juick. All rights reserved. +// +#import "Message.h" +#import "Chat.h" +#import "API.h" + +@interface API () +@property(nonatomic, strong) NSOperationQueue *backgroundQueue; +@property(nonatomic, strong) NSURL *baseURL; +@property(nonatomic, strong) NSURLSession *urlSession; + +-(void) get:(NSString *)path params:(NSDictionary *)params callback:(void(^)(NSDictionary *, NSError *))callback; +-(NSString *) authorizationHeader; + +@end + +@implementation API + +-(id)init { + if (self = [super init]) { + NSString *baseURLString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"base_url"]; + NSLog(@"Initializing with %@ base URL", baseURLString); + self.baseURL = [NSURL URLWithString:baseURLString]; + self.backgroundQueue = [NSOperationQueue new]; + self.urlSession = [NSURLSession sharedSession]; + } + return self; +} + +- (NSString *) authorizationHeader { + NSString *token = [[NSUserDefaults standardUserDefaults] stringForKey:@"token"]; + if (token) { + return [NSString stringWithFormat:@"Juick %@", token]; + } + return nil; +} + +- (void)authenticateWithUser:(NSString *)username password:(NSString *)password callback:(void(^)(BOOL))completed { + NSURL *url = [NSURL URLWithString:@"me"relativeToURL:self.baseURL]; + NSString *basicAuthString = [NSString stringWithFormat:@"Basic %@", [[[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]]; + + [self fetchDataWithURL:url data:nil boundary:nil authorizationHeader:basicAuthString callback:^(NSData *response, NSError *err) { + if (!err) { + NSError *jsonError; + NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:response options:0 error:&jsonError]; + if (!jsonError) { + User *me = [User fromJSON:jsonData]; + [[NSUserDefaults standardUserDefaults] setValue:me.token forKeyPath:@"token"]; + self.currentUser = me; + completed(YES); + } + } + completed(NO); + }]; +} + +- (void)signout { + self.currentUser = nil; + [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"token"]; +} + +-(void) pullNextFromPath:(NSString *)path params:(NSDictionary *) params callback:(void(^)(NSArray *, NSError *))callback { + [self get:path params:params callback:^(NSDictionary *response, NSError *error) { + NSMutableArray *messages = [NSMutableArray new]; + if (!error) { + [((NSArray *)response) enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + [messages addObject:[Message fromJSON:obj]]; + }]; + } + callback(messages, error); + }]; +} + +- (void)me:(void (^)(User *, NSError *error))callback { + [self get:@"me" params:nil callback:^(NSDictionary *response, NSError *err) { + User *visitor; + if (!err) { + visitor = [User fromJSON:response]; + self.currentUser = visitor; + } + callback(visitor, err); + }]; +} + +-(void) postMessage:(NSString *)text result:(void (^)(Message *, NSError *))callback { + [self post:@"post" params:@{ + @"body": text + } callback:^(NSDictionary *response, NSError *err) { + Message *result; + if (!err) { + result = [Message fromJSON:response[@"newMessage"]]; + } + callback(result, err); + }]; +} + +-(void) postReplyToThread:(NSNumber *)mid inReplyTo:(NSNumber *)rid text:(NSString *)text result:(void(^)(Message *, NSError *))callback { + [self post:@"comment" params:@{ + @"mid": mid, + @"rid": [NSString stringWithFormat:@"%ld", [rid integerValue]], + @"body": text + } callback:^(NSDictionary *response, NSError *err) { + Message *reply; + if (!err) { + NSLog(@"Success!"); + reply = [Message fromJSON:response]; + } + callback(reply, err); + }]; +} + +-(void) postPMToUser:(NSString *)uname text:(NSString *)text result:(void (^)(NSError *))callback { + [self.backgroundQueue addOperationWithBlock:^{ + [self post:@"pm" params:@{ + @"uname": uname, + @"body": text + } callback:^(NSDictionary *response, NSError *err) { + callback(err); + }]; + }]; +} + +-(void) fetchChats:(void (^)(NSArray *, NSError *))callback { + [self get:@"groups_pms" params:nil callback:^(NSDictionary *response, NSError *err) { + NSMutableArray *groups = [NSMutableArray new]; + NSArray *pms = [response objectForKey:@"pms"]; + [pms enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + [groups addObject:[Chat fromJSON:obj]]; + }]; + callback(groups, err); + }]; +} + +-(void) fetchChatWithUser:(NSString *)uname callback:(void (^)(NSArray *, NSError *))callback { + NSDictionary *params = @{@"uname": uname}; + [self get:@"pm" params:params callback:^(NSDictionary *response, NSError *err) { + NSMutableArray *messages = [NSMutableArray new]; + if (!err) { + NSArray *messagesList = (NSArray *)response; + [messagesList enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + [messages addObject:[Message fromJSON:obj]]; + }]; + } + callback(messages, err); + }]; +} + +-(void) authenticate:(void (^)(User *user, NSError *error))callback { + [self get:@"me" params:nil callback:^(NSDictionary *response, NSError *error) { + User *user; + if (!error) { + user = [User fromJSON:response]; + } + callback(user, error); + }]; +} + +-(void) getUserByName:(NSString *) name callback:(void(^)(User *))callback { + NSDictionary *params = @{@"uname": name}; + [self get:@"users" params:params callback:^(NSDictionary *response, NSError *err) { + if (!err) { + callback([User fromJSON:[(NSArray *)response firstObject]]); + } else { + callback(nil); + } + }]; +} + +-(void) refreshDeviceRegistration:(DeviceRegistration *)registrationData callback:(void (^)(BOOL))callback { + NSURL *notificationsUrl = [NSURL URLWithString:@"notifications" relativeToURL:self.baseURL]; + NSError *error; + NSDictionary *token = [registrationData toJSON]; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:@[token] options:kNilOptions error:&error]; + [self fetchDataWithURL:notificationsUrl data:jsonData boundary:nil authorizationHeader:self.authorizationHeader callback:^(NSData *response, NSError *err) { + callback(!err); + }]; +} + +-(void) fetchDataWithURL:(NSURL *) url data:(NSData *)postData boundary:(NSString *)boundary authorizationHeader:(NSString *)header callback:(void(^)(NSData *, NSError *))callback { + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + if (header) { + [request addValue:header forHTTPHeaderField:@"Authorization"]; + } + if (boundary) { + request.HTTPMethod = @"POST"; + request.HTTPBody = postData; + NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]; + [request setValue:contentType forHTTPHeaderField: @"Content-Type"]; + } else if (postData) { + request.HTTPMethod = @"PUT"; + request.HTTPBody = postData; + [request setValue:@"application/json" forHTTPHeaderField: @"Content-Type"]; + } else { + request.HTTPMethod = @"GET"; + } + + NSURLSessionDataTask *task = [self.urlSession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { + if (error) { + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + callback(nil, error); + }]; + } else { + NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode; + if (!response || statusCode != 200) { + NSURL *url = request.URL; + NSError *err = [NSError errorWithDomain:@"JuickErrorDomain" + code:statusCode + userInfo:@{@"url": url.absoluteString}]; + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + callback(nil, err); + }]; + } else { + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + callback(data, error); + }]; + } + } + }]; + [self.backgroundQueue addOperationWithBlock:^{ + [task resume]; + }]; +} + +-(NSURL *) url:(NSURL *)url withParameters:(NSDictionary *)params { + NSURLComponents *parametersUrl = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:YES]; + NSMutableArray *items = [NSMutableArray array]; + for (NSString * key in params) { + NSString *value = [NSString stringWithFormat:@"%@", params[key]]; + [items addObject:[NSURLQueryItem queryItemWithName:key value:value]]; + } + [parametersUrl setQueryItems:items]; + return [parametersUrl URL]; +} + +-(NSData *) multipartData:(NSDictionary *)params withBoundary:(NSString *)boundary { + NSMutableData *body = [NSMutableData data]; + for (NSString *key in params) { + [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; + [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=%@\r\n\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]]; + [body appendData:[[NSString stringWithFormat:@"%@\r\n", params[key]] dataUsingEncoding:NSUTF8StringEncoding]]; + } + [body appendData:[[NSString stringWithFormat:@"--%@--", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; + return body; +} + +-(void) get:(NSString *) path params:(NSDictionary *)params callback:(void(^)(NSDictionary *, NSError *))callback { + NSURL *url = [NSURL URLWithString:path relativeToURL:self.baseURL]; + NSURL *requestUrl = params ? [self url:url withParameters:params] : url; + [self fetchDataWithURL:requestUrl data:nil boundary:nil authorizationHeader:self.authorizationHeader callback:^(NSData *data, NSError *err) { + if (err) { + callback(nil, err); + } else { + NSError *jsonError; + NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; + if (jsonError) { + callback(nil, jsonError); + } else { + callback(jsonData, nil); + } + } + }]; +} + +-(void) post:(NSString *) path params:(NSDictionary *)params callback:(void(^)(NSDictionary *, NSError *))callback { + NSURL *url = [NSURL URLWithString:path relativeToURL:self.baseURL]; + NSString *boundary = [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]]; + [self fetchDataWithURL:url data:[self multipartData:params withBoundary:boundary] boundary:boundary + authorizationHeader:self.authorizationHeader callback:^(NSData *data, NSError *err) { + if (!err) { + NSError *jsonError; + NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; + if (jsonError) { + callback(nil, jsonError); + } else { + callback(jsonData, nil); + } + } else { + callback(nil, err); + } + }]; +} + +-(void) fetchImageWithURL:(NSURL *) url callback:(void(^)(NSData *))callback { + [self fetchDataWithURL:url data:nil boundary:nil authorizationHeader:self.authorizationHeader callback:^(NSData *data, NSError *err) { + if (err) { + callback(nil); + } else { + callback(data); + } + }]; +} + ++(NSString *) messagesUrl { + return @"messages"; +} + ++(NSString *) discussionsUrl { + return @"messages/discussions"; +} + ++(NSString *) threadUrl { + return @"thread"; +} + ++(NSString *) feedUrl { + return @"home"; +} + +@end diff --git a/Juick/APIClient.h b/Juick/APIClient.h deleted file mode 100644 index d8317a4..0000000 --- a/Juick/APIClient.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// APIClient.h -// Juick -// -// Created by Vitaly Takmazov on 22/08/16. -// Copyright © 2016 com.juick. All rights reserved. -// -#import -#import "Message.h" -#import "DeviceRegistration.h" - -@interface APIClient : NSObject - -@property (nonatomic, strong) NSDateFormatter *dateFormatter; -@property (nonatomic, strong) NSURLCredential *credential; - -+(APIClient *) sharedClient; - --(void) pullNextFromPath:(NSString *)path params:(NSDictionary *)params callback:(void(^)(NSArray*, NSError *))callback; --(void) postMessage:(NSString *)text result:(void(^)(Message *, NSError *))callback; --(void) postReplyToThread:(NSNumber *)mid inReplyTo:(NSNumber *)rid text:(NSString *)text result:(void(^)(Message *, NSError *))callback; --(void) postPMToUser:(NSString *)uname text:(NSString *)text result:(void(^)(NSError *))callback; --(void) fetchChats:(void(^)(NSArray *, NSError *))callback; --(void) fetchChatWithUser:(NSString *)uname callback:(void(^)(NSArray *, NSError *))callback; --(void) authenticate:(void(^)(User *, NSError *))callback; --(void) refreshDeviceRegistration:(DeviceRegistration *)registrationData callback:(void(^)(BOOL))callback; --(void) getUserByName:(NSString *) name callback:(void(^)(User *))callback; --(BOOL) isAuthenticated; --(void) fetchImageWithURL:(NSURL *) url callback:(void(^)(NSData *))callback; - -+(NSString *) messagesUrl; -+(NSString *) threadUrl; -+(NSString *) feedUrl; -+(NSString *) discussionsUrl; - -@end diff --git a/Juick/APIClient.m b/Juick/APIClient.m deleted file mode 100644 index e988edc..0000000 --- a/Juick/APIClient.m +++ /dev/null @@ -1,312 +0,0 @@ -// -// APIClient.m -// Juick -// -// Created by Vitaly Takmazov on 22/08/16. -// Copyright © 2016 com.juick. All rights reserved. -// -#import "APIClient.h" -#import "Message.h" -#import "Chat.h" - -@interface APIClient () -@property(nonatomic, strong) NSOperationQueue *backgroundQueue; -@property(nonatomic, strong) NSURL *baseURL; -@property(nonatomic, strong) NSURLSession *urlSession; -@property(nonatomic, strong) NSURLProtectionSpace *apiProtectionSpace; --(void) get:(NSString *)path params:(NSDictionary *)params callback:(void(^)(NSDictionary *, NSError *))callback; -@end - -@implementation APIClient - -@synthesize credential = _credential; - -+(APIClient *) sharedClient { - static APIClient *sharedAPIClient = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedAPIClient = [[self alloc] init]; - }); - return sharedAPIClient; -} - --(id)init { - if (self = [super init]) { - NSString *baseURLString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"base_url"]; - NSLog(@"Initializing with %@ base URL", baseURLString); - self.baseURL = [NSURL URLWithString:baseURLString]; - self.apiProtectionSpace = [[NSURLProtectionSpace alloc] initWithHost:self.baseURL.host port:0 protocol:NSURLProtectionSpaceHTTPS realm:@"Juick" authenticationMethod:NSURLAuthenticationMethodHTTPBasic]; - self.backgroundQueue = [NSOperationQueue new]; - self.urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:self.backgroundQueue]; - - self.dateFormatter = [[NSDateFormatter alloc] init]; - self.dateFormatter.dateFormat = @"yyyy-MM-dd HH:mm:ss"; - [self.dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]]; - } - return self; -} - --(void) pullNextFromPath:(NSString *)path params:(NSDictionary *) params callback:(void(^)(NSArray *, NSError *))callback { - [self get:path params:params callback:^(NSDictionary *response, NSError *error) { - NSMutableArray *messages = [NSMutableArray new]; - if (!error) { - [((NSArray *)response) enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - [messages addObject:[Message fromJSON:obj]]; - }]; - } - callback(messages, error); - }]; -} - --(void) postMessage:(NSString *)text result:(void (^)(Message *, NSError *))callback { - [self post:@"post" params:@{ - @"body": text - } callback:^(NSDictionary *response, NSError *err) { - Message *result; - if (!err) { - result = [Message fromJSON:response[@"newMessage"]]; - } - callback(result, err); - }]; -} - --(void) postReplyToThread:(NSNumber *)mid inReplyTo:(NSNumber *)rid text:(NSString *)text result:(void(^)(Message *, NSError *))callback { - [self post:@"comment" params:@{ - @"mid": mid, - @"rid": [NSString stringWithFormat:@"%ld", [rid integerValue]], - @"body": text - } callback:^(NSDictionary *response, NSError *err) { - Message *reply; - if (!err) { - NSLog(@"Success!"); - reply = [Message fromJSON:response]; - } - callback(reply, err); - }]; -} - --(void) postPMToUser:(NSString *)uname text:(NSString *)text result:(void (^)(NSError *))callback { - [self.backgroundQueue addOperationWithBlock:^{ - [self post:@"pm" params:@{ - @"uname": uname, - @"body": text - } callback:^(NSDictionary *response, NSError *err) { - callback(err); - }]; - }]; -} - --(void) fetchChats:(void (^)(NSArray *, NSError *))callback { - [self get:@"groups_pms" params:nil callback:^(NSDictionary *response, NSError *err) { - NSMutableArray *groups = [NSMutableArray new]; - NSArray *pms = [response objectForKey:@"pms"]; - [pms enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - [groups addObject:[Chat fromJSON:obj]]; - }]; - callback(groups, err); - }]; -} - --(void) fetchChatWithUser:(NSString *)uname callback:(void (^)(NSArray *, NSError *))callback { - NSDictionary *params = @{@"uname": uname}; - [self get:@"pm" params:params callback:^(NSDictionary *response, NSError *err) { - NSMutableArray *messages = [NSMutableArray new]; - if (!err) { - NSArray *messagesList = (NSArray *)response; - [messagesList enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - [messages addObject:[Message fromJSON:obj]]; - }]; - } - callback(messages, err); - }]; -} - --(void) authenticate:(void (^)(User *user, NSError *error))callback { - [self get:@"me" params:nil callback:^(NSDictionary *response, NSError *error) { - User *user; - if (!error) { - user = [User fromJSON:response]; - } - callback(user, error); - }]; -} - --(void) getUserByName:(NSString *) name callback:(void(^)(User *))callback { - NSDictionary *params = @{@"uname": name}; - [self get:@"users" params:params callback:^(NSDictionary *response, NSError *err) { - if (!err) { - callback([User fromJSON:[(NSArray *)response firstObject]]); - } else { - callback(nil); - } - }]; -} - --(void) refreshDeviceRegistration:(DeviceRegistration *)registrationData callback:(void (^)(BOOL))callback { - NSURL *notificationsUrl = [NSURL URLWithString:@"notifications" relativeToURL:self.baseURL]; - NSError *error; - NSDictionary *token = [registrationData toJSON]; - NSData *jsonData = [NSJSONSerialization dataWithJSONObject:@[token] options:kNilOptions error:&error]; - [self fetchDataWithURL:notificationsUrl data:jsonData boundary:nil callback:^(NSData *response, NSError *err) { - callback(!err); - }]; -} - - -- (NSURLCredential *)credential { - NSDictionary *credentials = [[NSURLCredentialStorage sharedCredentialStorage] credentialsForProtectionSpace:self.apiProtectionSpace]; - return [credentials.objectEnumerator nextObject]; -} - -- (void)setCredential:(NSURLCredential *)credential { - if (credential) { - [[NSURLCredentialStorage sharedCredentialStorage] removeCredential:_credential forProtectionSpace:self.apiProtectionSpace options:@{NSURLCredentialStorageRemoveSynchronizableCredentials:@YES}]; - [[NSURLCredentialStorage sharedCredentialStorage] setCredential:credential forProtectionSpace:self.apiProtectionSpace]; - } else { - [[NSURLCredentialStorage sharedCredentialStorage] removeCredential:_credential forProtectionSpace:self.apiProtectionSpace]; - } -} - - --(BOOL) isAuthenticated { - NSString *password = self.credential.password; - return password != nil; -} - -- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler { - if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic) { - if (self.isAuthenticated) { - completionHandler(NSURLSessionAuthChallengeUseCredential, self.credential); - return; - } - } - completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); -} - --(void) fetchDataWithURL:(NSURL *) url data:(NSData *)postData boundary:(NSString *)boundary callback:(void(^)(NSData *, NSError *))callback { - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; - if (boundary) { - request.HTTPMethod = @"POST"; - request.HTTPBody = postData; - NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]; - [request setValue:contentType forHTTPHeaderField: @"Content-Type"]; - } else if (postData) { - request.HTTPMethod = @"PUT"; - request.HTTPBody = postData; - [request setValue:@"application/json" forHTTPHeaderField: @"Content-Type"]; - } else { - request.HTTPMethod = @"GET"; - } - - NSURLSessionDataTask *task = [self.urlSession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { - if (error) { - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(nil, error); - }]; - } else { - NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode; - if (!response || statusCode != 200) { - NSURL *url = request.URL; - NSError *err = [NSError errorWithDomain:@"JuickErrorDomain" - code:statusCode - userInfo:@{@"url": url.absoluteString}]; - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(nil, err); - }]; - } else { - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(data, error); - }]; - } - } - }]; - [self.backgroundQueue addOperationWithBlock:^{ - [task resume]; - }]; -} - --(NSURL *) url:(NSURL *)url withParameters:(NSDictionary *)params { - NSURLComponents *parametersUrl = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:YES]; - NSMutableArray *items = [NSMutableArray array]; - for (NSString * key in params) { - NSString *value = [NSString stringWithFormat:@"%@", params[key]]; - [items addObject:[NSURLQueryItem queryItemWithName:key value:value]]; - } - [parametersUrl setQueryItems:items]; - return [parametersUrl URL]; -} - --(NSData *) multipartData:(NSDictionary *)params withBoundary:(NSString *)boundary { - NSMutableData *body = [NSMutableData data]; - for (NSString *key in params) { - [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; - [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=%@\r\n\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]]; - [body appendData:[[NSString stringWithFormat:@"%@\r\n", params[key]] dataUsingEncoding:NSUTF8StringEncoding]]; - } - [body appendData:[[NSString stringWithFormat:@"--%@--", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; - return body; -} - --(void) get:(NSString *) path params:(NSDictionary *)params callback:(void(^)(NSDictionary *, NSError *))callback { - NSURL *url = [NSURL URLWithString:path relativeToURL:self.baseURL]; - NSURL *requestUrl = params ? [self url:url withParameters:params] : url; - [self fetchDataWithURL:requestUrl data:nil boundary:nil callback:^(NSData *data, NSError *err) { - if (err) { - callback(nil, err); - } else { - NSError *jsonError; - NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; - if (jsonError) { - callback(nil, jsonError); - } else { - callback(jsonData, nil); - } - } - }]; -} - --(void) post:(NSString *) path params:(NSDictionary *)params callback:(void(^)(NSDictionary *, NSError *))callback { - NSURL *url = [NSURL URLWithString:path relativeToURL:self.baseURL]; - NSString *boundary = [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]]; - [self fetchDataWithURL:url data:[self multipartData:params withBoundary:boundary] boundary:boundary callback:^(NSData *data, NSError *err) { - if (!err) { - NSError *jsonError; - NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; - if (jsonError) { - callback(nil, jsonError); - } else { - callback(jsonData, nil); - } - } else { - callback(nil, err); - } - }]; -} - --(void) fetchImageWithURL:(NSURL *) url callback:(void(^)(NSData *))callback { - [self fetchDataWithURL:url data:nil boundary:nil callback:^(NSData *data, NSError *err) { - if (err) { - callback(nil); - } else { - callback(data); - } - }]; -} - -+(NSString *) messagesUrl { - return @"messages"; -} - -+(NSString *) discussionsUrl { - return @"messages/discussions"; -} - -+(NSString *) threadUrl { - return @"thread"; -} - -+(NSString *) feedUrl { - return @"home"; -} - -@end diff --git a/Juick/AppDelegate.h b/Juick/AppDelegate.h index 8b89131..6c277a9 100644 --- a/Juick/AppDelegate.h +++ b/Juick/AppDelegate.h @@ -9,10 +9,11 @@ @import UIKit; #import "ThreadViewController.h" #import "User.h" +#import "API.h" extern NSString * const UserUpdatedNotificationName; -@interface AppDelegate : UIResponder +@interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @@ -30,6 +31,8 @@ extern NSString * const UserUpdatedNotificationName; @property (strong, nonatomic) NSString *pushedUname; @property (strong, nonatomic) NSNumber *pushedReplyId; +@property (strong, nonatomic) API *api; +@property (strong, nonatomic) NSDateFormatter *sharedDateFormatter; @end diff --git a/Juick/AppDelegate.m b/Juick/AppDelegate.m index d4c2304..47c18ef 100644 --- a/Juick/AppDelegate.m +++ b/Juick/AppDelegate.m @@ -6,11 +6,9 @@ // Copyright (c) 2013 com.juick. All rights reserved. // -#import "AppDelegate.h" #import "MessagesViewController.h" #import "LoginViewController.h" -#import "APIClient.h" #import "Message.h" #import "User.h" #import "DeviceRegistration.h" @@ -28,6 +26,10 @@ NSString * const UserUpdatedNotificationName = @"UserUpdated"; - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + self.api = [API new]; + self.sharedDateFormatter = [NSDateFormatter new]; + self.sharedDateFormatter.dateFormat = @"yyyy-MM-dd HH:mm:ss"; + [self.sharedDateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]]; [[UINavigationBar appearance] setTintColor:[UIColor colorNamed:@"Title"]]; [[UINavigationBar appearance] setBarTintColor:[UIColor colorNamed:@"Background"]]; [[UINavigationBar appearance] setTitleTextAttributes:@{NSForegroundColorAttributeName: [UIColor colorNamed:@"Muted"]}]; @@ -36,8 +38,9 @@ NSString * const UserUpdatedNotificationName = @"UserUpdated"; [[UITabBar appearance] setTintColor:[UIColor colorNamed:@"Title"]]; [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleDefault; - +#if !TARGET_IPHONE_SIMULATOR [self registerForRemoteNotifications]; +#endif NSDictionary *userInfo = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]; if (userInfo) { [self parseNotificationPayload:userInfo]; @@ -63,7 +66,7 @@ NSString * const UserUpdatedNotificationName = @"UserUpdated"; DeviceRegistration *registration = [DeviceRegistration new]; registration.type = @"apns"; registration.token = token; - [[APIClient sharedClient] refreshDeviceRegistration:registration callback:^(BOOL success) { + [self.api refreshDeviceRegistration:registration callback:^(BOOL success) { if (success) { NSLog(@"successfully refreshed registration with %@", token); } @@ -156,4 +159,8 @@ NSString * const UserUpdatedNotificationName = @"UserUpdated"; [[self navigator] performSegueWithIdentifier:@"loginSegue" sender:vc]; } +- (void)unauthorized { + [self presentLoginView:self.window.rootViewController]; +} + @end diff --git a/Juick/Main.storyboard b/Juick/Main.storyboard index 212e384..bfa1a3f 100644 --- a/Juick/Main.storyboard +++ b/Juick/Main.storyboard @@ -1,9 +1,10 @@ - + - + + @@ -98,62 +99,63 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - - + + + + + @@ -360,6 +362,11 @@ + + + + + @@ -441,5 +448,11 @@ + + + + + + diff --git a/Juick/Model/Message.m b/Juick/Model/Message.m index f5c2640..d39be54 100644 --- a/Juick/Model/Message.m +++ b/Juick/Model/Message.m @@ -7,7 +7,6 @@ // #import "Message.h" -#import "AppDelegate.h" @implementation Message diff --git a/Juick/Model/User.h b/Juick/Model/User.h index cbf4e46..344626e 100644 --- a/Juick/Model/User.h +++ b/Juick/Model/User.h @@ -12,6 +12,7 @@ @property (nonatomic, strong) NSString *uname; @property (nonatomic, strong) NSString *uid; @property (nonatomic, strong) NSString *avatar; +@property (nonatomic, strong) NSString *token; @property (nonatomic) NSInteger unreadCount; + (User *) fromJSON:(NSDictionary *)jsonData; diff --git a/Juick/Model/User.m b/Juick/Model/User.m index 94b77c4..a0c34f9 100644 --- a/Juick/Model/User.m +++ b/Juick/Model/User.m @@ -7,7 +7,6 @@ // #import "User.h" -#import "APIClient.h" @implementation User @@ -16,6 +15,7 @@ user.uid = jsonData[@"uid"]; user.uname = jsonData[@"uname"]; user.avatar = jsonData[@"avatar"]; + user.token = jsonData[@"hash"]; user.unreadCount = [jsonData[@"messagesCount"] integerValue]; return user; } diff --git a/Juick/Supporting Files/Juick-Prefix.pch b/Juick/Supporting Files/Juick-Prefix.pch index 7278b52..957a052 100644 --- a/Juick/Supporting Files/Juick-Prefix.pch +++ b/Juick/Supporting Files/Juick-Prefix.pch @@ -14,9 +14,10 @@ #import #import #import - #import + #import #import #import "UIImage+Utils.h" #import "UIView+Shimmer.h" + #import "AppDelegate.h" #endif diff --git a/Juick/Supporting Files/main.m b/Juick/Supporting Files/main.m index 67b222f..8e01106 100644 --- a/Juick/Supporting Files/main.m +++ b/Juick/Supporting Files/main.m @@ -8,8 +8,6 @@ #import -#import "AppDelegate.h" - int main(int argc, char * argv[]) { @autoreleasepool { diff --git a/Juick/ViewControllers/ChatViewController.m b/Juick/ViewControllers/ChatViewController.m index 2612719..695d7ec 100644 --- a/Juick/ViewControllers/ChatViewController.m +++ b/Juick/ViewControllers/ChatViewController.m @@ -8,7 +8,6 @@ #import "ChatViewController.h" #import "BubbleMessageCell.h" -#import "APIClient.h" #import "MessageInputView.h" @@ -29,7 +28,6 @@ self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive; self.tableView.allowsSelection = NO; self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentAutomatic; - self.me = [APIClient sharedClient].credential.user; [self reloadChat]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil]; self.refreshControl = [UIRefreshControl new]; @@ -40,7 +38,7 @@ -(void) reloadChat { self.messages = [NSMutableArray array]; [self.tableView reloadData]; - [[APIClient sharedClient] fetchChatWithUser:self.uname callback:^(NSArray *messages, NSError *err) { + [[AppDelegate shared].api fetchChatWithUser:self.uname callback:^(NSArray *messages, NSError *err) { if (err == nil) { [self.messages addObjectsFromArray:[[messages reverseObjectEnumerator] allObjects]]; NSMutableArray *indexPaths = [NSMutableArray new]; @@ -107,7 +105,7 @@ } -(void) textSent:(NSString *)text { - [[APIClient sharedClient] postPMToUser:self.uname text:text result:^(NSError *err) { + [[AppDelegate shared].api postPMToUser:self.uname text:text result:^(NSError *err) { if (!err) { NSLog(@"Success!"); [self.accessoryView becomeFirstResponder]; diff --git a/Juick/ViewControllers/DialogsViewController.m b/Juick/ViewControllers/DialogsViewController.m index 0b7f2ca..432cd53 100644 --- a/Juick/ViewControllers/DialogsViewController.m +++ b/Juick/ViewControllers/DialogsViewController.m @@ -9,8 +9,6 @@ #import "DialogsViewController.h" #import "ChatViewController.h" #import "ConversationCell.h" -#import "APIClient.h" -#import "AppDelegate.h" @implementation DialogsViewController - (void)viewDidLoad { @@ -31,7 +29,7 @@ - (void) refreshData { self.chats = [NSMutableArray array]; [self.tableView reloadData]; - [[APIClient sharedClient] fetchChats:^(NSArray *groups, NSError *err) { + [[AppDelegate shared].api fetchChats:^(NSArray *groups, NSError *err) { if (err == nil) { [self.chats addObjectsFromArray:groups]; NSMutableArray *indexPaths = [NSMutableArray new]; diff --git a/Juick/ViewControllers/DiscoverViewController.m b/Juick/ViewControllers/DiscoverViewController.m index 7bf9f74..10185ef 100644 --- a/Juick/ViewControllers/DiscoverViewController.m +++ b/Juick/ViewControllers/DiscoverViewController.m @@ -7,7 +7,6 @@ // #import "DiscoverViewController.h" -#import "APIClient.h" @interface DiscoverViewController () @@ -17,7 +16,7 @@ - (void)viewDidLoad { self.messagesDelegate = self; self.title = @"Discover"; - self.path = [APIClient messagesUrl]; + self.path = [API messagesUrl]; [self setShouldScrollToUnreadOnRefresh:NO]; [super viewDidLoad]; } diff --git a/Juick/ViewControllers/DiscussionsController.m b/Juick/ViewControllers/DiscussionsController.m index 607ac26..47ee7f2 100644 --- a/Juick/ViewControllers/DiscussionsController.m +++ b/Juick/ViewControllers/DiscussionsController.m @@ -7,7 +7,6 @@ // #import "DiscussionsController.h" -#import "APIClient.h" @interface DiscussionsController () @@ -18,7 +17,7 @@ - (void)viewDidLoad { self.messagesDelegate = self; self.title = @"Discussions"; - self.path = [APIClient discussionsUrl]; + self.path = [API discussionsUrl]; [self setShouldScrollToUnreadOnRefresh:NO]; [super viewDidLoad]; } @@ -26,7 +25,7 @@ - (void)loadMore { Message *lastMsg = [self.messages lastObject]; if (lastMsg != nil) { - NSDate *msgDate = [[APIClient sharedClient].dateFormatter dateFromString:lastMsg.timestamp]; + NSDate *msgDate = [[AppDelegate shared].sharedDateFormatter dateFromString:lastMsg.timestamp]; self.params = [@{@"to" : [NSString stringWithFormat:@"%.0f", [msgDate timeIntervalSince1970] * 1000]} mutableCopy]; [self setShouldScrollToUnreadOnRefresh:NO]; [self refreshData]; diff --git a/Juick/ViewControllers/FeedViewController.m b/Juick/ViewControllers/FeedViewController.m index da1b914..c492f2e 100644 --- a/Juick/ViewControllers/FeedViewController.m +++ b/Juick/ViewControllers/FeedViewController.m @@ -10,8 +10,6 @@ #import "ThreadViewController.h" #import "BlogViewController.h" #import "MessageCell.h" -#import "APIClient.h" -#import "AppDelegate.h" #import "LoginViewController.h" NSString * const UserNotAuthenticatedNotificationName = @"UserNotAuthenticated"; @@ -28,7 +26,7 @@ NSString * const UserNotAuthenticatedNotificationName = @"UserNotAuthenticated"; [self refreshPath]; [self setShouldScrollToUnreadOnRefresh:NO]; [super viewDidLoad]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userDidSignedIn:) name:UserSignedInNotificationName object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userDidSignedIn:) name:UserChangedNotificationName object:nil]; } -(void)avatarClicked:(NSString *)uname { @@ -46,17 +44,11 @@ NSString * const UserNotAuthenticatedNotificationName = @"UserNotAuthenticated"; } } - - -- (void)didReceiveChallenge { - -} - -(void) refreshPath { - if ([[APIClient sharedClient] isAuthenticated]) { - self.path = [APIClient feedUrl]; + if ([[AppDelegate shared].api currentUser]) { + self.path = [API feedUrl]; } else { - self.path = [APIClient messagesUrl]; + self.path = [API messagesUrl]; self.params = @{@"popular": @1}; } } diff --git a/Juick/ViewControllers/JuickNavigationController.h b/Juick/ViewControllers/JuickNavigationController.h index b508466..974c177 100644 --- a/Juick/ViewControllers/JuickNavigationController.h +++ b/Juick/ViewControllers/JuickNavigationController.h @@ -11,7 +11,9 @@ NS_ASSUME_NONNULL_BEGIN @interface JuickNavigationController : UITabBarController -- (IBAction)newMessage:(id)sender; +- (IBAction)showLoginForm:(id)sender; +- (void) refreshStatus; +@property (weak, nonatomic) IBOutlet UIBarButtonItem *leftButton; @end NS_ASSUME_NONNULL_END diff --git a/Juick/ViewControllers/JuickNavigationController.m b/Juick/ViewControllers/JuickNavigationController.m index 8673866..aaf3765 100644 --- a/Juick/ViewControllers/JuickNavigationController.m +++ b/Juick/ViewControllers/JuickNavigationController.m @@ -8,31 +8,54 @@ #import "JuickNavigationController.h" -#import "AppDelegate.h" -#import "APIClient.h" #import "MessagesViewController.h" #import "ThreadViewController.h" #import "NewPostViewController.h" #import "Message.h" +#import "LoginViewController.h" @interface JuickNavigationController () +@property(nonatomic, strong) UIButton *avatarButton; @end @implementation JuickNavigationController + +-(void) refreshStatus { + [[AppDelegate shared].api me:^(User *user, NSError *err) { + NSString *avatarUrl; + if (err || !user) { + avatarUrl = @"https://i.juick.com/av-96.png"; + } else { + avatarUrl = user.avatar; + } + [[AppDelegate shared].api fetchImageWithURL:[NSURL URLWithString:avatarUrl] callback:^(NSData *data) { + self.avatarButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [self.avatarButton addTarget:self action:@selector(showLoginForm:) forControlEvents:UIControlEventTouchUpInside]; + [self.avatarButton setImage:[UIImage imageWithImage:[UIImage imageWithData:data] fitInsideWidth:44 fitInsideHeight:44] forState:UIControlStateNormal]; + self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.avatarButton]; + }]; + }]; +} + - (void)viewDidLoad { [super viewDidLoad]; - // Do any additional setup after loading the view. + [self refreshStatus]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userDidSignedIn:) name:UserChangedNotificationName object:nil]; +} + +- (void)userDidSignedIn:(NSNotification *) notification { + [self refreshStatus]; } #pragma mark - Navigation - (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender { if ([identifier isEqualToString:@"editorSegue"]) { - if ([[APIClient sharedClient] isAuthenticated]) { + if ([[AppDelegate shared].api currentUser]) { return YES; } else { [[AppDelegate shared] presentLoginView:self]; @@ -71,7 +94,7 @@ } } -- (IBAction)newMessage:(id)sender { - [[AppDelegate shared] presentLoginView:self]; +- (IBAction)showLoginForm:(id)sender { + [self performSegueWithIdentifier:@"loginSegue" sender:self]; } @end diff --git a/Juick/ViewControllers/LoginViewController.h b/Juick/ViewControllers/LoginViewController.h index e1a4900..451a99c 100644 --- a/Juick/ViewControllers/LoginViewController.h +++ b/Juick/ViewControllers/LoginViewController.h @@ -10,11 +10,14 @@ #import "User.h" -extern NSString * const UserSignedInNotificationName; +extern NSString * const UserChangedNotificationName; @interface LoginViewController : UIViewController +@property (weak, nonatomic) IBOutlet UILabel *currentUser; @property (weak, nonatomic) IBOutlet UITextField *usernameField; @property (weak, nonatomic) IBOutlet UITextField *passwordField; -@property (weak, nonatomic) IBOutlet UIVisualEffectView *visualEffectView; +@property (weak, nonatomic) IBOutlet UIButton *signOutButton; +@property (weak, nonatomic) IBOutlet UIImageView *imageView; +- (IBAction)signoutPressed:(id)sender; @property (strong, nonatomic) IBOutlet NSLayoutConstraint *bottomConstraint; @end diff --git a/Juick/ViewControllers/LoginViewController.m b/Juick/ViewControllers/LoginViewController.m index 55099eb..a0e9b97 100644 --- a/Juick/ViewControllers/LoginViewController.m +++ b/Juick/ViewControllers/LoginViewController.m @@ -8,33 +8,32 @@ #import "LoginViewController.h" #import "User.h" -#import "AppDelegate.h" -#import "APIClient.h" -NSString * const UserSignedInNotificationName = @"UserSignedIn"; +NSString * const UserChangedNotificationName = @"UserSignedIn"; @interface LoginViewController() @property (nonatomic, assign) int paddingValue; +-(void) refreshState; @end @implementation LoginViewController - (void) awakeFromNib { [super awakeFromNib]; - self.title = @"Sign in"; [self.view setBackgroundColor:[UIColor colorNamed:@"Background"]]; - [self.visualEffectView.contentView setBackgroundColor:[UIColor colorNamed:@"Background"]]; self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelSignIn)]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:@selector(doneSignIn)]; - self.usernameField.text = [APIClient sharedClient].credential.user; - self.passwordField.text = [APIClient sharedClient].credential.password; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil]; } +- (void)viewWillAppear:(BOOL)animated { + [self refreshState]; +} + -(void) keyboardDidShow:(NSNotification *)sender { CGRect keyboardRect = [sender.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; self.bottomConstraint.constant = keyboardRect.size.height - self.view.safeAreaInsets.bottom + self.paddingValue; @@ -47,32 +46,54 @@ NSString * const UserSignedInNotificationName = @"UserSignedIn"; - (void) cancelSignIn { - CATransition* transition = [CATransition animation]; - transition.duration = 0.3; - transition.type = kCATransitionFade; - transition.subtype = kCATransitionFromTop; - - [self.navigationController.view.layer addAnimation:transition forKey:kCATransition]; - [self.navigationController dismissViewControllerAnimated:NO completion:nil]; + [self.navigationController dismissViewControllerAnimated:YES completion:nil]; } - (void) doneSignIn { - [[APIClient sharedClient] setCredential:[NSURLCredential credentialWithUser:self.usernameField.text password:self.passwordField.text persistence:NSURLCredentialPersistenceSynchronizable]]; - [[APIClient sharedClient] authenticate:^(User *user, NSError *error) { - if (user) { - [[NSNotificationCenter defaultCenter] postNotificationName:UserSignedInNotificationName object:user]; + + [[NSURLCache sharedURLCache] removeAllCachedResponses]; + [[AppDelegate shared].api authenticateWithUser:self.usernameField.text password:self.passwordField.text callback:^(BOOL success){ + if (success) { + [[NSNotificationCenter defaultCenter] postNotificationName:UserChangedNotificationName object:nil]; [[AppDelegate shared] registerForRemoteNotifications]; - CATransition* transition = [CATransition animation]; - transition.duration = 0.3; - transition.type = kCATransitionFade; - transition.subtype = kCATransitionFromTop; - - [self.navigationController.view.layer addAnimation:transition forKey:kCATransition]; - [self.navigationController dismissViewControllerAnimated:NO completion:nil]; - } else { - [User throwUnableToLogin:self error:error]; - [[APIClient sharedClient] setCredential:nil]; + [self.navigationController dismissViewControllerAnimated:YES completion:nil]; } }]; } + +- (void)refreshState { + if ([AppDelegate shared].api.currentUser) { + self.title = @"Profile"; + [self.usernameField setHidden:YES]; + [self.passwordField setHidden:YES]; + [self.currentUser setHidden:NO]; + self.currentUser.text = [AppDelegate shared].api.currentUser.uname; + __weak UIImageView *weakAttach = self.imageView; + [[AppDelegate shared].api fetchImageWithURL:[NSURL URLWithString:[AppDelegate shared].api.currentUser.avatar] callback:^(NSData *data) { + [UIView transitionWithView:weakAttach + duration:0.3 + options:UIViewAnimationOptionTransitionCrossDissolve + animations:^{ + weakAttach.image = [UIImage imageWithData:data]; + } + completion:nil]; + }]; + [self.signOutButton setHidden:NO]; + } else { + self.title = @"Sign in"; + self.imageView.image = [UIImage imageNamed:@"Splash"]; + [self.usernameField setHidden:NO]; + [self.passwordField setHidden:NO]; + [self.currentUser setHidden:YES]; + [self.signOutButton setHidden:YES]; + } +} + +- (IBAction)signoutPressed:(id)sender { + [[AppDelegate shared].api signout]; + [[NSURLCache sharedURLCache] removeAllCachedResponses]; + [self.navigationController dismissViewControllerAnimated:YES completion:^{ + [[NSNotificationCenter defaultCenter] postNotificationName:UserChangedNotificationName object:nil]; + }]; +} @end diff --git a/Juick/ViewControllers/MessagesViewController.m b/Juick/ViewControllers/MessagesViewController.m index 25a2ca5..f61d8b4 100644 --- a/Juick/ViewControllers/MessagesViewController.m +++ b/Juick/ViewControllers/MessagesViewController.m @@ -9,8 +9,6 @@ #import "MessagesViewController.h" #import "MessageCell.h" -#import "AppDelegate.h" -#import "APIClient.h" #import "Message.h" #import "NewPostViewController.h" @@ -35,20 +33,20 @@ NSString* const messageCellIdentifier = @"messageCell"; if (self.messages.count == 0) { [self.tableView reloadData]; } - [[APIClient sharedClient] pullNextFromPath:self.path params:self.params callback:^(NSArray *next, NSError *err) { + [[AppDelegate shared].api pullNextFromPath:self.path params:self.params callback:^(NSArray *next, NSError *err) { if (err) { [User throwUnableToLogin:self error:err]; return; } NSArray *newMsgs = next; if ([self isAtTop:self.params]) { - if (![self.path isEqualToString:[APIClient threadUrl]]) { + if (![self.path isEqualToString:[API threadUrl]]) { [self.messages removeAllObjects]; [self.tableView reloadData]; } } NSUInteger oldCount = [self.messages count]; - if ([self.path isEqualToString:[APIClient threadUrl]]) { + if ([self.path isEqualToString:[API threadUrl]]) { NSUInteger lastRid = [((Message *)[self.messages lastObject]).rid unsignedIntegerValue] + 1; NSUInteger count = [next count]; if (oldCount > 0) { diff --git a/Juick/ViewControllers/NewPostViewController.m b/Juick/ViewControllers/NewPostViewController.m index e4b5ac7..344e7b9 100644 --- a/Juick/ViewControllers/NewPostViewController.m +++ b/Juick/ViewControllers/NewPostViewController.m @@ -9,8 +9,6 @@ #import "NewPostViewController.h" #import "MessagesViewController.h" #import "QuoteView.h" -#import "APIClient.h" -#import "AppDelegate.h" NSString * const NewMessageNotificationName = @"NewMessage"; NSString * const ReplyPostedNotificationName = @"ReplyPosted"; @@ -47,8 +45,8 @@ NSString * const ReplyPostedNotificationName = @"ReplyPosted"; - (IBAction)sendAction:(id)sender { self.navigationItem.rightBarButtonItem.enabled = NO; if (_replyTo == nil) { - self.navigationItem.rightBarButtonItem.enabled = YES; - [[APIClient sharedClient] postMessage:self.textView.text result:^(Message *msg, NSError *err) { + [[AppDelegate shared].api postMessage:self.textView.text result:^(Message *msg, NSError *err) { + self.navigationItem.rightBarButtonItem.enabled = YES; if (!err) { [self dismissViewControllerAnimated:YES completion:^{ [[NSNotificationCenter defaultCenter] postNotificationName:NewMessageNotificationName object:msg]; @@ -60,7 +58,7 @@ NSString * const ReplyPostedNotificationName = @"ReplyPosted"; }]; } else { self.navigationItem.rightBarButtonItem.enabled = NO; - [[APIClient sharedClient] postReplyToThread:_replyTo.mid inReplyTo:_replyTo.rid text:self.textView.text result:^(Message *msg, NSError *err) { + [[AppDelegate shared].api postReplyToThread:_replyTo.mid inReplyTo:_replyTo.rid text:self.textView.text result:^(Message *msg, NSError *err) { if (!err) { [self dismissViewControllerAnimated:YES completion:^{ [[NSNotificationCenter defaultCenter] postNotificationName:ReplyPostedNotificationName object:msg]; diff --git a/Juick/ViewControllers/ThreadViewController.m b/Juick/ViewControllers/ThreadViewController.m index c345822..8ee7623 100644 --- a/Juick/ViewControllers/ThreadViewController.m +++ b/Juick/ViewControllers/ThreadViewController.m @@ -9,14 +9,12 @@ #import "ThreadViewController.h" #import "NewPostViewController.h" #import "MessageCell.h" -#import "APIClient.h" -#import "AppDelegate.h" @implementation ThreadViewController -(void) viewDidLoad { - [self setPath:[APIClient threadUrl]]; + [self setPath:[API threadUrl]]; [super viewDidLoad]; self.messagesDelegate = self; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(replyPosted:) name:ReplyPostedNotificationName object:nil]; @@ -30,10 +28,10 @@ - (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender { if ([identifier isEqualToString:@"editorSegue"]) { - if ([[APIClient sharedClient] isAuthenticated]) { + if ([[NSUserDefaults standardUserDefaults] stringForKey:@"token"]) { return YES; } else { - [[AppDelegate shared] presentLoginView:self]; + [[AppDelegate shared] unauthorized]; return NO; } } diff --git a/Juick/Views/BubbleMessageCell.m b/Juick/Views/BubbleMessageCell.m index 65d3c0c..a5131e6 100644 --- a/Juick/Views/BubbleMessageCell.m +++ b/Juick/Views/BubbleMessageCell.m @@ -7,7 +7,6 @@ // #import "BubbleMessageCell.h" -#import "APIClient.h" @implementation BubbleMessageCell @@ -31,7 +30,7 @@ self.message.text = message.text; self.unreadMarker.text = @""; __weak UIImageView *weakAvatar = self.avatarView; - [[APIClient sharedClient] fetchImageWithURL:[NSURL URLWithString:message.user.avatar] callback:^(NSData *data) { + [[AppDelegate shared].api fetchImageWithURL:[NSURL URLWithString:message.user.avatar] callback:^(NSData *data) { [UIView transitionWithView:weakAvatar duration:0.3 options:UIViewAnimationOptionTransitionCrossDissolve diff --git a/Juick/Views/ContentLoadingCell.xib b/Juick/Views/ContentLoadingCell.xib index f7ca251..21c121b 100644 --- a/Juick/Views/ContentLoadingCell.xib +++ b/Juick/Views/ContentLoadingCell.xib @@ -1,9 +1,9 @@ - + - + @@ -92,7 +92,7 @@ - + diff --git a/Juick/Views/ConversationCell.m b/Juick/Views/ConversationCell.m index dd17963..4c1e71c 100644 --- a/Juick/Views/ConversationCell.m +++ b/Juick/Views/ConversationCell.m @@ -7,7 +7,6 @@ // #import "ConversationCell.h" -#import "APIClient.h" @implementation ConversationCell @@ -17,7 +16,7 @@ self.lastMessage.text = chat.lastMessageText; self.unreadMarker.hidden = true; __weak UIImageView *weakAvatar = self.avatar; - [[APIClient sharedClient] fetchImageWithURL:[NSURL URLWithString:chat.avatar] callback:^(NSData *data) { + [[AppDelegate shared].api fetchImageWithURL:[NSURL URLWithString:chat.avatar] callback:^(NSData *data) { [UIView transitionWithView:weakAvatar duration:0.3 options:UIViewAnimationOptionTransitionCrossDissolve diff --git a/Juick/Views/MessageCell.m b/Juick/Views/MessageCell.m index a64b7c3..ad1a3a3 100644 --- a/Juick/Views/MessageCell.m +++ b/Juick/Views/MessageCell.m @@ -7,7 +7,6 @@ // #import "MessageCell.h" -#import "APIClient.h" #import "Entity.h" #import "NSDate+TimeAgo.h" @@ -50,7 +49,7 @@ const NSString *unreadMarker = @"●"; - (void) configureWithMessage:(Message *)msg { self.avatar.image = nil; __weak UIImageView *weakAvatar = self.avatar; - [[APIClient sharedClient] fetchImageWithURL:[NSURL URLWithString:msg.user.avatar] callback:^(NSData *data) { + [[AppDelegate shared].api fetchImageWithURL:[NSURL URLWithString:msg.user.avatar] callback:^(NSData *data) { [UIView transitionWithView:weakAvatar duration:0.3 options:UIViewAnimationOptionTransitionCrossDissolve @@ -67,7 +66,7 @@ const NSString *unreadMarker = @"●"; self.attachmentHeight.constant = imageHeight; self.attachmentWidth.constant = imageWidth; __weak UIImageView *weakAttach = self.attach; - [[APIClient sharedClient] fetchImageWithURL:[NSURL URLWithString:msg.attachment.small.url] callback:^(NSData *data) { + [[AppDelegate shared].api fetchImageWithURL:[NSURL URLWithString:msg.attachment.small.url] callback:^(NSData *data) { [UIView transitionWithView:weakAttach duration:0.3 options:UIViewAnimationOptionTransitionCrossDissolve @@ -82,7 +81,7 @@ const NSString *unreadMarker = @"●"; } self.title.text = msg.user.uname; - self.timestamp.text = [[[APIClient sharedClient].dateFormatter dateFromString:msg.timestamp] timeAgo]; + self.timestamp.text = [[[AppDelegate shared].sharedDateFormatter dateFromString:msg.timestamp] timeAgo]; NSUInteger count = [msg.repliesCount unsignedIntegerValue]; if (count > 0) { if ([msg.repliesBy length] > 0) { diff --git a/Juick/Views/MessageInputView.xib b/Juick/Views/MessageInputView.xib index 876be37..e13beca 100644 --- a/Juick/Views/MessageInputView.xib +++ b/Juick/Views/MessageInputView.xib @@ -1,9 +1,9 @@ - + - + @@ -75,7 +75,7 @@ - + -- cgit v1.2.3