I have an iOS app with Facebook functionality and a Parse backend. In the social media app, there is an ActivityViewController that shows: likes, comments, followers. That code is connected to my js cloud code for push notifications through Parse.
While I have comment notifications working great if userA comments on usersB's post (userB is notified), I want to add notification for a "UserB replied to your comment" activity portion (where userA is notified that there's been a response from UserB while it's on their post). Probably explaining that terribly, but basically pretty similar to face book's notifications in the comments portion but right now notifications are only one sided. I have been to work it, but I seem to only get users notifying themselves that they replied to a post.
I'm having a little trouble wrapping my head around the best way to implement that. The constants, cloud code and caches should be set up correctly. Code Below.
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
// Comment Portion, works fine
NSString *trimmedComment = [textField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if (trimmedComment.length != 0 && [self.photo objectForKey:kPAPPhotoUserKey]) {
PFObject *comment = [PFObject objectWithClassName:kPAPActivityClassKey];
[comment setObject:trimmedComment forKey:kPAPActivityContentKey]; // Set comment text
[comment setObject:[self.photo objectForKey:kPAPPhotoUserKey] forKey:kPAPActivityToUserKey]; // Set toUser
[comment setObject:[PFUser currentUser] forKey:kPAPActivityFromUserKey]; // Set fromUser
[comment setObject:kPAPActivityTypeComment forKey:kPAPActivityTypeKey];
[comment setObject:self.photo forKey:kPAPActivityPhotoKey];
PFACL *ACL = [PFACL ACLWithUser:[PFUser currentUser]];
[ACL setPublicReadAccess:YES];
[ACL setWriteAccess:YES forUser:[self.photo objectForKey:kPAPPhotoUserKey]];
comment.ACL = ACL;
[[PAPCache sharedCache] incrementCommentCountForPhoto:self.photo];
// Show HUD view
[MBProgressHUD showHUDAddedTo:self.view.superview animated:YES];
// If more than 5 seconds pass since we post a comment, stop waiting for the server to respond
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:5.0f target:self selector:#selector(handleCommentTimeout:) userInfo:#{#"comment": comment} repeats:NO];
[comment saveEventually:^(BOOL succeeded, NSError *error) {
[timer invalidate];
if (error && error.code == kPFErrorObjectNotFound) {
[[PAPCache sharedCache] decrementCommentCountForPhoto:self.photo];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Could not post comment", nil) message:NSLocalizedString(#"This photo is no longer available", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[alert show];
[self.navigationController popViewControllerAnimated:YES];
}
[[NSNotificationCenter defaultCenter] postNotificationName:PAPPhotoDetailsViewControllerUserCommentedOnPhotoNotification object:self.photo userInfo:#{#"comments": #(self.objects.count + 1)}];
[MBProgressHUD hideHUDForView:self.view.superview animated:YES];
[self loadObjects];
}];
}
//Reply portion is getting me stuck
NSString *trimmedReply = [textField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if (trimmedReply.length != 0 && [self.photo objectForKey:kPAPPhotoUserKey]) {
PFObject *reply = [PFObject objectWithClassName:kPAPActivityClassKey];
[reply setObject:trimmedReply forKey:kPAPActivityContentKey]; // Set reply text
[reply setObject:[self.photo objectForKey:kPAPPhotoUserKey] forKey:kPAPActivityFromUserKey]; //CHANGED TO FromUser
[reply setObject:[PFUser currentUser] forKey:kPAPActivityToUserKey]; // Changed ToUser
[reply setObject:kPAPActivityTypeReply forKey:kPAPActivityTypeKey];
[reply setObject:self.photo forKey:kPAPActivityPhotoKey];
PFACL *ACL = [PFACL ACLWithUser:[PFUser currentUser]];
[ACL setPublicReadAccess:YES];
[ACL setWriteAccess:YES forUser:[self.photo objectForKey:kPAPPhotoUserKey]];
reply.ACL = ACL;
[[PAPCache sharedCache] incrementReplyCountForPhoto:self.photo];
// Show HUD view
[MBProgressHUD showHUDAddedTo:self.view.superview animated:YES];
// If more than 5 seconds pass since we post a reply, stop waiting for the server to respond
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:5.0f target:self selector:#selector(handleReplyTimeout:) userInfo:#{#"reply": reply} repeats:NO];
[reply saveEventually:^(BOOL succeeded, NSError *error) {
[timer invalidate];
if (error && error.code == kPFErrorObjectNotFound) {
[[PAPCache sharedCache] decrementReplyCountForPhoto:self.photo];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Could not post reply", nil) message:NSLocalizedString(#"This photo is no longer available", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[alert show];
[self.navigationController popViewControllerAnimated:YES];
}
[[NSNotificationCenter defaultCenter] postNotificationName:PAPPhotoDetailsViewControllerUserRepliedOnPhotoNotification object:self.photo userInfo:#{#"replies": #(self.objects.count + 1)}];
[MBProgressHUD hideHUDForView:self.view.superview animated:YES];
[self loadObjects];
}];
}
///^^^
[textField setText:#""];
return [textField resignFirstResponder];
}
Related
Crashing when running Parse Anypic code.
-(void)sendCommentButton:(id) sender {
NSString *trimmedComment = [commentTextView.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if (trimmedComment.length != 0 && [self.photo objectForKey:kPAPPhotoUserKey]) {
PFObject *comment = [PFObject objectWithClassName:kPAPActivityClassKey];
[comment setObject:trimmedComment forKey:kPAPActivityContentKey]; // Set comment text
[comment setObject:[self.photo objectForKey:kPAPPhotoUserKey] forKey:kPAPActivityToUserKey]; // Set toUser
[comment setObject:[PFUser currentUser] forKey:kPAPActivityFromUserKey]; // Set fromUser
[comment setObject:kPAPActivityTypeComment forKey:kPAPActivityTypeKey];
[comment setObject:self.photo forKey:kPAPActivityPhotoKey];
[comment setObject:self.photoFile forKey:#"attachmentFile"];
PFACL *ACL = [PFACL ACLWithUser:[PFUser currentUser]];
[ACL setPublicReadAccess:YES];
[ACL setWriteAccess:YES forUser:[self.photo objectForKey:kPAPPhotoUserKey]];
comment.ACL = ACL;
[[PAPCache sharedCache] incrementCommentCountForPhoto:self.photo];
// Show HUD view
[MBProgressHUD showHUDAddedTo:self.view.superview animated:YES];
// If more than 5 seconds pass since we post a comment, stop waiting for the server to respond
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:5.0f target:self selector:#selector(handleCommentTimeout:) userInfo:#{#"comment": comment} repeats:NO];
[comment saveEventually:^(BOOL succeeded, NSError *error) {
[timer invalidate];
if (error && error.code == kPFErrorObjectNotFound) {
[[PAPCache sharedCache] decrementCommentCountForPhoto:self.photo];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Could not post comment", nil) message:NSLocalizedString(#"This photo is no longer available", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[alert show];
[self.navigationController popViewControllerAnimated:YES];
}
[[NSNotificationCenter defaultCenter] postNotificationName:PAPPhotoDetailsViewControllerUserCommentedOnPhotoNotification object:self.photo userInfo:#{#"comments": #(self.objects.count + 1)}];
[MBProgressHUD hideHUDForView:self.view.superview animated:YES];
[self loadObjects];
}];
}
[self.commentTextView setText:#""];
[self.commentTextView resignFirstResponder];
if (self.photoFile != nil) {
self.photoFile = nil;
}
}
Picking an Image
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
NSLog(#"Hello");
UIImage *img = [info objectForKey:UIImagePickerControllerOriginalImage];
// JPEG to decrease file size and enable faster uploads & downloads
NSData *imageData = UIImageJPEGRepresentation(img, 0.8f);
self.photoFile = [PFFile fileWithData:imageData];
// Request a background execution task to allow us to finish uploading the photo even if the app is backgrounded
self.fileUploadBackgroundTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:self.fileUploadBackgroundTaskId];
}];
NSLog(#"Requested background expiration task with id %lu for Anypic photo upload", (unsigned long)self.fileUploadBackgroundTaskId);
[self.photoFile saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
NSLog(#"Photo uploaded successfully");
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Photo Uploaded"
message:#"successfully"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
} else {
[[UIApplication sharedApplication] endBackgroundTask:self.fileUploadBackgroundTaskId];
}
}];
[self dismissViewControllerAnimated:YES completion:nil];
}
Question: Why is it crashing? I believe this is the code that is crashing it. What I did to crash it was not add an attachmentFile and just put a comment.
If you need more code or need any clarifications please comment down below
PFObject *comment = [PFObject objectWithClassName:kPAPActivityClassKey];
[comment setObject:trimmedComment forKey:kPAPActivityContentKey]; // Set comment text
[comment setObject:[self.photo objectForKey:kPAPPhotoUserKey] forKey:kPAPActivityToUserKey]; // Set toUser
[comment setObject:[PFUser currentUser] forKey:kPAPActivityFromUserKey]; // Set fromUser
[comment setObject:kPAPActivityTypeComment forKey:kPAPActivityTypeKey];
[comment setObject:self.photo forKey:kPAPActivityPhotoKey];
[comment setObject:self.photoFile forKey:#"attachmentFile"];
In one of these lines, you're passing nil to the first parameter of setObject:forKey. Add an Exception Breakpoint (in the breakpoints tab of left sidebar) and check which line it breaks on.
I am using the following code to update a Parse object as button action:
-(IBAction)sendPressed:(id)sender
{
NSLog(#"boton subir cadena pulsado");
loadingSpinner.hidden = NO;
[loadingSpinner startAnimating];
//Upload a new picture
NSData *pictureData = UIImagePNGRepresentation(self.chainPhoto.image);
PFFile *file = [PFFile fileWithName:#"img" data:pictureData];
[file saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded){
NSLog(#"IMAGEN CARGADA");
PFQuery *query = [PFQuery queryWithClassName:#"cadenas"];
// Retrieve the object by id
[query getObjectInBackgroundWithId: chain.objectId block:^(PFObject *imageObject, NSError *error)
{
[imageObject setObject:file forKey:#"image"];
[imageObject setObject:self.commentTextField.text forKey:#"chain_name"];
[imageObject saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded){
//Go back to the wall
[self.navigationController popViewControllerAnimated:YES];
}
else{
NSString *errorString = [[error userInfo] objectForKey:#"error"];
[self showErrorView:errorString];
}
}];
}
ERROR HERE--> else
{
NSString *errorString = [[error userInfo] objectForKey:#"error"];
[self showErrorView:errorString];
}
}
[loadingSpinner stopAnimating];
//loadingSpinner.hidden = YES;
//self.commentTextField.text =#" ";
self.progress_block.hidden = YES;
// self.imageView.image = [UIImage imageNamed:#"no-image.jpg"];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Restaurant Chain changed with success"
message:#"You can now go back to the list."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
} progressBlock:^(int percentDone) {
self.progress_block.hidden =NO;
self.progress_block.progress = (float) percentDone/100+progressValue;
}];
}
In the line that I have marked as ERROR HERE in the code, there is an error warning (Expected ":"), but I can't find out why.
Any help is welcome.
From the looks of it, you never closed the query bracket:
PFQuery getObjectInBackground.... {
but never closed it using the proper syntax or it looks like you have an extra bracket }. For better practice, you should use proper indentation with if statements or else complications can happen like this. You get lost in the code because you don't know where a statement begins or ends
You should close it after the else statement so:
} ERROR HERE--> else {
NSString *errorString = [[error userInfo] objectForKey:#"error"];
[self showErrorView:errorString];
//stop animating and other stuff
}
}];
I can't troubleshoot because i'm on my iPhone but I would suggest going back and using proper indentation so you can catch your culprit
So I am using Parse to link a user with their twitter account. In the app delegate I have the following:
[PFTwitterUtils initializeWithConsumerKey:CONSUMER_KEY consumerSecret:CONSUMER_SECRET];
Then the button which the user clicks to link the user to facebook calls the following:
-(IBAction)twitterConnectPressed{
NSLog(#"twitter");
[PFTwitterUtils linkUser:[PFUser currentUser] block:^(BOOL succeeded, NSError* error){
NSLog(#"haha");
if(succeeded){
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:#"Done!" message:#"Connected with Twitter!" delegate:self cancelButtonTitle:#"okay" otherButtonTitles: nil];
[alert show];
self.fbButton.backgroundColor = [TGAPublic grey];
}else{
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:#"Oops" message:error.userInfo[#"error"] delegate:self cancelButtonTitle:#"okay" otherButtonTitles: nil];
[alert show];
}
}];
}
However even though linkUser:block: is called it doesn't do anything at all. It doesn't create a pop up to log in to twitter like [PFFacebookUtils linkUser:] does and therefore doesn't end up calling the block either
PFTwitterUtils does not appear to handle all cases on iOS. In particular, if you do not have an account setup (Settings->Twitter) it does not fire up a web view and attempt to used web oauth. Conversely if you have multiple Twitter accounts configured (again in Settings) then it doesn't appear to fire up an action sheet to allow you to select which account you'd like to link.
There's a great tutorial on how to do these things which exposes an extension to PFFacebookUtils here: http://natashatherobot.com/ios-twitter-login-parse/
It does not do linking though, just login, but should be a good basis to add linking.
I've got similar problem with link/unlink methods for both PFFacebookUtils and PFTwitterUtils (v. 1.7.4).
The only way I managed to make it work was to replace them by, unfortunately, messing with internal Parse implementation of authData:
#import "TwitterAuthProvider.h"
#import "PFTwitterUtils.h"
#import "PFUser.h"
static NSString * const kTwitterKey = #"XXX";
static NSString * const kTwitterSecret = #"XXX";
#implementation TwitterAuthProvider
- (instancetype)init {
if ((self = [super init])) {
[PFTwitterUtils initializeWithConsumerKey:kTwitterKey consumerSecret:kTwitterSecret];
}
return self;
}
- (void)setAuthData:(id)twAuthData forUser:(PFUser *)user {
static NSString * const kParseAuthDataKey = #"authData";
static NSString * const kParseLinkedServiceNamesKey = #"linkedServiceNames";
static NSString * const kParseAuthProviderName = #"twitter";
NSMutableDictionary *authData = [[user valueForKey:kParseAuthDataKey] mutableCopy] ?: [NSMutableDictionary dictionary];
authData[kParseAuthProviderName] = twAuthData ?: [NSNull null];
[user setObject:authData forKey:kParseAuthDataKey];
[user setValue:authData forKey:kParseAuthDataKey];
NSMutableSet *linkedServices = [[user valueForKey:kParseLinkedServiceNamesKey] mutableCopy] ?: [NSMutableSet set];
if (twAuthData) {
[linkedServices addObject:kParseAuthProviderName];
} else {
[linkedServices removeObject:kParseAuthProviderName];
}
[user setValue:linkedServices forKey:kParseLinkedServiceNamesKey];
}
- (void)linkWithCompletion:(PFBooleanResultBlock)completion {
NSParameterAssert(completion != nil);
PFUser *user = [PFUser currentUser];
__weak typeof(self) weakSelf = self;
PF_Twitter *twitter = [PFTwitterUtils twitter];
[twitter authorizeWithSuccess:^(void) {
[weakSelf setAuthData:[self twitterAuthData] forUser:user];
[user saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!succeeded) {
//revert
[weakSelf setAuthData:nil forUser:user];
}
completion(succeeded, error);
}];
} failure:^(NSError *error) {
completion(NO, error);
} cancel:^(void) {
completion(NO, nil);
}];
}
- (void)unlinkWithCompletion:(PFBooleanResultBlock)completion {
NSParameterAssert(completion != nil);
PFUser *user = [PFUser currentUser];
[self setAuthData:nil forUser:user];
[user saveInBackgroundWithBlock:completion];
}
- (NSDictionary *)twitterAuthData {
PF_Twitter *twitter = [PFTwitterUtils twitter];
return #{
#"auth_token" : twitter.authToken,
#"auth_token_secret": twitter.authTokenSecret,
#"consumer_key": kTwitterKey,
#"consumer_secret": kTwitterSecret,
#"id": twitter.userId,
#"screen_name": twitter.screenName,
};
}
#end
I want to put an alert view on a 15 minute timer with a YES or NO button. This works fine if the user stays on that view. However the UIAlertView uses a local variable for its title and in the delegate method. When the user changes views the program crashes. Can I make a UIAlertView wait for 15 minutes then implement the delegate method? I tried to put the delegate method in other views, but don't know how to pass the variable with the alert. I've done a little research and think maybe with a Notification or background thread (but background threads I don't think can do UI stuff and an alert is UI)
- (IBAction)sendInAppMsg:(UIButton *)sender
{
....
//****This is the message that calls the UIAlertView on a timer
[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(pause) userInfo:nil repeats:NO];
}
-(void) pause
{
UIAlertView *responseAlert = [[UIAlertView alloc] initWithTitle:#"Success?" message:[NSString stringWithFormat:#"Did you reach %# %#", self.currentDoc.firstName, self.currentDoc.lastName ] delegate:self cancelButtonTitle:nil otherButtonTitles:#"Yes", #"No", nil];
[responseAlert show];
}
-(void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
//Update Parse Cloud with users response
PFObject *contactAttempt = [PFObject objectWithClassName:#"ContactAttempt"];
contactAttempt [#"inApp"] = #"push";
contactAttempt [#"from"] = [[PFUser currentUser] username];
contactAttempt [#"to"] = [NSString stringWithFormat:#"%# %#", self.currentDoc.firstName, self.currentDoc.lastName ];
if (buttonIndex == 0) {
contactAttempt [#"response5"] = #YES;
[contactAttempt saveInBackground];
}
if (buttonIndex == 1)
{
contactAttempt [#"response5"] = #NO;
[contactAttempt saveInBackground];
}
}
//works fine and updates cloud with user response unless user changes views :(
here is my code which is running properly but I want to use LoginWithFacebook default button which is provided by facebook.
Is there any image provided by facebook then please give me suggestion.
thankx in advance.....
#import "FacebbokViewController.h"
#import "UserAppAppDelegate.h"
#interface FacebbokViewController ()
#end
#implementation FacebbokViewController
- (id)init
{
self = [super init];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// button which I have used
UIButton *login=[[UIButton alloc]initWithFrame:CGRectMake(100,100, 200,80)];
[login setBackgroundColor:[UIColor blueColor]];
[login setTitle:#"login With facebook" forState:UIControlStateNormal];
[login setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[login addTarget:self action:#selector(loginWithFacebook) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:login];
}
// method which excute on my button click
-(IBAction)loginWithFacebook{
UserAppAppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
NSArray *permissions = [[NSArray alloc] initWithObjects:
#"email", nil];
if(!appDelegate.session.isOpen)
{
// create a fresh session object
appDelegate.session = [[FBSession alloc] init];
[FBSession setActiveSession: appDelegate.session];
[FBSession openActiveSessionWithReadPermissions:permissions
allowLoginUI:YES
completionHandler:^(FBSession *session, FBSessionState state, NSError *error) {
[self sessionStateChanged:session state:state error:error];
}];
}
else{
NSLog(#"hi");
}
}
- (void)sessionStateChanged:(FBSession *)session state:(FBSessionState) state error:(NSError *)error
{
// If the session was opened successfully
if (!error && state == FBSessionStateOpen){
NSLog(#"Session opened");
[self userData]; // method created to fetch user’s data.
// Show the user the logged-in UI
// do all the things as you have the info you requested from facebook
return;
}
if (state == FBSessionStateClosed || state == FBSessionStateClosedLoginFailed){
// If the session is closed
NSLog(#"Session closed");
// Show the user the logged-out UI
[FBSession.activeSession closeAndClearTokenInformation];
}
// Handle errors
if (error){
NSLog(#"Error");
NSString *alertText;
NSString *alertTitle;
// If the error requires people using an app to make an action outside of the app in order to recover
if ([FBErrorUtility shouldNotifyUserForError:error] == YES){
alertTitle = #"Something went wrong";
alertText = [FBErrorUtility userMessageForError:error];
[self showMessage:alertText withTitle:alertTitle];
} else {
// If the user cancelled login, do nothing
if ([FBErrorUtility errorCategoryForError:error] == FBErrorCategoryUserCancelled) {
NSLog(#"User cancelled login");
// Handle session closures that happen outside of the app
} else if ([FBErrorUtility errorCategoryForError:error] == FBErrorCategoryAuthenticationReopenSession){
alertTitle = #"Session Error";
alertText = #"Your current session is no longer valid. Please log in again.";
[self showMessage:alertText withTitle:alertTitle];
// Here we will handle all other errors with a generic error message.
// We recommend you check our Handling Errors guide for more information
// https://developers.facebook.com/docs/ios/errors/
} else {
//Get more error information from the error
NSDictionary *errorInformation = [[[error.userInfo objectForKey:#"com.facebook.sdk:ParsedJSONResponseKey"] objectForKey:#"body"] objectForKey:#"error"];
// Show the user an error message
alertTitle = #"Something went wrong";
alertText = [NSString stringWithFormat:#"Please retry. \n\n If the problem persists contact us and mention this error code: %#", [errorInformation objectForKey:#"message"]];
[self showMessage:alertText withTitle:alertTitle];
}
}
// Clear this token
[FBSession.activeSession closeAndClearTokenInformation];
}
}
-(void)showMessage:(NSString*)alertMessage withTitle:(NSString*)alertTitle
{
[[[UIAlertView alloc] initWithTitle:alertTitle
message:alertMessage
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil] show];
}
-(void)userData
{
// Start the facebook request
[FBRequestConnection startWithGraphPath:#"me" parameters:[NSDictionary dictionaryWithObject:#"id,email" forKey:#"fields"] HTTPMethod:#"GET" completionHandler:^(FBRequestConnection *connection, NSDictionary<FBGraphUser> *result, NSError *error)
{
//if(!error){
// NSLog(#"result %#",result);
NSString *fbid =result[#"email"];
NSLog(#"result %#",fbid);
//}
}];
}
you can use FBLoginView to have the the functionality as your requirement , you can use like this
FBLoginView *loginView = [[FBLoginView alloc] init];
loginView.frame = YOURFRAME;
[self.view addSubview:loginView];
Take a look at FBLoginView in the Facebook SDK. Official link/tutorial. In the later part of the tutorial, implementing custom views and logging in with API calls is also covered. But note that you would have to design your button on your own in the latter case.