diff options
author | Vitaly Takmazov | 2019-10-01 17:29:46 +0300 |
---|---|---|
committer | Vitaly Takmazov | 2019-10-01 22:14:59 +0300 |
commit | dbab6ab54c40a016f75e75a4143576c3fa41c3e0 (patch) | |
tree | 6fd5e68ec76c249fcaa07219c53ccab2f4c98fd5 /Juick | |
parent | aeb513ed5c165e355c9c378efde7445f597d0380 (diff) |
Drop AFNetworking and SSKeychain
Diffstat (limited to 'Juick')
-rw-r--r-- | Juick/APIClient.h | 3 | ||||
-rw-r--r-- | Juick/APIClient.m | 331 | ||||
-rw-r--r-- | Juick/AppDelegate.m | 11 | ||||
-rw-r--r-- | Juick/Helpers/NSData+Hex.h | 19 | ||||
-rw-r--r-- | Juick/Helpers/NSData+Hex.m | 22 | ||||
-rw-r--r-- | Juick/Main.storyboard | 6 | ||||
-rw-r--r-- | Juick/Supporting Files/Juick-Prefix.pch | 2 | ||||
-rw-r--r-- | Juick/ViewControllers/ChatViewController.m | 2 | ||||
-rw-r--r-- | Juick/ViewControllers/FeedViewController.m | 2 | ||||
-rw-r--r-- | Juick/ViewControllers/LoginViewController.m | 16 | ||||
-rw-r--r-- | Juick/ViewControllers/MessagesViewController.m | 6 | ||||
-rw-r--r-- | Juick/ViewControllers/NewPostViewController.h | 3 | ||||
-rw-r--r-- | Juick/ViewControllers/NewPostViewController.m | 29 | ||||
-rw-r--r-- | Juick/ViewControllers/ThreadViewController.m | 6 |
14 files changed, 277 insertions, 181 deletions
diff --git a/Juick/APIClient.h b/Juick/APIClient.h index f2b2a7c..d8317a4 100644 --- a/Juick/APIClient.h +++ b/Juick/APIClient.h @@ -9,9 +9,10 @@ #import "Message.h" #import "DeviceRegistration.h" -@interface APIClient : NSObject +@interface APIClient : NSObject<NSURLSessionDelegate, NSURLSessionTaskDelegate> @property (nonatomic, strong) NSDateFormatter *dateFormatter; +@property (nonatomic, strong) NSURLCredential *credential; +(APIClient *) sharedClient; diff --git a/Juick/APIClient.m b/Juick/APIClient.m index 2712f1d..3332556 100644 --- a/Juick/APIClient.m +++ b/Juick/APIClient.m @@ -9,10 +9,12 @@ #import "Message.h" #import "Chat.h" -@interface APIClient() -@property(nonatomic, readwrite) AFHTTPSessionManager *manager; +@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 @@ -30,197 +32,244 @@ if (self = [super init]) { NSString *baseURLString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"base_url"]; NSLog(@"Initializing with %@ base URL", baseURLString); - _manager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:baseURLString]]; - _manager.requestSerializer = [AFJSONRequestSerializer new]; - [_manager.requestSerializer setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]; + 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"]]; - _urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; } return self; } --(AFHTTPSessionManager *) manager { - NSString *username = [SAMKeychain passwordForService:[[NSBundle mainBundle] bundleIdentifier] account:@"com.juick.username"]; - if (username) { - [_manager.requestSerializer - setAuthorizationHeaderFieldWithUsername:username - password:[SAMKeychain passwordForService:[[NSBundle mainBundle] bundleIdentifier] account:@"com.juick.password"]]; - } - return _manager; -} - -(void) pullNextFromPath:(NSString *)path params:(NSDictionary *) params callback:(void(^)(NSArray<Message *> *, NSError *))callback { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self.backgroundQueue addOperationWithBlock:^{ - [self.manager GET:path parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { - NSInteger statusCode = [((NSHTTPURLResponse *) task.response) statusCode]; - if (statusCode == 200) { - NSMutableArray *messages = [NSMutableArray new]; - [((NSArray *)responseObject) enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - [messages addObject:[Message fromJSON:obj]]; - }]; - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(messages, nil); - }]; - } else { - callback(nil, [NSError errorWithDomain:@"JuickErrorDomain" code:statusCode userInfo:nil]); - } - - } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { - NSLog(@"REST Error: %@", error); - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - NSInteger statusCode = ((NSHTTPURLResponse *)task.response).statusCode; - callback(nil, [NSError errorWithDomain:@"JuickErrorDomain" code:statusCode userInfo:nil]); - }]; + [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.backgroundQueue addOperationWithBlock:^{ - [self.manager POST:@"post" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) { - [formData appendPartWithFormData:[text dataUsingEncoding:NSUTF8StringEncoding] name:@"body"]; - } progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { - NSLog(@"Success!"); - Message *result = [Message fromJSON:responseObject]; - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(result, nil); - }]; - } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { - NSLog(@"Error: %@", [error localizedDescription]); - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(nil, error); - }]; - }]; + [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.backgroundQueue addOperationWithBlock:^{ - [self.manager POST:@"comment" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) { - [formData appendPartWithFormData:[[mid stringValue] dataUsingEncoding:NSUTF8StringEncoding] name:@"mid"]; - [formData appendPartWithFormData:[[NSString stringWithFormat:@"%d", [rid intValue]] dataUsingEncoding:NSUTF8StringEncoding] name:@"rid"]; - [formData appendPartWithFormData:[text dataUsingEncoding:NSUTF8StringEncoding] name:@"body"]; - } progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { + [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!"); - Message *reply = [Message fromJSON:responseObject]; - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(reply, nil); - }]; - } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { - NSLog(@"Error: %@", [error localizedDescription]); - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(nil, error); - }]; - }]; + reply = [Message fromJSON:response]; + } + callback(reply, err); }]; } + -(void) postPMToUser:(NSString *)uname text:(NSString *)text result:(void (^)(NSError *))callback { [self.backgroundQueue addOperationWithBlock:^{ - [self.manager POST:@"pm" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) { - [formData appendPartWithFormData:[text dataUsingEncoding:NSUTF8StringEncoding] name:@"body"]; - [formData appendPartWithFormData:[uname dataUsingEncoding:NSUTF8StringEncoding] name:@"uname"]; - } progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(nil); - }]; - } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { - NSLog(@"Error: %@", [error localizedDescription]); - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(error); - }]; + [self post:@"pm" params:@{ + @"uname": uname, + @"body": text + } callback:^(NSDictionary *response, NSError *err) { + callback(err); }]; }]; } -(void) fetchChats:(void (^)(NSArray *, NSError *))callback { - [self.backgroundQueue addOperationWithBlock:^{ - [self.manager GET:@"groups_pms" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { - NSMutableArray *groups = [NSMutableArray new]; - NSArray *pms = [(NSDictionary *)responseObject objectForKey:@"pms"]; - [pms enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - [groups addObject:[Chat fromJSON:obj]]; - }]; - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(groups, nil); - }]; - } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { - NSLog(@"Error: %@", [error localizedDescription]); - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(nil, error); - }]; + [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}; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self.backgroundQueue addOperationWithBlock:^{ - [self.manager GET:@"pm" parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { - NSMutableArray *messages = [NSMutableArray new]; - NSArray *messagesList = (NSArray *)responseObject; - [messagesList enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - [messages addObject:[Message fromJSON:obj]]; - }]; - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(messages, nil); - }]; - } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { - NSLog(@"Error: %@", [error localizedDescription]); - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(nil, error); - }]; + [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.manager GET:@"me" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { - NSString *username = [SAMKeychain passwordForService:[[NSBundle mainBundle] bundleIdentifier] account:@"com.juick.username"]; - NSString *password = [SAMKeychain passwordForService:[[NSBundle mainBundle] bundleIdentifier] account:@"com.juick.password"]; - [self.manager.requestSerializer setAuthorizationHeaderFieldWithUsername:username password:password]; - callback([User fromJSON:responseObject], nil); - } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { - [self.manager.requestSerializer clearAuthorizationHeader]; - callback(nil, error); + [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.manager GET:@"users" parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { - callback([User fromJSON:[(NSArray *)responseObject firstObject]]); - } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { - callback(nil); + [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 { - [self.manager PUT:@"/notifications" parameters:@[[registrationData toJSON]] success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { - callback(YES); - } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { - NSLog(@"fail %@", [error localizedDescription]); - callback(NO); - } ]; + 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 { + [[NSURLCredentialStorage sharedCredentialStorage] setCredential:credential forProtectionSpace:self.apiProtectionSpace]; +} + + -(BOOL) isAuthenticated { - NSString *password = [SAMKeychain passwordForService:[[NSBundle mainBundle] bundleIdentifier] account:@"com.juick.password"]; + NSString *password = self.credential.password; return password != nil; } -- (void)fetchImageWithURL:(NSURL *)url callback:(void (^)(NSData *))callback { - NSURLSessionDataTask *dataTask = [self.urlSession dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - callback(data); - }]; +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler { + if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic) { + completionHandler(NSURLSessionAuthChallengeUseCredential, self.credential); + } else { + 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) { + NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode; + if (!response || statusCode != 200) { + NSError *err = [NSError errorWithDomain:@"JuickErrorDomain" + code:statusCode + userInfo:nil]; + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + callback(nil, err); + }]; + } else { + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + callback(data, error); + }]; + } }]; [self.backgroundQueue addOperationWithBlock:^{ - [dataTask resume]; + [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) { + NSError *jsonError; + NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; + if (jsonError) { + callback(nil, jsonError); + } else { + callback(jsonData, nil); + } + }]; +} + +-(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); + } }]; } diff --git a/Juick/AppDelegate.m b/Juick/AppDelegate.m index 9501316..61c8da5 100644 --- a/Juick/AppDelegate.m +++ b/Juick/AppDelegate.m @@ -16,6 +16,7 @@ #import "DeviceRegistration.h" #import "NewPostViewController.h" #import "FeedViewController.h" +#import "NSData+Hex.h" NSString * const UserUpdatedNotificationName = @"UserUpdated"; @@ -40,13 +41,6 @@ NSString * const UserUpdatedNotificationName = @"UserUpdated"; NSDictionary *userInfo = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]; if (userInfo) { [self parseNotificationPayload:userInfo]; - } else { - if ([[NSUserDefaults standardUserDefaults] objectForKey:@"FirstRun"] == nil) { - [SAMKeychain deletePasswordForService:[[NSBundle mainBundle] bundleIdentifier] account:@"com.juick.username"]; - [SAMKeychain deletePasswordForService:[[NSBundle mainBundle] bundleIdentifier] account:@"com.juick.password"]; - [SAMKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlock]; - [[NSUserDefaults standardUserDefaults] setObject:@"1" forKey:@"FirstRun"]; - } } return YES; } @@ -64,8 +58,7 @@ NSString * const UserUpdatedNotificationName = @"UserUpdated"; } -(void) application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { - NSString *token = [[deviceToken description] stringByTrimmingCharactersInSet: [NSCharacterSet characterSetWithCharactersInString:@"<>"]]; - token = [token stringByReplacingOccurrencesOfString:@" " withString:@""]; + NSString *token = [deviceToken hexString]; [[NSOperationQueue new] addOperationWithBlock:^{ DeviceRegistration *registration = [DeviceRegistration new]; registration.type = @"apns"; diff --git a/Juick/Helpers/NSData+Hex.h b/Juick/Helpers/NSData+Hex.h new file mode 100644 index 0000000..bd8e519 --- /dev/null +++ b/Juick/Helpers/NSData+Hex.h @@ -0,0 +1,19 @@ +// +// NSData+Hex.h +// Juick +// +// Created by Vitaly Takmazov on 01/10/2019. +// Copyright © 2019 com.juick. All rights reserved. +// + +#import <Foundation/Foundation.h> + +NS_ASSUME_NONNULL_BEGIN + +@interface NSData (Hex) + +- (NSString *)hexString; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Juick/Helpers/NSData+Hex.m b/Juick/Helpers/NSData+Hex.m new file mode 100644 index 0000000..243a380 --- /dev/null +++ b/Juick/Helpers/NSData+Hex.m @@ -0,0 +1,22 @@ +// +// NSData+Hex.m +// Juick +// +// Created by Vitaly Takmazov on 01/10/2019. +// Copyright © 2019 com.juick. All rights reserved. +// + +#import "NSData+Hex.h" + +@implementation NSData (Hex) +- (NSString *)hexString { + NSMutableString *string = [NSMutableString stringWithCapacity:self.length * 3]; + [self enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop){ + for (NSUInteger offset = 0; offset < byteRange.length; ++offset) { + uint8_t byte = ((const uint8_t *)bytes)[offset]; + [string appendFormat:@"%02x", byte]; + } + }]; + return string; +} +@end diff --git a/Juick/Main.storyboard b/Juick/Main.storyboard index 1ab86fe..81266dd 100644 --- a/Juick/Main.storyboard +++ b/Juick/Main.storyboard @@ -143,9 +143,6 @@ <userDefinedRuntimeAttributes> <userDefinedRuntimeAttribute type="string" keyPath="placeholder" value="Username..."/> </userDefinedRuntimeAttributes> - <connections> - <action selector="usernameChanged:" destination="4g9-hM-bzq" eventType="editingChanged" id="qst-Yi-KZ6"/> - </connections> </textField> <textField opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="251" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" adjustsFontForContentSizeCategory="YES" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="xGs-fu-6K0"> <rect key="frame" x="0.0" y="274" width="339" height="34"/> @@ -154,9 +151,6 @@ <userDefinedRuntimeAttributes> <userDefinedRuntimeAttribute type="string" keyPath="placeholder" value="Password..."/> </userDefinedRuntimeAttributes> - <connections> - <action selector="passwordChanged:" destination="4g9-hM-bzq" eventType="editingChanged" id="Td0-2L-Dgo"/> - </connections> </textField> </subviews> </stackView> diff --git a/Juick/Supporting Files/Juick-Prefix.pch b/Juick/Supporting Files/Juick-Prefix.pch index c9a0d25..3a66330 100644 --- a/Juick/Supporting Files/Juick-Prefix.pch +++ b/Juick/Supporting Files/Juick-Prefix.pch @@ -17,9 +17,7 @@ #import <MobileCoreServices/MobileCoreServices.h> #import <UserNotifications/UserNotifications.h> - #import <SAMKeychain/SAMKeychain.h> #import "UIImage+Utils.h" - #import <AFNetworking/AFNetworking.h> #import <DateTools/DateTools.h> #import <PHFComposeBarView/PHFComposeBarView.h> #import <UIView+Shimmer.h> diff --git a/Juick/ViewControllers/ChatViewController.m b/Juick/ViewControllers/ChatViewController.m index 0de3d2a..989b531 100644 --- a/Juick/ViewControllers/ChatViewController.m +++ b/Juick/ViewControllers/ChatViewController.m @@ -26,7 +26,7 @@ self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive; self.tableView.allowsSelection = NO; - self.me = [SAMKeychain passwordForService:[[NSBundle mainBundle] bundleIdentifier] account:@"com.juick.username"]; + self.me = [APIClient sharedClient].credential.user; [self reloadChat]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil]; self.refreshControl = [UIRefreshControl new]; diff --git a/Juick/ViewControllers/FeedViewController.m b/Juick/ViewControllers/FeedViewController.m index 523ea52..31bf0ca 100644 --- a/Juick/ViewControllers/FeedViewController.m +++ b/Juick/ViewControllers/FeedViewController.m @@ -70,7 +70,7 @@ } } -- (void)userDidSignedIn:(User *) user { +- (void)userDidSignedIn:(NSNotification *) notification { [self refreshPath]; [self refreshData]; } diff --git a/Juick/ViewControllers/LoginViewController.m b/Juick/ViewControllers/LoginViewController.m index 898e59c..f9b17a3 100644 --- a/Juick/ViewControllers/LoginViewController.m +++ b/Juick/ViewControllers/LoginViewController.m @@ -29,8 +29,8 @@ NSString * const UserSignedInNotificationName = @"UserSignedIn"; target:self action:@selector(cancelSignIn)]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:@selector(doneSignIn)]; - self.usernameField.text = [SAMKeychain passwordForService:[[NSBundle mainBundle] bundleIdentifier] account:@"com.juick.username"]; - self.passwordField.text = [SAMKeychain passwordForService:[[NSBundle mainBundle] bundleIdentifier] account:@"com.juick.password"];; + 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]; } @@ -57,6 +57,7 @@ NSString * const UserSignedInNotificationName = @"UserSignedIn"; } - (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]; @@ -73,15 +74,4 @@ NSString * const UserSignedInNotificationName = @"UserSignedIn"; } }]; } -- (IBAction)passwordChanged:(id)sender { - if ([self.passwordField.text isKindOfClass:[NSString class]]) { - [SAMKeychain setPassword:self.passwordField.text forService:[[NSBundle mainBundle] bundleIdentifier] account:@"com.juick.password"]; - } -} - -- (IBAction)usernameChanged:(id)sender { - if ([self.usernameField.text isKindOfClass:[NSString class]]) { - [SAMKeychain setPassword:self.usernameField.text forService:[[NSBundle mainBundle] bundleIdentifier] account:@"com.juick.username"]; - } -} @end diff --git a/Juick/ViewControllers/MessagesViewController.m b/Juick/ViewControllers/MessagesViewController.m index afa0f9a..ad28484 100644 --- a/Juick/ViewControllers/MessagesViewController.m +++ b/Juick/ViewControllers/MessagesViewController.m @@ -106,6 +106,7 @@ NSString* const messageCellIdentifier = @"messageCell"; self.refreshControl = [UIRefreshControl new]; [self.refreshControl addTarget:self action:@selector(refreshData) forControlEvents:UIControlEventValueChanged]; [self refreshData]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(newMessage:) name:NewMessageNotificationName object:nil]; } - (void) composePressed { @@ -174,4 +175,9 @@ NSString* const messageCellIdentifier = @"messageCell"; [self.navigationController pushViewController:threadVC animated:YES]; } +-(void) newMessage:(NSNotification *)obj { + Message *msg = (Message *)[obj object]; + [self viewThreadForMessage:msg mid:msg.mid scrollTo:0]; +} + @end diff --git a/Juick/ViewControllers/NewPostViewController.h b/Juick/ViewControllers/NewPostViewController.h index 7a1dfac..05e3c73 100644 --- a/Juick/ViewControllers/NewPostViewController.h +++ b/Juick/ViewControllers/NewPostViewController.h @@ -9,6 +9,9 @@ #import <UIKit/UIKit.h> #import "Message.h" +extern NSString * const NewMessageNotificationName; +extern NSString * const ReplyPostedNotificationName; + @interface NewPostViewController : UIViewController @property (weak, nonatomic) IBOutlet UITextView *textView; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *bottomConstraint; diff --git a/Juick/ViewControllers/NewPostViewController.m b/Juick/ViewControllers/NewPostViewController.m index 65172f1..afa14a9 100644 --- a/Juick/ViewControllers/NewPostViewController.m +++ b/Juick/ViewControllers/NewPostViewController.m @@ -10,6 +10,10 @@ #import "MessagesViewController.h" #import "QuoteView.h" #import "APIClient.h" +#import "AppDelegate.h" + +NSString * const NewMessageNotificationName = @"NewMessage"; +NSString * const ReplyPostedNotificationName = @"ReplyPosted"; @interface NewPostViewController () @@ -42,17 +46,28 @@ - (IBAction)sendAction:(id)sender { self.navigationController.navigationItem.rightBarButtonItem.enabled = NO; if (_replyTo == nil) { + self.navigationController.navigationItem.rightBarButtonItem.enabled = YES; [[APIClient sharedClient] postMessage:self.textView.text result:^(Message *msg, NSError *err) { - self.navigationController.navigationItem.rightBarButtonItem.enabled = YES; - [self.navigationController popViewControllerAnimated:YES]; - [(MessagesViewController *)self.navigationController.visibleViewController setShouldScrollToUnreadOnRefresh:YES]; + if (!err) { + [self dismissViewControllerAnimated:YES completion:^{ + [[NSNotificationCenter defaultCenter] postNotificationName:NewMessageNotificationName object:msg]; + }]; + } else { + self.navigationController.navigationItem.rightBarButtonItem.enabled = YES; + // TODO: display error + } }]; } else { + self.navigationController.navigationItem.rightBarButtonItem.enabled = NO; [[APIClient sharedClient] postReplyToThread:_replyTo.mid inReplyTo:_replyTo.rid text:self.textView.text result:^(Message *msg, NSError *err) { - self.navigationController.navigationItem.rightBarButtonItem.enabled = YES; - [self.navigationController popViewControllerAnimated:YES]; - [(MessagesViewController *)self.navigationController.visibleViewController setShouldScrollToUnreadOnRefresh:YES]; - [(MessagesViewController *)self.navigationController.visibleViewController refreshData]; + if (!err) { + [self dismissViewControllerAnimated:YES completion:^{ + [[NSNotificationCenter defaultCenter] postNotificationName:ReplyPostedNotificationName object:msg]; + }]; + } else { + self.navigationController.navigationItem.rightBarButtonItem.enabled = YES; + // TODO: display error + } }]; } } diff --git a/Juick/ViewControllers/ThreadViewController.m b/Juick/ViewControllers/ThreadViewController.m index 3ea64f7..af56bc3 100644 --- a/Juick/ViewControllers/ThreadViewController.m +++ b/Juick/ViewControllers/ThreadViewController.m @@ -19,6 +19,7 @@ [self setPath:[APIClient threadUrl]]; [super viewDidLoad]; self.messagesDelegate = self; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(replyPosted:) name:ReplyPostedNotificationName object:nil]; } -(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { @@ -41,4 +42,9 @@ [self refreshData]; } +- (void)replyPosted:(NSNotification *) notification { + [self setShouldScrollToUnreadOnRefresh:YES]; + [self refreshData]; +} + @end |