summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Vitaly Takmazov2019-10-01 17:29:46 +0300
committerGravatar Vitaly Takmazov2019-10-01 22:14:59 +0300
commitdbab6ab54c40a016f75e75a4143576c3fa41c3e0 (patch)
tree6fd5e68ec76c249fcaa07219c53ccab2f4c98fd5
parentaeb513ed5c165e355c9c378efde7445f597d0380 (diff)
Drop AFNetworking and SSKeychain
-rw-r--r--Juick.xcodeproj/project.pbxproj8
-rw-r--r--Juick/APIClient.h3
-rw-r--r--Juick/APIClient.m331
-rw-r--r--Juick/AppDelegate.m11
-rw-r--r--Juick/Helpers/NSData+Hex.h19
-rw-r--r--Juick/Helpers/NSData+Hex.m22
-rw-r--r--Juick/Main.storyboard6
-rw-r--r--Juick/Supporting Files/Juick-Prefix.pch2
-rw-r--r--Juick/ViewControllers/ChatViewController.m2
-rw-r--r--Juick/ViewControllers/FeedViewController.m2
-rw-r--r--Juick/ViewControllers/LoginViewController.m16
-rw-r--r--Juick/ViewControllers/MessagesViewController.m6
-rw-r--r--Juick/ViewControllers/NewPostViewController.h3
-rw-r--r--Juick/ViewControllers/NewPostViewController.m29
-rw-r--r--Juick/ViewControllers/ThreadViewController.m6
-rw-r--r--Podfile2
-rw-r--r--Podfile.lock24
17 files changed, 284 insertions, 208 deletions
diff --git a/Juick.xcodeproj/project.pbxproj b/Juick.xcodeproj/project.pbxproj
index a7b563a..5a8a4fb 100644
--- a/Juick.xcodeproj/project.pbxproj
+++ b/Juick.xcodeproj/project.pbxproj
@@ -39,6 +39,7 @@
776C41BD1FD3EF180063B82E /* MessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 776C41BB1FD3EF180063B82E /* MessageCell.m */; };
776C41BE1FD3EF180063B82E /* MessageCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 776C41BC1FD3EF180063B82E /* MessageCell.xib */; };
776C41C11FD3FF6E0063B82E /* FeedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 776C41C01FD3FF6E0063B82E /* FeedViewController.m */; };
+ 778560602343D24E00BB37A2 /* NSData+Hex.m in Sources */ = {isa = PBXBuildFile; fileRef = 7785605F2343D24E00BB37A2 /* NSData+Hex.m */; };
77975A1D182B6E9A00410C2B /* NewPostViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 77975A1C182B6E9A00410C2B /* NewPostViewController.m */; };
77975A1F182BDCE900410C2B /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77975A1E182BDCE900410C2B /* SystemConfiguration.framework */; };
77975A21182BDCF300410C2B /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77975A20182BDCF300410C2B /* MobileCoreServices.framework */; };
@@ -141,6 +142,8 @@
776C41BC1FD3EF180063B82E /* MessageCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MessageCell.xib; sourceTree = "<group>"; };
776C41BF1FD3FF6E0063B82E /* FeedViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FeedViewController.h; sourceTree = "<group>"; };
776C41C01FD3FF6E0063B82E /* FeedViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FeedViewController.m; sourceTree = "<group>"; };
+ 7785605E2343D24E00BB37A2 /* NSData+Hex.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSData+Hex.h"; sourceTree = "<group>"; };
+ 7785605F2343D24E00BB37A2 /* NSData+Hex.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSData+Hex.m"; sourceTree = "<group>"; };
77975A1B182B6E9A00410C2B /* NewPostViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NewPostViewController.h; sourceTree = "<group>"; };
77975A1C182B6E9A00410C2B /* NewPostViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NewPostViewController.m; sourceTree = "<group>"; };
77975A1E182BDCE900410C2B /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
@@ -339,6 +342,8 @@
77C67EE41828342000427098 /* NSURL+PathParameters.m */,
77B09992189D0B9900A84F59 /* UIImage+Utils.h */,
77B09993189D0B9900A84F59 /* UIImage+Utils.m */,
+ 7785605E2343D24E00BB37A2 /* NSData+Hex.h */,
+ 7785605F2343D24E00BB37A2 /* NSData+Hex.m */,
);
path = Helpers;
sourceTree = "<group>";
@@ -537,12 +542,10 @@
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Juick/Pods-Juick-resources.sh",
"${PODS_ROOT}/DateTools/DateTools/DateTools/DateTools.bundle",
- "${PODS_ROOT}/SAMKeychain/Support/SAMKeychain.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/DateTools.bundle",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SAMKeychain.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -578,6 +581,7 @@
77C67EE51828342000427098 /* NSURL+PathParameters.m in Sources */,
77E35A82189A5B5A00B2D216 /* LoginViewController.m in Sources */,
773E6397204BCB64008B8F8D /* ConversationCell.m in Sources */,
+ 778560602343D24E00BB37A2 /* NSData+Hex.m in Sources */,
776C41BD1FD3EF180063B82E /* MessageCell.m in Sources */,
77317BB8181BBE8500D60005 /* main.m in Sources */,
77B8B39C207A5629005CB20C /* MessageInputView.m in Sources */,
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
diff --git a/Podfile b/Podfile
index d8cdb6d..db6b6b7 100644
--- a/Podfile
+++ b/Podfile
@@ -2,8 +2,6 @@ source 'https://cdn.jsdelivr.net/cocoa/'
platform :ios, "11.1"
target "Juick" do
- pod 'AFNetworking'
- pod 'SAMKeychain'
pod 'DateTools'
pod 'PHFComposeBarView'
pod 'UIView+Shimmer'
diff --git a/Podfile.lock b/Podfile.lock
index 107c8bc..08a1a2b 100644
--- a/Podfile.lock
+++ b/Podfile.lock
@@ -1,50 +1,28 @@
PODS:
- - AFNetworking (3.2.1):
- - AFNetworking/NSURLSession (= 3.2.1)
- - AFNetworking/Reachability (= 3.2.1)
- - AFNetworking/Security (= 3.2.1)
- - AFNetworking/Serialization (= 3.2.1)
- - AFNetworking/UIKit (= 3.2.1)
- - AFNetworking/NSURLSession (3.2.1):
- - AFNetworking/Reachability
- - AFNetworking/Security
- - AFNetworking/Serialization
- - AFNetworking/Reachability (3.2.1)
- - AFNetworking/Security (3.2.1)
- - AFNetworking/Serialization (3.2.1)
- - AFNetworking/UIKit (3.2.1):
- - AFNetworking/NSURLSession
- DateTools (2.0.0)
- PHFComposeBarView (2.1.0):
- PHFDelegateChain (~> 1.0)
- PHFDelegateChain (1.0.1)
- - SAMKeychain (1.5.3)
- "UIView+Shimmer (1.0.0)"
DEPENDENCIES:
- - AFNetworking
- DateTools
- PHFComposeBarView
- - SAMKeychain
- "UIView+Shimmer"
SPEC REPOS:
https://cdn.jsdelivr.net/cocoa/:
- - AFNetworking
- DateTools
- PHFComposeBarView
- PHFDelegateChain
- - SAMKeychain
- "UIView+Shimmer"
SPEC CHECKSUMS:
- AFNetworking: b6f891fdfaed196b46c7a83cf209e09697b94057
DateTools: 933ac9c490f21f92127cf690ccd8c397e0126caf
PHFComposeBarView: 6382ab846e2f4d8634273c4a78d074bc5deed07f
PHFDelegateChain: 491f9cd8a3fb8761f390ff05f74a0e168d48d285
- SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
"UIView+Shimmer": ed634f95e8f4bda666b28b47bd85a4336a4117d8
-PODFILE CHECKSUM: df93a469bf7674ef7f4d2c559037f99f6c878d5f
+PODFILE CHECKSUM: 07617045e3d7a5f7cdf98542a754f10d85b5ff19
COCOAPODS: 1.7.1