15 |
16 | @property (weak, nonatomic) IBOutlet UITableView * tableView;
17 | @property (weak, nonatomic) IBOutlet UISearchBar *searchBar;
18 | @property (strong, nonatomic) IBOutlet UIRefreshControl * refreshControl;
19 | @property (strong, nonatomic) IBOutlet INInboxNavTitleView * titleView;
20 |
21 | @property (strong, nonatomic) INModelProvider * provider;
22 |
23 | #pragma Actions
24 |
25 | - (IBAction)composeTapped:(id)sender;
26 |
27 | #pragma Refreshing & Thread Ppovider
28 |
29 | - (void)setProvider:(INModelProvider*)provider;
30 | - (void)setProvider:(INModelProvider *)provider andTitle:(NSString*)title;
31 |
32 | @end
33 |
--------------------------------------------------------------------------------
/Explorations/WebPluginTest-PostMessage/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
33 |
34 |
36 |
37 |
38 | Imagine the Message Body Here
39 |
40 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | ----
3 |
4 | Copyright (c) 2014 InboxApp, Inc. and Contributors
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/BigSur/Views/INMessageTableViewCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // INDraftTableViewCell.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/22/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INMessageTableViewCell.h"
10 | #import "INConvenienceCategories.h"
11 |
12 | @implementation INMessageTableViewCell
13 |
14 | - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
15 | {
16 | self = [super initWithStyle:style reuseIdentifier: reuseIdentifier];
17 | if (self) {
18 |
19 | }
20 | return self;
21 | }
22 |
23 | - (void)setMessage:(INMessage *)message
24 | {
25 | _message = message;
26 |
27 | NSMutableArray * addresses = [NSMutableArray array];
28 | [addresses addObjectsFromArray: [_message to]];
29 | [self.participantsLabel setPrefixString: @"" andRecipients:addresses includeMe: YES];
30 | [self.dateLabel setText: [NSString stringForMessageDate: [_message date]]];
31 | [self.bodyLabel setText: [NSString stringByCleaningWhitespaceInString: [_message body]]];
32 | if ([[_message subject] length] > 0)
33 | [self.subjectLabel setText: [_message subject]];
34 | else
35 | [self.subjectLabel setText: @"(no subject)"];
36 | }
37 |
38 | @end
39 |
--------------------------------------------------------------------------------
/Explorations/WebPluginTest/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Palomar",
3 | "version": "0.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "NODE_ENV=development foreman start -e configs/development.env",
8 | "test": "NODE_ENV=test foreman run -e configs/test.env ./node_modules/.bin/mocha --compilers coffee:coffee-script --reporter spec tests/**.coffee"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "express": "3.4.7",
14 | "jade": "*",
15 | "sequelize": "1.7.0-beta.2",
16 | "mysql": "~2.0.0-rc2",
17 | "pg": "~2.8.3",
18 | "coffee-script": "1.6.2",
19 | "coffeescript-compiler": "0.1.1",
20 | "mocha": "~1.15.1",
21 | "supertest": "~0.8.2",
22 | "should": "2.1.1",
23 | "coffee-errors": "~0.8.4",
24 | "aws-sdk": "~2.0.0-rc3",
25 | "connect-coffee-script": "0.1.1",
26 | "connect-less": "0.3.1",
27 | "basic-auth": "0.0.1",
28 | "restler": "~3.1.0",
29 | "crypto-js": "~3.1.2-2",
30 | "underscore": "~1.5.2",
31 | "async": "~0.2.9",
32 | "handlebars": "~1.3.0",
33 | "twilio": "~1.5.0",
34 | "html-to-text": "0.0.2"
35 | },
36 | "engines": {
37 | "node": "0.8.x",
38 | "npm": "1.1.x"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/BigSur/INAppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // INAppDelegate.h
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 4/22/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "JSSlidingViewController.h"
11 | #import "INSidebarViewController.h"
12 | #import "INMailViewController.h"
13 | #import "INSplitViewController.h"
14 |
15 | static NSString * BigSurNamespaceChanged = @"BigSurNamespaceChanged";
16 |
17 | @interface INAppDelegate : UIResponder
18 |
19 | @property (strong, nonatomic) NSString * runtimeLogPath;
20 |
21 | @property (strong, nonatomic) JSSlidingViewController * slidingViewController; // iPhone
22 | @property (strong, nonatomic) INSplitViewController * splitViewController; // iPad
23 | @property (strong, nonatomic) INSidebarViewController * sidebarViewController;
24 | @property (strong, nonatomic) INMailViewController * mainViewController;
25 |
26 | @property (strong, nonatomic) UIWindow * window;
27 | @property (strong, nonatomic) INNamespace * currentNamespace;
28 |
29 | + (INAppDelegate*)current;
30 |
31 | #pragma mark Showing Content
32 |
33 | - (void)showDrafts;
34 | - (void)showThreadsWithTag:(INTag*)tag;
35 |
36 | @end
37 |
--------------------------------------------------------------------------------
/Explorations/WebPluginTest-PostMessage/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Palomar",
3 | "version": "0.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "NODE_ENV=development foreman start -e configs/development.env",
8 | "test": "NODE_ENV=test foreman run -e configs/test.env ./node_modules/.bin/mocha --compilers coffee:coffee-script --reporter spec tests/**.coffee"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "express": "3.4.7",
14 | "jade": "*",
15 | "sequelize": "1.7.0-beta.2",
16 | "mysql": "~2.0.0-rc2",
17 | "pg": "~2.8.3",
18 | "coffee-script": "1.6.2",
19 | "coffeescript-compiler": "0.1.1",
20 | "mocha": "~1.15.1",
21 | "supertest": "~0.8.2",
22 | "should": "2.1.1",
23 | "coffee-errors": "~0.8.4",
24 | "aws-sdk": "~2.0.0-rc3",
25 | "connect-coffee-script": "0.1.1",
26 | "connect-less": "0.3.1",
27 | "basic-auth": "0.0.1",
28 | "restler": "~3.1.0",
29 | "crypto-js": "~3.1.2-2",
30 | "underscore": "~1.5.2",
31 | "async": "~0.2.9",
32 | "handlebars": "~1.3.0",
33 | "twilio": "~1.5.0",
34 | "html-to-text": "0.0.2"
35 | },
36 | "engines": {
37 | "node": "0.8.x",
38 | "npm": "1.1.x"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Explorations/WebPluginTest/app.coffee:
--------------------------------------------------------------------------------
1 | express = require("express")
2 | http = require("http")
3 | path = require("path")
4 | global.fs = require("fs")
5 |
6 | app = express()
7 |
8 | # all environments
9 | app.set "port", process.env.PORT or 3000
10 | app.use express.favicon()
11 | app.use express.json()
12 | app.use express.urlencoded()
13 | app.use express.multipart(defer: false)
14 | app.use express.methodOverride()
15 | app.use app.router
16 | app.use express.static(path.join(__dirname, "public"))
17 | app.use express.errorHandler() if "development" is app.get("env")
18 |
19 | plugin_root = '../BigSur/plugins'
20 | app.get "/plugin-frame/:name", (req, res) ->
21 | res.write("")
22 | res.end()
23 |
24 | app.get "/plugin/:name", (req, res) ->
25 | fs.readFile "public/plugin_base.js", (err, base_data) ->
26 | path = "#{plugin_root}/#{req.params.name}.plugin/plugin.js"
27 | fs.readFile path, (err, data) ->
28 | if data
29 | res.write(base_data)
30 | res.write(data)
31 | res.end()
32 | else
33 | res.end(404)
34 |
35 | server = http.createServer(app)
36 | server.listen app.get("port"), ->
37 | console.log "Express server listening on port " + app.get("port")
38 |
--------------------------------------------------------------------------------
/Explorations/WebPluginTest-PostMessage/app.coffee:
--------------------------------------------------------------------------------
1 | express = require("express")
2 | http = require("http")
3 | path = require("path")
4 | global.fs = require("fs")
5 |
6 | app = express()
7 |
8 | # all environments
9 | app.set "port", process.env.PORT or 3000
10 | app.use express.favicon()
11 | app.use express.json()
12 | app.use express.urlencoded()
13 | app.use express.multipart(defer: false)
14 | app.use express.methodOverride()
15 | app.use app.router
16 | app.use express.static(path.join(__dirname, "public"))
17 | app.use express.errorHandler() if "development" is app.get("env")
18 |
19 | plugin_root = '../BigSur/plugins'
20 | app.get "/plugin-frame/:name", (req, res) ->
21 | res.write("")
22 | res.end()
23 |
24 | app.get "/plugin/:name", (req, res) ->
25 | fs.readFile "public/plugin_base.js", (err, base_data) ->
26 | path = "#{plugin_root}/#{req.params.name}.plugin/plugin.js"
27 | fs.readFile path, (err, data) ->
28 | if data
29 | res.write(base_data)
30 | res.write(data)
31 | res.end()
32 | else
33 | res.end(404)
34 |
35 | server = http.createServer(app)
36 | server.listen app.get("port"), ->
37 | console.log "Express server listening on port " + app.get("port")
38 |
--------------------------------------------------------------------------------
/BigSur/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "40x40",
11 | "scale" : "2x"
12 | },
13 | {
14 | "size" : "60x60",
15 | "idiom" : "iphone",
16 | "filename" : "bigsur@2x.png",
17 | "scale" : "2x"
18 | },
19 | {
20 | "idiom" : "iphone",
21 | "size" : "60x60",
22 | "scale" : "3x"
23 | },
24 | {
25 | "idiom" : "ipad",
26 | "size" : "29x29",
27 | "scale" : "1x"
28 | },
29 | {
30 | "idiom" : "ipad",
31 | "size" : "29x29",
32 | "scale" : "2x"
33 | },
34 | {
35 | "idiom" : "ipad",
36 | "size" : "40x40",
37 | "scale" : "1x"
38 | },
39 | {
40 | "idiom" : "ipad",
41 | "size" : "40x40",
42 | "scale" : "2x"
43 | },
44 | {
45 | "size" : "76x76",
46 | "idiom" : "ipad",
47 | "filename" : "bigsur76.png",
48 | "scale" : "1x"
49 | },
50 | {
51 | "size" : "76x76",
52 | "idiom" : "ipad",
53 | "filename" : "bigsur76@2x.png",
54 | "scale" : "2x"
55 | }
56 | ],
57 | "info" : {
58 | "version" : 1,
59 | "author" : "xcode"
60 | }
61 | }
--------------------------------------------------------------------------------
/Explorations/TinderPaging/TinderPaging/INPagingContainerViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // INPagingContainerViewController.h
3 | // TinderPaging
4 | //
5 | // Created by Ben Gotow on 6/19/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | typedef enum : NSUInteger {
12 | Hidden,
13 | Appearing,
14 | Visible,
15 | Disappearing
16 | } VisibilityState;
17 |
18 | @class INPagingContainerViewController;
19 |
20 | @protocol INPagingChildViewController
21 |
22 | - (void)prepareHeaderViews:(INPagingContainerViewController*)controller;
23 |
24 | @end
25 |
26 | @interface INPagingContainerViewController : UIViewController
27 | {
28 | NSMutableDictionary * _visibility;
29 | }
30 |
31 | @property (nonatomic, strong) UIScrollView * scrollView;
32 | @property (nonatomic, strong) UIView * headerContainerView;
33 | @property (nonatomic, strong) NSMutableDictionary * headerViews;
34 | @property (nonatomic, strong) NSMutableDictionary * headerRules;
35 |
36 | @property (nonatomic, strong) NSArray * viewControllers;
37 |
38 |
39 | #pragma mark Header Views
40 |
41 | - (void)declareHeaderView:(UIView*)v withName:(NSString*)name;
42 | - (void)declareCenter:(CGPoint)p of:(NSString*)name forController:(UIViewController*)controller;
43 |
44 |
45 | @end
46 |
--------------------------------------------------------------------------------
/BigSur/Controllers/INComposeViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // INComposeViewController.h
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/5/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "INComposeRecipientRowView.h"
11 | #import "INComposeSubjectRowView.h"
12 | #import "INComposeAttachmentsRowView.h"
13 | #import "INPlaceholderTextView.h"
14 |
15 | @interface INComposeViewController : UIViewController
16 | {
17 | NSMutableArray * _verticalLayoutConstraints;
18 | UIPopoverController * _attachmentsPopover;
19 |
20 | }
21 |
22 | @property (nonatomic, strong) INDraft * draft;
23 |
24 | @property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
25 | @property (weak, nonatomic) NSMutableArray *scrollViewRows;
26 | @property (strong, nonatomic) INPlaceholderTextView * bodyTextView;
27 | @property (strong, nonatomic) INComposeRecipientRowView * toRecipientsView;
28 | @property (strong, nonatomic) INComposeRecipientRowView * ccBccRecipientsView;
29 | @property (strong, nonatomic) INComposeSubjectRowView * subjectView;
30 | @property (strong, nonatomic) INComposeAttachmentsRowView * attachmentsView;
31 |
32 | - (id)initWithDraft:(INMessage*)draft;
33 |
34 | @end
35 |
--------------------------------------------------------------------------------
/Explorations/WebPluginTest/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
42 |
43 |
45 |
46 |
47 | Imagine the Message Body Here
48 |
49 |
--------------------------------------------------------------------------------
/BigSur/INDeltaSyncEngine.h:
--------------------------------------------------------------------------------
1 | //
2 | // INDeltaSyncEngine.h
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/27/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | static NSString * INSyncFinishedNotification = @"INSyncFinishedNotification";
12 |
13 | @interface INDeltaSyncEngine : NSObject
14 |
15 | @property (nonatomic, strong) NSMutableArray * syncOperations;
16 | @property (nonatomic, strong) NSDate * syncDate;
17 | @property (nonatomic, strong) NSTimer * syncTimer;
18 |
19 | @property (nonatomic, strong) UILocalNotification * unreadNotification;
20 |
21 | /* Creates a new instance of the sync engine with the desired configuration.
22 |
23 | @param configuration Currenty unused.
24 | @return A configured sync engine instance
25 | */
26 | - (id)initWithConfiguration:(NSDictionary*)config;
27 |
28 | /* Start a sync, and call the callback when sync has completely
29 | finished or failed with an error.
30 |
31 | @param callback A block to invoke when the sync has finished or has failed.
32 | If a sync is already in progress or there is no current namespace,
33 | the callback is called immediately with success=NO, error=nil.
34 | */
35 | - (void)syncWithCallback:(ErrorBlock)callback;
36 |
37 | /* Clear all sync state, usually called during the logout process. */
38 | - (void)resetSyncState;
39 |
40 | @end
41 |
--------------------------------------------------------------------------------
/BigSur/Categories/NSObject+AssociatedObjects.m:
--------------------------------------------------------------------------------
1 | //
2 | // NSObject+AssociatedObjects.m
3 | //
4 | // Created by Andy Matuschak on 8/27/09.
5 | // Public domain because I love you.
6 | //
7 |
8 | #import "NSObject+AssociatedObjects.h"
9 | #import
10 |
11 | @implementation NSObject (AMAssociatedObjects)
12 |
13 | - (void)associateValue:(id)value withKey:(void *)key
14 | {
15 | objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_RETAIN);
16 | }
17 |
18 | - (void)weaklyAssociateValue:(id)value withKey:(void *)key
19 | {
20 | objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_ASSIGN);
21 | }
22 |
23 | - (id)associatedValueForKey:(void *)key
24 | {
25 | return objc_getAssociatedObject(self, key);
26 | }
27 |
28 | #pragma clang diagnostic push
29 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
30 |
31 | - (void)performSelectorOnMainThreadOnce:(SEL)selector
32 | {
33 | [self associateValue:[NSNumber numberWithBool:YES] withKey:(void *)selector];
34 |
35 | dispatch_async(dispatch_get_main_queue(), ^{
36 | if ([self associatedValueForKey:(void *)selector]) {
37 | [self performSelector:selector];
38 | [self associateValue:nil withKey:(void *)selector];
39 | }
40 | });
41 | }
42 |
43 | - (void)markPerformedSelector:(SEL)selector
44 | {
45 | [self associateValue:nil withKey:(void *)selector];
46 | }
47 |
48 | #pragma clang diagnostic pop
49 |
50 | @end
51 |
--------------------------------------------------------------------------------
/BigSur/Views/INMessageCollectionViewCell.h:
--------------------------------------------------------------------------------
1 | //
2 | // INMessageCollectionViewCell.h
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/1/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @class INMessageCollectionViewCell;
12 |
13 | typedef void (^ CellBlock)();
14 |
15 | @interface INMessageCollectionViewCell : UICollectionViewCell
16 |
17 | @property (nonatomic, strong) INMessage * message;
18 | @property (nonatomic, strong) CellBlock messageHeightDeterminedBlock;
19 |
20 | @property (nonatomic, strong) NSMutableArray * bodySegments;
21 |
22 | @property (nonatomic, weak) IBOutlet INMessageContentView * bodyView;
23 | @property (nonatomic, weak) IBOutlet UIButton * fromProfileButton;
24 | @property (nonatomic, weak) IBOutlet INRecipientsLabel * fromField;
25 | @property (nonatomic, weak) IBOutlet INRecipientsLabel * toField;
26 | @property (nonatomic, weak) IBOutlet UILabel * dateField;
27 | @property (nonatomic, weak) IBOutlet UIButton * draftDeleteButton;
28 | @property (nonatomic, weak) IBOutlet UIButton * draftEditButton;
29 | @property (nonatomic, weak) IBOutlet UIView * draftOptionsView;
30 | @property (nonatomic, weak) IBOutlet UIView * headerContainerView;
31 | @property (nonatomic, strong) CALayer * headerBorderLayer;
32 | @property (nonatomic, assign) BOOL collapsed;
33 |
34 | + (float)cachedHeightForMessage:(INMessage*)message;
35 |
36 | @end
37 |
--------------------------------------------------------------------------------
/BigSur/Controllers/INThreadViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // INThreadViewController.h
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 4/30/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "INTagsView.h"
11 | #import "BPopdownButton.h"
12 |
13 | @interface INThreadViewController : UIViewController
14 |
15 | @property (weak, nonatomic) IBOutlet UICollectionView * collectionView;
16 | @property (weak, nonatomic) IBOutlet UILabel * threadSubjectLabel;
17 | @property (weak, nonatomic) IBOutlet UIView *threadHeaderView;
18 | @property (weak, nonatomic) IBOutlet INTagsView *tagsView;
19 | @property (weak, nonatomic) IBOutlet UIView *errorView;
20 | @property (weak, nonatomic) IBOutlet UILabel *errorLabel;
21 |
22 | @property (nonatomic, strong) INModelProvider * messageProvider;
23 | @property (nonatomic, strong) INModelProvider * draftProvider;
24 |
25 | @property (nonatomic, strong) INThread * thread;
26 | @property (nonatomic, strong) NSArray * messages;
27 | @property (nonatomic, strong) NSMutableDictionary * messagesCollapsedState;
28 | @property (nonatomic, strong) NSArray * drafts;
29 |
30 | @property (nonatomic, strong) NSMutableArray * actions;
31 | @property (nonatomic, strong) BPopdownButton * actionsButton;
32 |
33 | - (id)initWithThread:(INThread*)thread;
34 |
35 | @end
36 |
--------------------------------------------------------------------------------
/BigSur/Views/INSidebarTableViewCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // INSidebarTableViewCell.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/13/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INSidebarTableViewCell.h"
10 |
11 | @implementation INSidebarTableViewCell
12 |
13 | - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
14 | {
15 | self = [super initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:reuseIdentifier];
16 | if (self) {
17 | [[self textLabel] setFont: [UIFont fontWithName:@"HelveticaNeue-Light" size:16]];
18 | [[self textLabel] setTextColor: [UIColor whiteColor]];
19 | [[self detailTextLabel] setFont: [UIFont fontWithName:@"HelveticaNeue-Medium" size:14]];
20 | [[self detailTextLabel] setTextColor: [UIColor colorWithWhite:1 alpha:0.5]];
21 | [[self detailTextLabel] setTextAlignment: NSTextAlignmentRight];
22 | [self setBackgroundColor: [UIColor colorWithWhite:0 alpha:0.05]];
23 |
24 | [self setSelectedBackgroundView: [[UIView alloc] init]];
25 | [[self selectedBackgroundView] setBackgroundColor: [UIColor colorWithWhite:0 alpha:0.3]];
26 | }
27 | return self;
28 | }
29 |
30 | - (void)awakeFromNib
31 | {
32 | // Initialization code
33 | }
34 |
35 | - (void)layoutSubviews
36 | {
37 | [super layoutSubviews];
38 | [[self imageView] setContentMode: UIViewContentModeCenter];
39 |
40 | float h = self.frame.size.height;
41 | [[self imageView] setFrame: CGRectMake(8, (h-20)/2 + 1, 20, 20)];
42 | [[self textLabel] setFrame: CGRectMake(8 + 20 + 8, 0, self.frame.size.width, h-1)];
43 | [[self detailTextLabel] setFrame: CGRectMake(self.frame.size.width - 66, 0, 50, h)];
44 | }
45 |
46 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated
47 | {
48 | [super setSelected:selected animated:animated];
49 | }
50 |
51 | @end
52 |
--------------------------------------------------------------------------------
/arclib/src/InboxUncrustifyLintEngine.php:
--------------------------------------------------------------------------------
1 | getPaths();
12 |
13 | $uncrustify_linter = new InboxUncrustifyLinter();
14 |
15 | // Remove any paths that don't exist before we add paths to linters. We want
16 | // to do this for linters that operate on file contents because the
17 | // generated list of paths will include deleted paths when a file is
18 | // removed.
19 | foreach ($paths as $key => $path) {
20 | if (!$this->pathExists($path)) {
21 | unset($paths[$key]);
22 | }
23 | }
24 |
25 | foreach ($paths as $path) {
26 | if (!preg_match('/^.*\.(m|h)$/i', $path)) {
27 | // This isn't a python file, so don't try to apply the PyLint linter
28 | // to it.
29 | continue;
30 | } else {
31 | echo $path;
32 | }
33 |
34 | // Add the path, to tell the linter it should examine the source code
35 | // to try to find problems.
36 | $uncrustify_linter->addPath($path);
37 | }
38 |
39 | // We only built one linter, but you can build more than one (e.g., a
40 | // Javascript linter for JS), and return a list of linters to execute. You
41 | // can also add a path to more than one linter (for example, if you want
42 | // to run a Python linter and a more general text linter on every .py file).
43 |
44 | return array(
45 | $uncrustify_linter
46 | );
47 | }
48 |
49 | }
--------------------------------------------------------------------------------
/BigSur/BigSur-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | Inbox
9 | CFBundleExecutable
10 | ${EXECUTABLE_NAME}
11 | CFBundleIdentifier
12 | com.inbox.${PRODUCT_NAME:rfc1034identifier}
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | ${PRODUCT_NAME}
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleURLTypes
24 |
25 |
26 | CFBundleTypeRole
27 | Editor
28 | CFBundleURLName
29 | inbox-api
30 | CFBundleURLSchemes
31 |
32 |
33 |
34 | CFBundleVersion
35 | 1.0
36 | INAPIPath
37 | https://api.inboxapp.com/
38 | INAppID
39 |
40 | LSRequiresIPhoneOS
41 |
42 | UIBackgroundModes
43 |
44 | fetch
45 |
46 | UIRequiredDeviceCapabilities
47 |
48 | armv7
49 |
50 | UISupportedInterfaceOrientations
51 |
52 | UIInterfaceOrientationPortrait
53 | UIInterfaceOrientationLandscapeLeft
54 | UIInterfaceOrientationLandscapeRight
55 |
56 | UIViewControllerBasedStatusBarAppearance
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/BigSur/INPluginManager.h:
--------------------------------------------------------------------------------
1 | //
2 | // INPluginManager.h
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 6/5/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 |
12 | @protocol INGlobalExports
13 |
14 | - (void)alert:(NSString*)alert;
15 | - (void)log:(NSString*)msg;
16 | - (void)openURL:(NSString*)url;
17 | - (void)getJSON:(NSString*)url withCallback:(JSValue*)callback;
18 |
19 | @end
20 |
21 | @protocol INThreadExports
22 | @property (nonatomic, strong) NSString * subject;
23 | @property (nonatomic, strong) NSString * snippet;
24 | @property (nonatomic, strong) NSArray * participants;
25 | @property (nonatomic, strong) NSDate * lastMessageDate;
26 | @property (nonatomic, assign) BOOL unread;
27 | - (NSArray*)messages;
28 | @end
29 |
30 | @protocol INMessageExports
31 | @property (nonatomic, strong) NSString * body;
32 | @property (nonatomic, strong) NSDate * date;
33 | @property (nonatomic, strong) NSString * subject;
34 | @property (nonatomic, strong) NSArray * from;
35 | @property (nonatomic, strong) NSArray * to;
36 | @end
37 |
38 | @interface INThread (PluginSupport)
39 | - (NSArray*)messages;
40 | @end
41 |
42 | @interface INMessage (PluginSupport)
43 | @end
44 |
45 |
46 | @interface INPluginManager : NSObject
47 |
48 | @property (nonatomic, strong) NSMutableDictionary * pluginContexts;
49 | @property (nonatomic, strong) NSMutableDictionary * pluginNamesByRole;
50 | @property (nonatomic, strong) dispatch_queue_t pluginBackgroundQueue;
51 | @property (nonatomic, strong) NSString * pluginCompiledResources;
52 |
53 | + (INPluginManager *)shared;
54 |
55 | - (NSURL*)webViewBaseURL;
56 |
57 | - (JSContext*)contextForPluginWithName:(NSString*)name;
58 | - (NSArray*)pluginNamesForRole:(NSString*)role;
59 |
60 | #pragma Exposed Methods
61 |
62 | - (void)alert:(NSString*)alert;
63 | - (void)log:(NSString*)msg;
64 | - (void)openURL:(NSString*)url;
65 | - (void)getJSON:(NSString*)url withCallback:(JSValue*)callback;
66 |
67 | @end
68 |
--------------------------------------------------------------------------------
/BigSur/Controllers/INComposeViewController.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/BigSur/Views/INLeftJustifiedFlowLayout.m:
--------------------------------------------------------------------------------
1 | //
2 | // INLeftJustifiedFlowLayout.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/5/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INLeftJustifiedFlowLayout.h"
10 |
11 | @implementation INLeftJustifiedFlowLayout
12 |
13 | - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
14 | {
15 | NSArray * layoutAttributes = [super layoutAttributesForElementsInRect: rect];
16 | for (UICollectionViewLayoutAttributes * attributes in layoutAttributes)
17 | [self adjustAttributes: attributes];
18 | return layoutAttributes;
19 | }
20 |
21 | - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
22 | {
23 | id attributes = (UICollectionViewLayoutAttributes*)[super layoutAttributesForItemAtIndexPath: indexPath];
24 | [self adjustAttributes: attributes];
25 | return attributes;
26 | }
27 |
28 | - (void)adjustAttributes:(UICollectionViewLayoutAttributes *)attributes
29 | {
30 | // Important: This function calls itself on other cells in some scenarios. It must be FAST.
31 | // Don't do time-consuming things here.
32 | NSIndexPath * indexPath = [attributes indexPath];
33 |
34 | // make sure that we always left-justify the cell in a non-full row. The default flow layout
35 | // implementation sort of spreads the cells across the center and increases spacing. This is
36 | // kind of strange.
37 | CGRect previousFrame = CGRectZero;
38 | if ([indexPath row] > 0) {
39 | NSIndexPath* previousIndexPath = [NSIndexPath indexPathForItem:indexPath.item-1 inSection:indexPath.section];
40 | if ([previousIndexPath row] != NSNotFound)
41 | previousFrame = [self layoutAttributesForItemAtIndexPath:previousIndexPath].frame;
42 | }
43 | CGRect frame = attributes.frame;
44 |
45 | if (previousFrame.origin.y == frame.origin.y)
46 | frame.origin.x = previousFrame.origin.x + previousFrame.size.width + self.minimumInteritemSpacing;
47 | else
48 | frame.origin.x = [self sectionInset].left;
49 |
50 | [attributes setFrame: frame];
51 | }
52 |
53 |
54 | @end
55 |
--------------------------------------------------------------------------------
/BigSur/Views/INInboxNavTitleView.m:
--------------------------------------------------------------------------------
1 | //
2 | // INInboxNavTitleView.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/13/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INInboxNavTitleView.h"
10 | #import "UIView+FrameAdditions.h"
11 |
12 | @implementation INInboxNavTitleView
13 |
14 | - (id)initWithFrame:(CGRect)frame
15 | {
16 | self = [super initWithFrame:frame];
17 | if (self) {
18 | _titleLabel = [[UILabel alloc] initWithFrame: CGRectMake(0, 0, 0, 30)];
19 | [_titleLabel setFont: [[UINavigationBar appearance] titleTextAttributes][NSFontAttributeName]];
20 | [_titleLabel setTextColor: [[UINavigationBar appearance] titleTextAttributes][NSForegroundColorAttributeName]];
21 | _titleUnreadLabel = [[UILabel alloc] initWithFrame: CGRectMake(0, 8, 0, 18)];
22 | [_titleUnreadLabel setFont: [UIFont fontWithName:@"HelveticaNeue" size: 14]];
23 | [_titleUnreadLabel setTextColor: [UIColor colorWithWhite:0.4 alpha:1]];
24 | [_titleUnreadLabel setTextAlignment: NSTextAlignmentCenter];
25 | [_titleUnreadLabel setBackgroundColor: [UIColor whiteColor]];
26 |
27 | [self addSubview: _titleLabel];
28 | [self addSubview: _titleUnreadLabel];
29 | }
30 | return self;
31 | }
32 |
33 | - (void)setTitle:(NSString*)title andUnreadCount:(long)count
34 | {
35 | float x = 0;
36 | float width = ceilf([_titleLabel.text sizeWithAttributes: @{NSFontAttributeName: [_titleLabel font]}].width) + 6;
37 | if (width > 180)
38 | width = 180;
39 |
40 | [_titleLabel setText: title];
41 | [_titleLabel in_setFrameWidth: width];
42 | x += [_titleLabel frame].size.width;
43 |
44 | if ((count == NSNotFound) || (count == 0)) {
45 | [_titleUnreadLabel in_setFrameWidth: 0];
46 | } else {
47 | [_titleUnreadLabel setText: [NSString stringWithFormat: @"%ld", count]];
48 | [_titleUnreadLabel in_setFrameWidth: ceilf([_titleUnreadLabel.text sizeWithAttributes: @{NSFontAttributeName: [_titleUnreadLabel font]}].width) + 6];
49 | [_titleUnreadLabel in_setFrameX: x];
50 | x += [_titleUnreadLabel frame].size.width;
51 | }
52 |
53 | [self setBounds:CGRectMake(0, 0, x, 30)];
54 | }
55 |
56 |
57 | @end
58 |
--------------------------------------------------------------------------------
/BigSur/Debugging/INStressTestSyncEngine.m:
--------------------------------------------------------------------------------
1 | //
2 | // INTroubleshootingSyncEngine.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 6/13/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INStressTestSyncEngine.h"
10 |
11 | @implementation INStressTestSyncEngine
12 |
13 | - (id)initWithConfiguration:(NSDictionary*)config
14 | {
15 | self = [super init];
16 | if (self) {
17 | _timer = [NSTimer scheduledTimerWithTimeInterval:0.025 target:self selector:@selector(update) userInfo:nil repeats:YES];
18 | [[NSRunLoop currentRunLoop] addTimer:_timer forMode:UITrackingRunLoopMode];
19 |
20 | dispatch_async(dispatch_get_main_queue(), ^{
21 | INNamespace * namespace = [[INNamespace alloc] init];
22 | [namespace updateWithResourceDictionary:@{@"email_address":@"bengotow@gmail.com", @"id":@"namespace_id"}];
23 | [[INDatabaseManager shared] persistModel: namespace];
24 | [[NSNotificationCenter defaultCenter] postNotificationName:INNamespacesChangedNotification object:nil];
25 | });
26 |
27 | _threads = [NSMutableArray array];
28 | for (int ii = 0; ii < 200; ii ++) {
29 | INThread * thread = [[INThread alloc] init];
30 | NSDictionary * d = @{@"id":[NSString stringWithFormat: @"%d", ii],
31 | @"subject":@"Hello World",
32 | @"namespace":@"namespace_id",
33 | @"tags": @[ @{@"id":INTagIDInbox, @"name":@"inbox"},
34 | @{@"id":INTagIDUnread, @"name":@"unread"}
35 | ],
36 | @"last_message_date": @([[NSDate date] timeIntervalSince1970]),
37 | @"from": @[@{@"email": @"bengotow@gmail.com"}],
38 | @"participants":@[@{@"email": @"ben@inboxapp.com", @"name": @"Ben Gotow"}]
39 | };
40 | [thread updateWithResourceDictionary:d];
41 | [_threads addObject: thread];
42 | }
43 | }
44 | return self;
45 | }
46 |
47 | - (BOOL)providesCompleteCacheOf:(Class)klass
48 | {
49 | return YES;
50 | }
51 |
52 | - (void)resetSyncState
53 | {
54 |
55 | }
56 |
57 | - (void)update
58 | {
59 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
60 | for (INThread * thread in _threads) {
61 | NSString * r = [NSString stringWithFormat:@"%d",rand()];
62 | [thread setSubject: r];
63 | }
64 | [[INDatabaseManager shared] persistModels: _threads];
65 | });
66 | }
67 |
68 | @end
69 |
--------------------------------------------------------------------------------
/BigSur/plugins/swa.plugin/plugin.js:
--------------------------------------------------------------------------------
1 | plugin.isAvailableForMessage = function(message) {
2 | return (message.from[0].email.indexOf('luv.southwest.com') != -1);
3 | }
4 |
5 | plugin.initialHTMLForMessage = function(message) {
6 | return "
Loading flight status...
";
7 | }
8 |
9 | plugin.finalHTMLForMessage = function(message, callback) {
10 | $ = cheerio.load(message.body);
11 |
12 | var passengerName = $('table tbody tr td table tbody tr td table tbody tr td tbody tr td div').eq(4).text().trim()
13 | var flightNumber = $('table tbody tr td table tbody tr td table tbody tr td tbody tr td div').eq(10).text().trim()
14 | var dateString = $('table tbody tr td table tbody tr td table tbody tr td tbody tr td div').eq(9).text().trim()
15 | var dateYear = (new Date).getFullYear();
16 | var date = new Date(Date.parse(dateString + " " + dateYear))
17 | var dateMonth = date.getMonth() + 1;
18 | var dateDay = date.getDate();
19 |
20 |
21 | var url = "https://api.flightstats.com/flex/flightstatus/rest/v2/json/flight/status/SWA/"+flightNumber+"/dep/"+dateYear+"/"+dateMonth+"/"+dateDay+"?appId=b16bee5a&appKey=8027de581e3af06afab756d41b50c935&utc=false";
22 |
23 | app.getJSONWithCallback(url, function(json, error) {
24 | var result = ""
25 | if (json['error']) {
26 | result = json.error.errorMessage;
27 | } else {
28 | var flight = json.flightStatuses[0];
29 | var departTerminal = flight.airportResources.departureTerminal;
30 | var departAirport = flight.departureAirportFsCode;
31 | var publishedDeparture = flight.operationalTimes.publishedDeparture.dateLocal;
32 | var scheduledDeparture = flight.operationalTimes.scheduledGateDeparture.dateLocal;
33 | result = "Departing from "+departAirport+" Terminal "+departTerminal+" at "+scheduledDeparture;
34 |
35 | }
36 | app.log(JSON.stringify(json));
37 | var html = "
"+result+"
";
38 | callback(html);
39 | });
40 | }
--------------------------------------------------------------------------------
/Explorations/TinderPaging/TinderPaging/ViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.m
3 | // TinderPaging
4 | //
5 | // Created by Ben Gotow on 6/19/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "ViewController.h"
10 |
11 |
12 | @implementation ViewController
13 |
14 | - (void)viewWillAppear:(BOOL)animated
15 | {
16 | UIColor * lightGreen = [UIColor colorWithRed:0.5 green:1 blue:0.5 alpha:1];
17 | NSAssert([self.view.backgroundColor isEqual: lightGreen] == NO, @"Already fired willAppear!");
18 | [UIView animateWithDuration:(animated ? 0.3 : 0) animations:^{
19 | [UIView setAnimationBeginsFromCurrentState: YES];
20 | [self.view setBackgroundColor: lightGreen];
21 | }];
22 | }
23 |
24 | - (void)viewDidAppear:(BOOL)animated
25 | {
26 | NSAssert([self.view.backgroundColor isEqual: [UIColor greenColor]] == NO, @"Already fired didAppear!");
27 | [UIView animateWithDuration:(animated ? 0.3 : 0) animations:^{
28 | [UIView setAnimationBeginsFromCurrentState: YES];
29 | [self.view setBackgroundColor: [UIColor greenColor]];
30 | }];
31 | }
32 |
33 | - (void)viewWillDisappear:(BOOL)animated
34 | {
35 | UIColor * lightRed = [UIColor colorWithRed:1 green:0.5 blue:0.5 alpha:1];
36 | NSAssert([self.view.backgroundColor isEqual: lightRed] == NO, @"Already fired willDisppear!");
37 | [UIView animateWithDuration:(animated ? 0.3 : 0) animations:^{
38 | [UIView setAnimationBeginsFromCurrentState: YES];
39 | [self.view setBackgroundColor: lightRed];
40 | }];
41 | }
42 |
43 | - (void)viewDidDisappear:(BOOL)animated
44 | {
45 | NSAssert([self.view.backgroundColor isEqual: [UIColor redColor]] == NO, @"Already fired didDisappear!");
46 | [UIView animateWithDuration:(animated ? 0.3 : 0) animations:^{
47 | [UIView setAnimationBeginsFromCurrentState: YES];
48 | [self.view setBackgroundColor: [UIColor redColor]];
49 | }];
50 | }
51 |
52 | - (void)viewDidLoad
53 | {
54 | [super viewDidLoad];
55 | }
56 |
57 | - (void)didReceiveMemoryWarning
58 | {
59 | [super didReceiveMemoryWarning];
60 | }
61 |
62 | - (void)prepareHeaderViews:(INPagingContainerViewController*)controller
63 | {
64 | UILabel * label = [[UILabel alloc] initWithFrame: CGRectMake(0, 0, 100, 20)];
65 | [label setText: @"Label Here!"];
66 |
67 | [controller declareHeaderView:label withName:@"label"];
68 | }
69 |
70 | @end
71 |
--------------------------------------------------------------------------------
/BigSur/Controllers/INRootViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // INRootViewController.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/12/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INRootViewController.h"
10 | #import "JSSlidingViewController.h"
11 | #import "INAppDelegate.h"
12 |
13 | @implementation INRootViewController
14 |
15 | - (void)viewDidLoad
16 | {
17 | [super viewDidLoad];
18 |
19 | if ([self parentSlidingViewController]) {
20 | UIImage * sidebarIcon = [[UIImage imageNamed: @"icon_sidebar.png"] imageWithRenderingMode: UIImageRenderingModeAlwaysOriginal];
21 | UIBarButtonItem * sidebar = [[UIBarButtonItem alloc] initWithImage:sidebarIcon style:UIBarButtonItemStyleBordered target:self action:@selector(sidebarTapped:)];
22 | [sidebar setTintColor: [UIColor darkGrayColor]];
23 | [self.navigationItem setLeftBarButtonItem: sidebar];
24 | }
25 | }
26 |
27 | - (void)viewDidAppear:(BOOL)animated
28 | {
29 | }
30 |
31 | - (void)viewWillDisappear:(BOOL)animated
32 | {
33 | }
34 |
35 | - (JSSlidingViewController*)parentSlidingViewController
36 | {
37 | JSSlidingViewController * js = (JSSlidingViewController *)[self parentViewController];
38 | if ([js isKindOfClass: [JSSlidingViewController class]] == NO)
39 | js = (JSSlidingViewController *)[js parentViewController];
40 | return js;
41 | }
42 |
43 | - (IBAction)sidebarTapped:(id)sender
44 | {
45 | JSSlidingViewController * js = [self parentSlidingViewController];
46 | if ([js isOpen])
47 | [js closeSlider:YES completion:nil];
48 | else
49 | [js openSlider:YES completion:nil];
50 | }
51 |
52 | - (void)smartPushViewController:(UIViewController*)vc animated:(BOOL)animated
53 | {
54 | if (self.navigationController)
55 | [self.navigationController pushViewController:vc animated:animated];
56 | else {
57 | if ([self.parentViewController isKindOfClass: [INSplitViewController class]]){
58 | INSplitViewController * split = (INSplitViewController*)self.parentViewController;
59 | if ([[split paneViewControllers] count] == 3) {
60 | [split popPane: NO];
61 | [split pushViewController: vc animated: NO];
62 | } else {
63 | [split pushViewController: vc animated: animated];
64 | }
65 | }
66 | }
67 | }
68 |
69 | - (void)smartPresentViewController:(UIViewController*)vc animated:(BOOL)animated
70 | {
71 | [self presentViewController:vc animated:animated completion:NULL];
72 | }
73 |
74 | @end
75 |
--------------------------------------------------------------------------------
/arclib/src/InboxUncrustifyLinter.php:
--------------------------------------------------------------------------------
1 | getConfiguredScript();
7 | $root = $this->getEngine()->getWorkingCopy()->getProjectRoot();
8 |
9 | $futures = array();
10 | foreach ($paths as $path) {
11 | $future = new ExecFuture('%C %s', $script, $path);
12 | $future->setCWD($root);
13 | $futures[$path] = $future;
14 | }
15 |
16 | foreach (Futures($futures)->limit(4) as $path => $future) {
17 | list($stdout) = $future->resolvex();
18 | $this->output[$path] = $stdout;
19 | }
20 | }
21 |
22 | public function getLinterName() {
23 | return "Inbox Uncrustify Linter";
24 | }
25 |
26 | public function lintPath($path) {
27 | // Not necessary to do anything because uncrustify writes directly stderr when there's a problem
28 |
29 | // $dict = array(
30 | // 'path' => idx($match, 'file', $path),
31 | // 'line' => $line,
32 | // 'char' => $char,
33 | // 'code' => idx($match, 'code', $this->getLinterName()),
34 | // 'severity' => $this->getMatchSeverity($match),
35 | // 'name' => idx($match, 'name', 'Lint'),
36 | // 'description' => idx($match, 'message', 'Undefined Lint Message'),
37 | // );
38 | //
39 | // $original = idx($match, 'original');
40 | // if ($original !== null) {
41 | // $dict['original'] = $original;
42 | // }
43 | //
44 | // $replacement = idx($match, 'replacement');
45 | // if ($replacement !== null) {
46 | // $dict['replacement'] = $replacement;
47 | // }
48 | //
49 | // $lint = ArcanistLintMessage::newFromDictionary($dict);
50 | // $this->addLintMessage($lint);
51 | }
52 |
53 | private function getConfiguredScript() {
54 | $key = 'lint.uncrustify.script';
55 | $config = $this->getEngine()
56 | ->getConfigurationManager()
57 | ->getConfigFromAnySource($key);
58 |
59 | if (!$config) {
60 | throw new ArcanistUsageException(
61 | "InboxUncrustifyLinter: ".
62 | "You must configure '{$key}' to point to an uncrustify script to execute.");
63 | }
64 |
65 | // NOTE: No additional validation since the "script" can be some random
66 | // shell command and/or include flags, so it does not need to point to some
67 | // file on disk.
68 |
69 | return $config;
70 | }
71 | }
72 |
73 | ?>
--------------------------------------------------------------------------------
/BigSur/Views/INTagsView.m:
--------------------------------------------------------------------------------
1 | //
2 | // INTagsView.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/14/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INTagsView.h"
10 | #import "UIView+FrameAdditions.h"
11 |
12 | @implementation INTagsView
13 |
14 | - (id)initWithFrame:(CGRect)frame
15 | {
16 | self = [super initWithFrame:frame];
17 | if (self) {
18 | [self setup];
19 | }
20 | return self;
21 | }
22 |
23 | - (void)awakeFromNib
24 | {
25 | [self setup];
26 | }
27 |
28 | - (void)setup
29 | {
30 | _tagViews = [NSMutableArray array];
31 | }
32 |
33 | - (void)setTags:(NSArray*)tags
34 | {
35 | [self setTags:tags withOmmittedTagIDs:@[]];
36 | }
37 |
38 | - (void)setTags:(NSArray*)tags withOmmittedTagIDs:(NSArray*)omitted
39 | {
40 | [_tagViews makeObjectsPerformSelector: @selector(removeFromSuperview)];
41 | [_tagViews removeAllObjects];
42 |
43 | for (INTag * tag in tags) {
44 | if ([omitted containsObject: [tag ID]])
45 | continue;
46 |
47 | UILabel * tagLabel = [[UILabel alloc] initWithFrame: CGRectZero];
48 | [tagLabel setTextColor: [UIColor whiteColor]];
49 | [tagLabel setFont: [UIFont fontWithName:@"HelveticaNeue-Medium" size:9]];
50 | [tagLabel setBackgroundColor: [tag color]];
51 | [[tagLabel layer] setCornerRadius: 3];
52 | [tagLabel setClipsToBounds: YES];
53 | [tagLabel setText: [[tag name] uppercaseString]];
54 | [tagLabel setTextAlignment: NSTextAlignmentCenter];
55 |
56 | CGSize textSize = [[tagLabel text] sizeWithAttributes: @{NSFontAttributeName: [tagLabel font]}];
57 | [tagLabel in_setFrameSize: CGSizeMake(textSize.width + 8, 13)];
58 | [self addSubview: tagLabel];
59 | [_tagViews addObject: tagLabel];
60 | }
61 |
62 | [self setNeedsLayout];
63 | [self layoutIfNeeded];
64 | }
65 |
66 | - (void)layoutSubviews
67 | {
68 | NSAssert((_alignment == NSTextAlignmentLeft) || (_alignment == NSTextAlignmentRight), @"Sorry, only left and right alignment supported.");
69 |
70 | float x = 0;
71 | float y = 0;
72 |
73 | for (UIView * tagView in _tagViews) {
74 |
75 | if (_alignment == NSTextAlignmentLeft)
76 | [tagView in_setFrameOrigin: CGPointMake(x, y)];
77 | else
78 | [tagView in_setFrameOrigin: CGPointMake(self.frame.size.width - x - tagView.frame.size.width, y)];
79 |
80 | x += [tagView frame].size.width + 5;
81 | if (x >= self.frame.size.width)
82 | y += [tagView frame].size.height + 5;
83 | }
84 |
85 | [self in_setFrameHeight: y + [[_tagViews lastObject] frame].size.height];
86 | }
87 |
88 | @end
89 |
--------------------------------------------------------------------------------
/BigSur/Controllers/INTaskQueueViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // INChangeQueueViewController.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/19/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INTaskQueueViewController.h"
10 |
11 | @implementation INTaskQueueViewController
12 |
13 |
14 | - (void)viewDidLoad
15 | {
16 | [[NSNotificationCenter defaultCenter] addObserver: self selector:@selector(update) name:INTaskQueueChangedNotification object:nil];
17 | [super viewDidLoad];
18 | [self update];
19 | }
20 |
21 | - (void)dealloc
22 | {
23 | [[NSNotificationCenter defaultCenter] removeObserver: self];
24 | }
25 |
26 | - (void)didReceiveMemoryWarning
27 | {
28 | [super didReceiveMemoryWarning];
29 | }
30 |
31 | - (void)update
32 | {
33 | [self.tableView reloadData];
34 | [_suspendedSwitch setOn: ![[INAPIManager shared] taskQueueSuspended]];
35 | }
36 |
37 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
38 | {
39 | return [[[INAPIManager shared] taskQueue] count];
40 | }
41 |
42 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
43 | {
44 | INAPITask * change = [[[INAPIManager shared] taskQueue] objectAtIndex: [indexPath row]];
45 | CGRect bounding = [[change description] boundingRectWithSize:CGSizeMake(300, INT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:14]} context:nil];
46 | return bounding.size.height + 10;
47 | }
48 |
49 | - (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
50 | {
51 | INAPITask * change = [[[INAPIManager shared] taskQueue] objectAtIndex: [indexPath row]];
52 | UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier: @"cell"];
53 | UITextView * text = (UITextView*)[cell viewWithTag: 1];
54 | [text setText: [change description]];
55 | return cell;
56 | }
57 |
58 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
59 | {
60 | [tableView deselectRowAtIndexPath:indexPath animated:YES];
61 |
62 | INAPITask * change = [[[INAPIManager shared] taskQueue] objectAtIndex: [indexPath row]];
63 | [[[UIAlertView alloc] initWithTitle:nil message:[change extendedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil] show];
64 | }
65 |
66 | - (IBAction)taskQueueSuspendedToggled:(id)sender
67 | {
68 | [[INAPIManager shared] setTaskQueueSuspended: ![sender isOn]];
69 | }
70 |
71 | @end
72 |
--------------------------------------------------------------------------------
/BigSur/Controllers/INContactsViewController.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/BigSur/Controllers/INLogViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // INLogViewController.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/19/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INLogViewController.h"
10 | #import "INAppDelegate.h"
11 |
12 | #define MAX_CHARS 20000
13 |
14 | @implementation INLogViewController
15 |
16 | - (void)viewDidLoad
17 | {
18 | [super viewDidLoad];
19 |
20 | UIBarButtonItem * share = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(shareLog:)];
21 | UIBarButtonItem * clear = [[UIBarButtonItem alloc] initWithTitle:@"Clear" style:UIBarButtonItemStyleBordered target:self action:@selector(clearLog:)];
22 | [[self navigationItem] setRightBarButtonItems:@[share, clear]];
23 | }
24 |
25 | - (void)viewWillAppear:(BOOL)animated
26 | {
27 | _updateTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(update) userInfo:nil repeats:YES];
28 | [self update];
29 | }
30 |
31 | - (void)viewWillDisappear:(BOOL)animated
32 | {
33 | [_updateTimer invalidate];
34 | }
35 |
36 | - (void)didReceiveMemoryWarning
37 | {
38 | [super didReceiveMemoryWarning];
39 | }
40 |
41 | - (void)update
42 | {
43 | NSString * text = nil;
44 | #if DEBUG
45 | text = @"The app log is printed to the system console for DEBUG builds.";
46 | #else
47 | text = [NSString stringWithContentsOfFile:[[INAppDelegate current] runtimeLogPath] encoding:NSUTF8StringEncoding error:nil];
48 | #endif
49 |
50 | if ([text length] > MAX_CHARS)
51 | text = [text substringFromIndex: [text length] - MAX_CHARS];
52 | [_appLog setText: text];
53 | [_appLog scrollRangeToVisible:NSMakeRange([_appLog.text length], 0)];
54 |
55 | // see http://stackoverflow.com/questions/19124037/scroll-to-bottom-of-uitextview-erratic-in-ios-7
56 | [_appLog setScrollEnabled:NO];
57 | [_appLog setScrollEnabled:YES];
58 | }
59 |
60 | - (IBAction)shareLog:(id)sender
61 | {
62 | UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[_appLog.text] applicationActivities:nil];
63 | [self.navigationController presentViewController:activityViewController animated:YES completion:^{
64 |
65 | }];
66 | }
67 |
68 | - (IBAction)clearLog:(id)sender
69 | {
70 | NSString * path = [[INAppDelegate current] runtimeLogPath];
71 | [[NSFileManager defaultManager] removeItemAtPath:path error:NULL];
72 | freopen([path cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
73 | [self update];
74 | }
75 |
76 | @end
77 |
--------------------------------------------------------------------------------
/Explorations/TinderPaging/TinderPaging/AppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.m
3 | // TinderPaging
4 | //
5 | // Created by Ben Gotow on 6/19/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "AppDelegate.h"
10 | #import "INPagingContainerViewController.h"
11 | #import "ViewController.h"
12 |
13 |
14 | @implementation AppDelegate
15 |
16 |
17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
18 | {
19 | INPagingContainerViewController * paging = [[INPagingContainerViewController alloc] init];
20 | ViewController * a = [[ViewController alloc] init];
21 | ViewController * b = [[ViewController alloc] init];
22 | ViewController * c = [[ViewController alloc] init];
23 | [paging setViewControllers: @[a,b,c]];
24 |
25 | self.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
26 | self.window.rootViewController = paging;
27 | [self.window makeKeyAndVisible];
28 |
29 | return YES;
30 | }
31 |
32 | - (void)applicationWillResignActive:(UIApplication *)application {
33 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
34 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
35 | }
36 |
37 | - (void)applicationDidEnterBackground:(UIApplication *)application {
38 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
39 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
40 | }
41 |
42 | - (void)applicationWillEnterForeground:(UIApplication *)application {
43 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
44 | }
45 |
46 | - (void)applicationDidBecomeActive:(UIApplication *)application {
47 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
48 | }
49 |
50 | - (void)applicationWillTerminate:(UIApplication *)application {
51 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
52 | }
53 |
54 | @end
55 |
--------------------------------------------------------------------------------
/BigSur/Views/INComposeRecipientCollectionViewCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // INComposeRecipientCollectionViewCell.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/6/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INComposeRecipientCollectionViewCell.h"
10 | #import "UIImageView+AFNetworking.h"
11 | #import "INConvenienceCategories.h"
12 | #import "INThemeManager.h"
13 |
14 | @implementation INComposeRecipientCollectionViewCell
15 |
16 | - (id)initWithFrame:(CGRect)frame
17 | {
18 | self = [super initWithFrame:frame];
19 | if (self) {
20 | _insetView = [[UIView alloc] initWithFrame: CGRectZero];
21 | [[self contentView] addSubview: _insetView];
22 |
23 | _nameLabel = [[UILabel alloc] initWithFrame: CGRectZero];
24 | [_nameLabel setTextAlignment: NSTextAlignmentCenter];
25 | [_nameLabel setFont: INComposeRecipientFont];
26 | [_insetView addSubview: _nameLabel];
27 |
28 | _profileImage = [[UIImageView alloc] initWithFrame: CGRectZero];
29 | [_insetView addSubview: _profileImage];
30 |
31 | [[_insetView layer] setBorderWidth: 1.0 / [[UIScreen mainScreen] scale]];
32 | [[_insetView layer] setBorderColor: [[UIColor colorWithWhite:215.0/255.0 alpha:1] CGColor]];
33 | [[_insetView layer] setCornerRadius: 4];
34 |
35 | [_insetView setClipsToBounds: YES];
36 | [_insetView setBackgroundColor: [UIColor colorWithWhite:244.0/255.0 alpha:1]];
37 | }
38 | return self;
39 | }
40 |
41 | - (void)layoutSubviews
42 | {
43 | CGRect f = CGRectMake(0, INComposeRecipientVPadding, self.frame.size.width, self.frame.size.height - INComposeRecipientVPadding * 2);
44 | [_insetView setFrame: f];
45 |
46 | float imageWidth = _insetView.frame.size.height;
47 | [_nameLabel setFrame: CGRectMake(imageWidth, 0, _insetView.frame.size.width - imageWidth, _insetView.frame.size.height)];
48 | [_profileImage setFrame: CGRectMake(0, 0, imageWidth, _insetView.frame.size.height)];
49 |
50 | [super layoutSubviews];
51 | }
52 |
53 | - (void)setRecipient:(NSDictionary*)recipient
54 | {
55 | [[self nameLabel] setText: recipient[@"name"]];
56 | [[self profileImage] setImageWithURL:[NSURL URLForGravatar: recipient[@"email"]] placeholderImage:[UIImage imageNamed: @"profile_placeholder.png"]];
57 | }
58 |
59 | - (void)setSelected:(BOOL)selected
60 | {
61 | [super setSelected: selected];
62 |
63 | if (selected) {
64 | [_insetView setBackgroundColor: [[INThemeManager shared] tintColor]];
65 | [[[self contentView] layer] setBorderColor: [[UIColor colorWithRed:0 green:0.42 blue:0.56 alpha:1] CGColor]];
66 | } else {
67 | [_insetView setBackgroundColor: [UIColor colorWithWhite:244.0/255.0 alpha:1]];
68 | [[[self contentView] layer] setBorderColor: [[UIColor colorWithWhite:215.0/255.0 alpha:1] CGColor]];
69 | }
70 | }
71 |
72 |
73 | @end
74 |
--------------------------------------------------------------------------------
/BigSur/Views/INMailItemTableViewCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // INThreadTableViewCell.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/1/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INMailItemTableViewCell.h"
10 | #import "NSString+FormatConversion.h"
11 | #import "UIView+FrameAdditions.h"
12 | #import "UIImageView+AFNetworking.h"
13 | #import "INConvenienceCategories.h"
14 | #import "INThemeManager.h"
15 |
16 |
17 | @implementation INMailItemTableViewCell
18 |
19 | - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
20 | {
21 | self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier: reuseIdentifier];
22 | if (self) {
23 | _participantsLabel = [[INRecipientsLabel alloc] initWithFrame: CGRectZero];
24 | [_participantsLabel setTextColor: [UIColor colorWithWhite:0.2 alpha:1]];
25 | [_participantsLabel setTextFont: [UIFont systemFontOfSize: 13]];
26 | [self.contentView addSubview: _participantsLabel];
27 |
28 | _dateLabel = [[UILabel alloc] initWithFrame: CGRectZero];
29 | [_dateLabel setFont: [UIFont systemFontOfSize: 13]];
30 | [_dateLabel setTextColor: [UIColor grayColor]];
31 | [_dateLabel setTextAlignment: NSTextAlignmentRight];
32 | [self.contentView addSubview: _dateLabel];
33 |
34 | _bodyLabel = [[UILabel alloc] initWithFrame: CGRectZero];
35 | [_bodyLabel setLineBreakMode: NSLineBreakByWordWrapping];
36 | _subjectLabel = [self textLabel];
37 | [_subjectLabel setFont: [UIFont boldSystemFontOfSize: 15]];
38 | [_subjectLabel setContentMode: UIViewContentModeCenter];
39 | [_subjectLabel removeFromSuperview];
40 | [self.contentView addSubview:_subjectLabel];
41 |
42 | [_bodyLabel setTextColor: [UIColor grayColor]];
43 | [_bodyLabel setFont: [UIFont systemFontOfSize: 13]];
44 | [_bodyLabel setNumberOfLines: 2];
45 | [self.contentView addSubview: _bodyLabel];
46 | }
47 | return self;
48 | }
49 |
50 | - (float)textLeftInset
51 | {
52 | return INSETS.left;
53 | }
54 |
55 | - (void)layoutSubviews
56 | {
57 | CGRect f = self.frame;
58 |
59 | [super layoutSubviews];
60 |
61 | [_dateLabel sizeToFit];
62 | [_dateLabel in_setFrameOrigin: CGPointMake(f.size.width - INSETS.right - _dateLabel.frame.size.width, INSETS.top)];
63 |
64 | float textX = [self textLeftInset];
65 | float textW = f.size.width - textX - INSETS.right;
66 |
67 | [_participantsLabel setFrame: CGRectMake(textX, INSETS.top, _dateLabel.frame.origin.x - textX, 16)];
68 | [_subjectLabel setFrame: CGRectMake(textX, [_participantsLabel in_bottomRight].y, textW, 20)];
69 | [_bodyLabel setFrame: CGRectMake(textX, [_subjectLabel in_bottomRight].y, textW, 35)];
70 | [_bodyLabel sizeToFit];
71 | }
72 |
73 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated
74 | {
75 | [super setSelected:selected animated:animated];
76 | }
77 |
78 | @end
79 |
--------------------------------------------------------------------------------
/BigSur/Views/INComposeSubjectRowView.m:
--------------------------------------------------------------------------------
1 | //
2 | // INComposeSubjectRowView.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/5/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INComposeSubjectRowView.h"
10 |
11 | @implementation INComposeSubjectRowView
12 |
13 | - (id)initWithFrame:(CGRect)frame
14 | {
15 | self = [super initWithFrame:frame];
16 | if (self) {
17 | _subjectField = [[INPlaceholderTextView alloc] initWithFrame: CGRectZero];
18 | [_subjectField setText: @""];
19 | [_subjectField setPlaceholder: @"Subject"];
20 | [_subjectField setDelegate: self];
21 | [_subjectField setReturnKeyType: UIReturnKeyNext];
22 | [_subjectField setScrollEnabled: NO];
23 | [_subjectField setTextContainerInset: UIEdgeInsetsZero];
24 | [_subjectField setTranslatesAutoresizingMaskIntoConstraints: NO];
25 | [_subjectField setFont: [UIFont systemFontOfSize: 15]];
26 | [_subjectField setBackgroundColor: [UIColor clearColor]];
27 | [self addSubview: _subjectField];
28 |
29 | [self.actionButton setImage: [UIImage imageNamed: @"icon_add_attachment.png"] forState:UIControlStateNormal];
30 | [self.rowLabel setText: @""];
31 |
32 | self.bodyView = _subjectField;
33 | }
34 | return self;
35 | }
36 |
37 | - (void)updateConstraints
38 | {
39 | NSDictionary * views = @{@"body":self.bodyView, @"label": self.rowLabel, @"action": self.actionButton};
40 | [self addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(12)-[body]-(16)-|" options:0 metrics:nil views: views]];
41 | [self addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(0)-[action]" options:0 metrics:nil views: views]];
42 |
43 | [super updateConstraints];
44 | }
45 |
46 | - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
47 | {
48 | NSString * newText = [[textView text] stringByReplacingCharactersInRange:range withString:text];
49 |
50 | // if the user has inserted a tab or return, reject the change and move to the next text field.
51 | // Unfortunately iOS doesn't expose any equivalent to Mac OS X's nextKeyResponder, so we have
52 | // to do it ourselves. The simplest way is to look at our sibling views and find the next one
53 | // that can become first responder.
54 | if (([newText rangeOfString: @"\n"].location != NSNotFound) || ([newText rangeOfString: @"\t"].location != NSNotFound)) {
55 |
56 | NSArray * siblings = [[self superview] subviews];
57 | NSUInteger ii = [siblings indexOfObject: self];
58 | for (NSUInteger x = ii; x < [siblings count]; x++){
59 | UIView * v = [siblings objectAtIndex: x];
60 | if ([v canBecomeFirstResponder]) {
61 | [v becomeFirstResponder];
62 | break;
63 | }
64 | }
65 | return NO;
66 | }
67 | return YES;
68 | }
69 |
70 | @end
71 |
--------------------------------------------------------------------------------
/BigSur/Categories/UIView+FrameAdditions.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIViewAdditions.m
3 | // PAR Works iOS SDK
4 | //
5 | // Copyright 2013 PAR Works, Inc.
6 | //
7 | // Licensed under the Apache License, Version 2.0 (the "License");
8 | // you may not use this file except in compliance with the License.
9 | // You may obtain a copy of the License at
10 | //
11 | // http://www.apache.org/licenses/LICENSE-2.0
12 | //
13 | // Unless required by applicable law or agreed to in writing, software
14 | // distributed under the License is distributed on an "AS IS" BASIS,
15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | // See the License for the specific language governing permissions and
17 | // limitations under the License.
18 | //
19 |
20 |
21 |
22 | #import "UIView+FrameAdditions.h"
23 |
24 | @implementation UIView (FrameAdditions)
25 |
26 | - (void)in_setFrameY:(float)y
27 | {
28 | CGRect frame = [self frame];
29 | frame.origin.y = y;
30 | [self setFrame: frame];
31 | }
32 |
33 | - (void)in_setFrameX:(float)x
34 | {
35 | CGRect frame = [self frame];
36 | frame.origin.x = x;
37 | [self setFrame: frame];
38 | }
39 |
40 | - (void)in_shiftFrame:(CGPoint)offset
41 | {
42 | CGRect frame = [self frame];
43 | [self setFrame: CGRectMake(frame.origin.x + offset.x, frame.origin.y + offset.y, frame.size.width, frame.size.height)];
44 | }
45 |
46 | - (void)in_setFrameOrigin:(CGPoint)origin
47 | {
48 | CGRect frame = [self frame];
49 | [self setFrame: CGRectMake(origin.x, origin.y, frame.size.width, frame.size.height)];
50 | }
51 |
52 | - (void)in_setFrameSize:(CGSize)size
53 | {
54 | CGRect frame = [self frame];
55 | [self setFrame: CGRectMake(frame.origin.x, frame.origin.y, size.width, size.height)];
56 | }
57 |
58 | - (void)in_setFrameCenter:(CGPoint)p
59 | {
60 | CGRect frame = [self frame];
61 | [self setFrame: CGRectMake(p.x - frame.size.width / 2, p.y - frame.size.height / 2, frame.size.width, frame.size.height)];
62 | }
63 |
64 | - (void)in_setFrameWidth:(float)w
65 | {
66 | CGRect frame = [self frame];
67 | frame.size.width = w;
68 | [self setFrame: frame];
69 | }
70 |
71 | - (void)in_setFrameHeight:(float)h
72 | {
73 | CGRect frame = [self frame];
74 | frame.size.height = h;
75 | [self setFrame: frame];
76 | }
77 |
78 | - (CGPoint)in_topRight
79 | {
80 | return CGPointMake([self frame].origin.x + [self frame].size.width, [self frame].origin.y);
81 | }
82 |
83 | - (CGPoint)in_bottomRight
84 | {
85 | return CGPointMake([self frame].origin.x + [self frame].size.width, [self frame].origin.y + [self frame].size.height);
86 | }
87 |
88 | - (CGPoint)in_bottomLeft
89 | {
90 | return CGPointMake([self frame].origin.x, [self frame].origin.y + [self frame].size.height);
91 | }
92 |
93 | - (id)viewAncestorOfClass:(Class)klass
94 | {
95 | if ([[self superview] isKindOfClass: klass])
96 | return [self superview];
97 | else
98 | return [[self superview] viewAncestorOfClass: klass];
99 | }
100 |
101 | @end
102 |
--------------------------------------------------------------------------------
/BigSur/Categories/NSString+FormatConversion.m:
--------------------------------------------------------------------------------
1 | //
2 | // NSString+DateConversion.m
3 | // Mib.io
4 | //
5 | // Created by Ben Gotow on 6/7/12.
6 | // Copyright (c) 2012 Foundry376. All rights reserved.
7 | //
8 |
9 | #import "NSString+FormatConversion.h"
10 | #import
11 |
12 | static NSMutableDictionary * formatters;
13 |
14 | @implementation NSString (FormatConversion)
15 |
16 | + (NSDateFormatter *)formatterForFormat:(NSString *)f
17 | {
18 | if (formatters == nil)
19 | formatters = [[NSMutableDictionary alloc] init];
20 |
21 | NSDateFormatter * formatter = [formatters objectForKey:f];
22 |
23 | if (!formatter) {
24 | formatter = [[NSDateFormatter alloc] init];
25 | [formatter setDateFormat:f];
26 | [formatters setObject:formatter forKey:f];
27 | }
28 | return formatter;
29 | }
30 |
31 | + (NSString *)stringWithDate:(NSDate *)date format:(NSString *)f
32 | {
33 | return [[NSString formatterForFormat:f] stringFromDate:date];
34 | }
35 |
36 | - (NSDate *)dateValueWithFormat:(NSString *)f
37 | {
38 | return [[NSString formatterForFormat:f] dateFromString:self];
39 | }
40 |
41 | - (NSString *)md5Value
42 | {
43 | const char * cStr = [self UTF8String];
44 | unsigned char digest[16];
45 |
46 | CC_MD5(cStr, (CC_LONG)strlen(cStr), digest); // This is the md5 call
47 |
48 | NSMutableString * output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
49 |
50 | for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
51 | [output appendFormat:@"%02x", digest[i]];
52 |
53 | return output;
54 | }
55 |
56 | + (NSString *)generateUUIDWithExtension:(NSString *)ext
57 | {
58 | CFUUIDRef theUUID = CFUUIDCreate(NULL);
59 | CFStringRef string = CFUUIDCreateString(NULL, theUUID);
60 |
61 | CFRelease(theUUID);
62 | NSString * s = (NSString *)CFBridgingRelease(string);
63 |
64 | if (ext)
65 | s = [s stringByAppendingFormat:@".%@", ext];
66 | return s;
67 | }
68 |
69 | - (NSString *)urlencode
70 | {
71 | NSMutableString * output = [NSMutableString string];
72 | const unsigned char * source = (const unsigned char *)[self UTF8String];
73 | unsigned long sourceLen = strlen((const char *)source);
74 |
75 | for (int i = 0; i < sourceLen; ++i) {
76 | const unsigned char thisChar = source[i];
77 |
78 | if (thisChar == ' ')
79 | [output appendString:@"+"];
80 | else if ((thisChar == '.') || (thisChar == '-') || (thisChar == '_') || (thisChar == '~') ||
81 | ((thisChar >= 'a') && (thisChar <= 'z')) ||
82 | ((thisChar >= 'A') && (thisChar <= 'Z')) ||
83 | ((thisChar >= '0') && (thisChar <= '9')))
84 | [output appendFormat:@"%c", thisChar];
85 | else
86 | [output appendFormat:@"%%%02X", thisChar];
87 | }
88 |
89 | return output;
90 | }
91 |
92 | - (id)asJSONObjectOfClass:(Class)klass
93 | {
94 | if ([self length] == 0)
95 | return nil;
96 |
97 | NSData * data = [self dataUsingEncoding:NSUTF8StringEncoding];
98 | id obj = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:NULL];
99 |
100 | if ([obj isKindOfClass:klass])
101 | return obj;
102 | else
103 | return nil;
104 | }
105 |
106 | @end
107 |
--------------------------------------------------------------------------------
/BigSur/Controllers/INMailViewController.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/BigSur/Views/INThreadTableViewCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // INThreadTableViewCell.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/22/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INThreadTableViewCell.h"
10 | #import "UIView+FrameAdditions.h"
11 | #import "INConvenienceCategories.h"
12 | #import "INThemeManager.h"
13 |
14 | @implementation INThreadTableViewCell
15 |
16 | - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
17 | {
18 | self = [super initWithStyle:style reuseIdentifier: reuseIdentifier];
19 | if (self) {
20 | _unreadDot = [[UIView alloc] initWithFrame: CGRectMake(0, 0, 14, 14)];
21 | [_unreadDot setBackgroundColor: [[INThemeManager shared] tintColor]];
22 | [[_unreadDot layer] setCornerRadius: _unreadDot.frame.size.width / 2];
23 | [self.contentView addSubview: _unreadDot];
24 |
25 | _threadCountLabel = [[UILabel alloc] initWithFrame: CGRectZero];
26 | [_threadCountLabel setFont: [UIFont systemFontOfSize: 13]];
27 | [_threadCountLabel setTextColor: [UIColor grayColor]];
28 | [_threadCountLabel setTextAlignment: NSTextAlignmentCenter];
29 | [[_threadCountLabel layer] setBorderWidth: 1];
30 | [[_threadCountLabel layer] setCornerRadius: 3];
31 | [[_threadCountLabel layer] setBorderColor: [[UIColor colorWithWhite:0.7 alpha:1] CGColor]];
32 | [self.contentView addSubview: _threadCountLabel];
33 | }
34 | return self;
35 | }
36 |
37 | - (float)textLeftInset
38 | {
39 | return 40;
40 | }
41 |
42 | - (void)layoutSubviews
43 | {
44 | CGRect f = self.frame;
45 |
46 | [super layoutSubviews];
47 |
48 | float textW = [self.subjectLabel frame].size.width;
49 |
50 | if ([[_threadCountLabel text] length]){
51 | CGSize s = [[_threadCountLabel text] sizeWithAttributes: @{NSFontAttributeName: [_threadCountLabel font]}];
52 | s.width += 8;
53 | s.height += 4;
54 | [_threadCountLabel setFrame: CGRectMake(f.size.width - INSETS.right - s.width, (f.size.height - s.height) / 2, s.width, s.height)];
55 | [_threadCountLabel setHidden: NO];
56 |
57 | textW -= s.width + INSETS.right;
58 | [self.subjectLabel in_setFrameWidth: textW];
59 | [self.bodyLabel in_setFrameSize: CGSizeMake(textW, 1000)];
60 | [self.bodyLabel sizeToFit];
61 |
62 | } else {
63 | [_threadCountLabel setHidden: YES];
64 | }
65 |
66 | [_unreadDot in_setFrameCenter: CGPointMake(20, self.subjectLabel.center.y)];
67 | }
68 |
69 |
70 | - (void)setThread:(INThread *)thread
71 | {
72 | _thread = thread;
73 |
74 | BOOL includeMe = (([[_thread messageIDs] count] > 1) || ([[_thread participants] count] > 2));
75 | [self.participantsLabel setPrefixString: @"" andRecipients:[_thread participants] includeMe: includeMe];
76 | [self.dateLabel setText: [NSString stringForMessageDate: [_thread lastMessageDate]]];
77 | [self.subjectLabel setText: [_thread subject]];
78 | [self.bodyLabel setText: [NSString stringByCleaningWhitespaceInString: [_thread snippet]]];
79 | [_unreadDot setHidden: (![_thread hasTagWithID: INTagIDUnread])];
80 |
81 | if ([[_thread messageIDs] count] > 1)
82 | [_threadCountLabel setText: [NSString stringWithFormat:@"%d", (int)[[_thread messageIDs] count]]];
83 | else
84 | [_threadCountLabel setText: @""];
85 |
86 | [self setNeedsLayout];
87 | }
88 |
89 | @end
90 |
--------------------------------------------------------------------------------
/BigSur/Categories/INConvenienceCategories.m:
--------------------------------------------------------------------------------
1 | //
2 | // NSString+INConvenienceCategories.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/2/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INConvenienceCategories.h"
10 | #import "NSString+FormatConversion.h"
11 |
12 | @implementation NSString (INConvenienceCategories)
13 |
14 | + (NSString *)stringForMessageDate:(NSDate*)date
15 | {
16 | return [self stringForMessageDate:date withStyle:NSDateFormatterShortStyle];
17 | }
18 |
19 | + (NSString *)stringForMessageDate:(NSDate*)date withStyle:(NSDateFormatterStyle)style
20 | {
21 | NSTimeInterval twelveHours = -60 * 60 * 12;
22 | if ([date timeIntervalSinceNow] > twelveHours)
23 | return [NSString stringWithDate:date format:@"h:mm aa"];
24 | else {
25 | if (style == NSDateFormatterShortStyle) {
26 | return [NSString stringWithDate:date format:@"MMM d, YYYY"];
27 | } else if (style == NSDateFormatterLongStyle) {
28 | return [NSString stringWithDate:date format:@"MMM d, YYYY 'at' h:mm aa"];
29 | }
30 | return @"Unknown Date Style";
31 | }
32 | }
33 |
34 | + (NSString *)stringByCleaningWhitespaceInString:(NSString*)snippet
35 | {
36 | unichar * cleaned = calloc([snippet length], sizeof(unichar));
37 | int cleanedLength = 0;
38 |
39 | NSCharacterSet * punctuationSet = [NSCharacterSet punctuationCharacterSet];
40 | NSCharacterSet * whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
41 |
42 | BOOL hasSpace = YES;
43 | for (int ii = 0; ii < [snippet length]; ii ++) {
44 | unichar c = [snippet characterAtIndex: ii];
45 | BOOL isWhitespace = [whitespaceSet characterIsMember: c];
46 |
47 | if (isWhitespace) {
48 | // If this character is whitespace, only add it to our string if we don't
49 | // already have a whitespace character.
50 | if (!hasSpace)
51 | cleaned[cleanedLength++] = ' ';
52 | hasSpace = YES;
53 | } else {
54 | // If this character is punctuation and our trailing character is a whitespace
55 | // character, place the punctutation where the whitespace is. Otherwise just
56 | // append the character.
57 | if (hasSpace && [punctuationSet characterIsMember: c])
58 | cleanedLength = fmaxf(0, cleanedLength-1);
59 | cleaned[cleanedLength++] = c;
60 | hasSpace = NO;
61 | }
62 | }
63 | return [[NSString alloc] initWithCharactersNoCopy:cleaned length:cleanedLength freeWhenDone: YES];
64 | }
65 |
66 | - (NSArray *)arrayOfValidEmailAddresses
67 | {
68 | NSRegularExpression * regex = [NSRegularExpression regularExpressionWithPattern: @"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?" options:NSRegularExpressionCaseInsensitive error:nil];
69 | NSArray * matches = [regex matchesInString:self options: 0 range:NSMakeRange(0, [self length])];
70 | NSMutableArray * results = [NSMutableArray array];
71 |
72 | for (NSTextCheckingResult * match in matches) {
73 | NSString * email = [self substringWithRange: [match range]];
74 | [results addObject: email];
75 | }
76 | return results;
77 | }
78 |
79 | @end
80 |
81 | @implementation NSURL (INConvenienceCategories)
82 |
83 | + (NSURL*)URLForGravatar:(NSString*)email
84 | {
85 | email = [[email stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]] lowercaseString];
86 | NSString * p = [NSString stringWithFormat: @"http://www.gravatar.com/avatar/%@?s=184", [email md5Value]];
87 | return [NSURL URLWithString: p];
88 | }
89 |
90 | @end
91 |
92 |
--------------------------------------------------------------------------------
/BigSur/Views/INPlaceholderTextView.m:
--------------------------------------------------------------------------------
1 | //
2 | // INPlaceholderTextView.m
3 | // BigSur
4 | //
5 |
6 | #import "INPlaceholderTextView.h"
7 |
8 |
9 | @interface INPlaceholderTextView ()
10 |
11 | @property (nonatomic, retain) UILabel *placeHolderLabel;
12 |
13 | @end
14 |
15 | @implementation INPlaceholderTextView
16 |
17 | CGFloat const UI_PLACEHOLDER_TEXT_CHANGED_ANIMATION_DURATION = 0.25;
18 |
19 | - (void)dealloc
20 | {
21 | [[NSNotificationCenter defaultCenter] removeObserver:self];
22 | #if __has_feature(objc_arc)
23 | #else
24 | [_placeHolderLabel release]; _placeHolderLabel = nil;
25 | [_placeholderColor release]; _placeholderColor = nil;
26 | [_placeholder release]; _placeholder = nil;
27 | [super dealloc];
28 | #endif
29 | }
30 |
31 | - (void)awakeFromNib
32 | {
33 | [super awakeFromNib];
34 |
35 | // Use Interface Builder User Defined Runtime Attributes to set
36 | // placeholder and placeholderColor in Interface Builder.
37 | if (!self.placeholder) {
38 | [self setPlaceholder:@""];
39 | }
40 |
41 | if (!self.placeholderColor) {
42 | [self setPlaceholderColor:[UIColor lightGrayColor]];
43 | }
44 |
45 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil];
46 | }
47 |
48 | - (id)initWithFrame:(CGRect)frame
49 | {
50 | if( (self = [super initWithFrame:frame]) )
51 | {
52 | [self setPlaceholder:@""];
53 | [self setPlaceholderColor:[UIColor lightGrayColor]];
54 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil];
55 | }
56 | return self;
57 | }
58 |
59 | - (void)textChanged:(NSNotification *)notification
60 | {
61 | if([[self placeholder] length] == 0)
62 | {
63 | return;
64 | }
65 |
66 | [UIView animateWithDuration:UI_PLACEHOLDER_TEXT_CHANGED_ANIMATION_DURATION animations:^{
67 | if([[self text] length] == 0)
68 | {
69 | [[self viewWithTag:999] setAlpha:1];
70 | }
71 | else
72 | {
73 | [[self viewWithTag:999] setAlpha:0];
74 | }
75 | }];
76 | }
77 |
78 | - (void)setText:(NSString *)text {
79 | [super setText:text];
80 | [self textChanged:nil];
81 | }
82 |
83 | - (void)drawRect:(CGRect)rect
84 | {
85 | if( [[self placeholder] length] > 0 )
86 | {
87 | if (_placeHolderLabel == nil )
88 | {
89 | _placeHolderLabel = [[UILabel alloc] initWithFrame:CGRectMake(self.textContainerInset.left + 5,self.textContainerInset.top,self.bounds.size.width - 16,0)];
90 | _placeHolderLabel.lineBreakMode = NSLineBreakByWordWrapping;
91 | _placeHolderLabel.numberOfLines = 0;
92 | _placeHolderLabel.font = self.font;
93 | _placeHolderLabel.backgroundColor = [UIColor clearColor];
94 | _placeHolderLabel.textColor = self.placeholderColor;
95 | _placeHolderLabel.alpha = 0;
96 | _placeHolderLabel.tag = 999;
97 | [self addSubview:_placeHolderLabel];
98 | }
99 |
100 | _placeHolderLabel.text = self.placeholder;
101 | [_placeHolderLabel sizeToFit];
102 | [self sendSubviewToBack:_placeHolderLabel];
103 | }
104 |
105 | if( [[self text] length] == 0 && [[self placeholder] length] > 0 )
106 | {
107 | [[self viewWithTag:999] setAlpha:1];
108 | }
109 |
110 | [super drawRect:rect];
111 | }
112 |
113 | @end
--------------------------------------------------------------------------------
/BigSur/Controllers/INContactsViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // INContactsViewController.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/5/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INContactsViewController.h"
10 | #import "INAppDelegate.h"
11 |
12 |
13 | @implementation INContactsViewController
14 |
15 | - (id)initForSelectingContactInNamespace:(INNamespace*)ns withCallback:(ContactSelectionBlock)block;
16 | {
17 | self = [super init];
18 | if (self) {
19 | _namespace = ns;
20 | _contactSelectionCallback = block;
21 | }
22 | return self;
23 | }
24 |
25 | - (void)viewDidLoad
26 | {
27 | [super viewDidLoad];
28 |
29 | [self setTitle: @"Contacts"];
30 |
31 | UIBarButtonItem * left = [[UIBarButtonItem alloc] initWithTitle:@"Cancel" style:UIBarButtonItemStyleBordered target:self action:@selector(cancelTapped:)];
32 | [[self navigationItem] setLeftBarButtonItem: left];
33 |
34 | [_tableView setRowHeight: 50];
35 |
36 | self.contactsProvider = [self.namespace newContactProvider];
37 | [_contactsProvider setItemSortDescriptors: @[[[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES]]];
38 | [_contactsProvider setDelegate:self];
39 | [_contactsProvider refresh];
40 | }
41 |
42 | - (IBAction)cancelTapped:(id)sender
43 | {
44 | [self dismissViewControllerAnimated:YES completion:NULL];
45 | }
46 |
47 | #pragma Provider Delegate
48 |
49 | - (void)provider:(INModelProvider*)provider dataAltered:(INModelProviderChangeSet *)changeSet
50 | {
51 | [_tableView beginUpdates];
52 | [_tableView deleteRowsAtIndexPaths:[changeSet indexPathsFor: INModelProviderChangeRemove] withRowAnimation:UITableViewRowAnimationAutomatic];
53 | [_tableView insertRowsAtIndexPaths:[changeSet indexPathsFor: INModelProviderChangeAdd] withRowAnimation:UITableViewRowAnimationAutomatic];
54 | [_tableView endUpdates];
55 | [_tableView reloadRowsAtIndexPaths:[changeSet indexPathsFor: INModelProviderChangeUpdate] withRowAnimation:UITableViewRowAnimationNone];
56 | }
57 |
58 | - (void)provider:(INModelProvider*)provider dataFetchFailed:(NSError *)error
59 | {
60 | [[[UIAlertView alloc] initWithTitle:@"Error!" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil] show];
61 | }
62 |
63 | - (void)providerDataChanged:(INModelProvider*)provider
64 | {
65 | [_tableView reloadData];
66 | }
67 |
68 |
69 | #pragma mark Table View Data
70 |
71 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
72 | {
73 | return [[_contactsProvider items] count];
74 | }
75 |
76 | - (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
77 | {
78 | UITableViewCell * cell = (UITableViewCell*)[tableView dequeueReusableCellWithIdentifier:@"contact"];
79 | if (!cell) cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"contact"];
80 |
81 | INContact * contact = [[_contactsProvider items] objectAtIndex: [indexPath row]];
82 | [[cell textLabel] setText: [contact name]];
83 | [[cell detailTextLabel] setText: [contact email]];
84 | [[cell detailTextLabel] setTextColor: [UIColor grayColor]];
85 | return cell;
86 | }
87 |
88 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
89 | {
90 | [tableView deselectRowAtIndexPath:indexPath animated:YES];
91 |
92 | INContact * contact = [[_contactsProvider items] objectAtIndex: [indexPath row]];
93 | if (_contactSelectionCallback) {
94 | _contactSelectionCallback(contact);
95 | }
96 | }
97 |
98 | @end
99 |
--------------------------------------------------------------------------------
/BigSur/Views/INComposeRowView.m:
--------------------------------------------------------------------------------
1 | //
2 | // INComposeRowView.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/5/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INComposeRowView.h"
10 |
11 | @implementation INComposeRowView
12 |
13 |
14 | - (id)initWithFrame:(CGRect)frame
15 | {
16 | self = [super initWithFrame:frame];
17 | if (self) {
18 | _rowLabel = [[UILabel alloc] initWithFrame: CGRectZero];
19 | _rowLabel.translatesAutoresizingMaskIntoConstraints = NO;
20 | [_rowLabel setTextColor: [UIColor colorWithWhite:0.7 alpha:1]];
21 | [_rowLabel setFont: [UIFont systemFontOfSize: 15]];
22 | [_rowLabel setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal];
23 | [self addSubview: _rowLabel];
24 |
25 | _actionButton = [UIButton buttonWithType: UIButtonTypeCustom];
26 | [_actionButton setTranslatesAutoresizingMaskIntoConstraints: NO];
27 | [_actionButton setTitleColor: [_rowLabel textColor] forState:UIControlStateNormal];
28 | [[_actionButton titleLabel] setFont: [UIFont boldSystemFontOfSize: 35]];
29 | [_actionButton setFrame: CGRectMake(0, 0, 40, 40)];
30 | [_actionButton setHidden: YES];
31 | [self addSubview: _actionButton];
32 |
33 | _bottomBorder = [[UIView alloc] init];
34 | [_bottomBorder setFrame: CGRectMake(0, 0, frame.size.width, 0.5)];
35 | [_bottomBorder setTranslatesAutoresizingMaskIntoConstraints: NO];
36 | [_bottomBorder setBackgroundColor: [UIColor colorWithWhite:0.84 alpha:1]];
37 | [self addSubview: _bottomBorder];
38 |
39 | [self setClipsToBounds: YES];
40 | [self setBackgroundColor: [UIColor whiteColor]];
41 | }
42 | return self;
43 | }
44 |
45 | - (BOOL)requiresConstraintBasedLayout
46 | {
47 | return YES;
48 | }
49 |
50 | - (void)layoutSubviews
51 | {
52 | [super layoutSubviews];
53 | [self positionBottomBorder];
54 | }
55 |
56 | - (void)positionBottomBorder
57 | {
58 | [CATransaction begin];
59 | [CATransaction setDisableActions: !_animatesBottomBorder];
60 | [_bottomBorder setFrame: CGRectMake(-1, self.frame.size.height - 0.5, self.frame.size.width + 2, 0.5)];
61 | [CATransaction commit];
62 | }
63 |
64 | - (UIButton*)actionButton
65 | {
66 | [_actionButton setHidden: NO];
67 | return _actionButton;
68 | }
69 |
70 | - (void)updateConstraints
71 | {
72 | NSDictionary * views = @{@"label":_rowLabel, @"body": _bodyView, @"action": _actionButton};
73 | BOOL hasLabel = [[_rowLabel text] length];
74 | BOOL hasAction = ![_actionButton isHidden];
75 |
76 | // todo: this is a little silly
77 |
78 | if (hasLabel && hasAction) {
79 | [self addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(9)-[label]-(2)-[body]-[action(==40)]-(0)-|" options:0 metrics:nil views: views]];
80 | } else if (hasLabel && !hasAction) {
81 | [self addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(9)-[label]-(2)-[body]-(4)-|" options:0 metrics:nil views: views]];
82 | } else if (!hasLabel && hasAction) {
83 | [self addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(4)-[body]-[action(==40)]-(0)-|" options:0 metrics:nil views: views]];
84 | } else {
85 | [self addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(4)-[body]-(4)-|" options:0 metrics:nil views: views]];
86 | }
87 |
88 | [self addConstraint: [NSLayoutConstraint constraintWithItem:_rowLabel attribute:NSLayoutAttributeBaseline relatedBy:NSLayoutRelationEqual toItem:_actionButton attribute:NSLayoutAttributeBaseline multiplier:1 constant:-4]];
89 | [super updateConstraints];
90 | }
91 |
92 | @end
93 |
--------------------------------------------------------------------------------
/BigSur/INStupidFullSyncEngine.m:
--------------------------------------------------------------------------------
1 | //
2 | // INStupidFullSyncEngine.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/16/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INStupidFullSyncEngine.h"
10 | #import "INAppDelegate.h"
11 |
12 | #define REQUEST_PAGE_SIZE 50
13 |
14 | @implementation INStupidFullSyncEngine
15 |
16 | - (id)initWithConfiguration:(NSDictionary*)config
17 | {
18 | self = [super init];
19 | if (self) {
20 | self.syncOperations = [NSMutableArray array];
21 |
22 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(checkAndSync) name:INAuthenticationChangedNotification object:nil];
23 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(checkAndSync) name:BigSurNamespaceChanged object:nil];
24 | [self checkAndSync];
25 | }
26 | return self;
27 | }
28 |
29 | - (BOOL)providesCompleteCacheOf:(Class)klass
30 | {
31 | if (klass == [INMessage class])
32 | return NO;
33 | return YES;
34 | }
35 |
36 | - (void)checkAndSync
37 | {
38 | dispatch_async(dispatch_get_main_queue(), ^{
39 | if ([[INAPIManager shared] isAuthenticated]) {
40 | [self sync];
41 |
42 | } else {
43 | [_syncOperations makeObjectsPerformSelector: @selector(cancel)];
44 | [_syncOperations removeAllObjects];
45 | }
46 | });
47 | }
48 |
49 | - (void)sync
50 | {
51 | if (_syncInProgress > 0)
52 | return;
53 |
54 | [self syncClass:[INTag class] callback: NULL];
55 | [self syncClass:[INContact class] callback: NULL];
56 | [self syncClass:[INThread class] callback: NULL];
57 | [self syncClass:[INDraft class] callback: NULL];
58 |
59 | [[INAPIManager shared] retryTasks];
60 | }
61 |
62 | - (void)syncWithCallback:(ErrorBlock)callback
63 | {
64 | [self syncClass:[INThread class] callback:callback];
65 | }
66 |
67 | - (void)syncClass:(Class)klass callback:(ErrorBlock)callback
68 | {
69 | [self syncClass:klass page: 0 callback:callback];
70 | }
71 |
72 | - (void)syncClass:(Class)klass page:(int)page callback:(ErrorBlock)callback
73 | {
74 | INNamespace * namespace = [[INAppDelegate current] currentNamespace];
75 | if (!namespace) return;
76 |
77 | NSString * path = [NSString stringWithFormat:@"/n/%@/%@", [namespace ID], [klass resourceAPIName]];
78 | NSLog(@"SYNC: %@ - %d", path, page);
79 |
80 | _syncInProgress += 1;
81 | AFHTTPRequestOperation * op = [[INAPIManager shared].AF GET:path parameters:@{@"offset":@(page * REQUEST_PAGE_SIZE), @"limit":@(REQUEST_PAGE_SIZE)} success:^(AFHTTPRequestOperation *operation, id models) {
82 | if ([models count] >= REQUEST_PAGE_SIZE) {
83 | [self syncClass: klass page: page + 1 callback: callback];
84 | } else {
85 | if (callback)
86 | callback(YES, nil);
87 | }
88 | [_syncOperations removeObject: operation];
89 | _syncInProgress -= 1;
90 |
91 | } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
92 | // sync interrupted
93 | if (callback)
94 | callback(NO, error);
95 | [_syncOperations removeObject: operation];
96 | _syncInProgress -= 1;
97 | }];
98 |
99 | AFHTTPResponseSerializer * serializer = [[INAPIManager shared] responseSerializerForClass: klass];
100 | [op setResponseSerializer:serializer];
101 | [_syncOperations addObject: op];
102 | }
103 |
104 | - (void)resetSyncState
105 | {
106 |
107 | }
108 |
109 | @end
110 |
--------------------------------------------------------------------------------
/BigSur/Views/INAttachmentTableViewCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // INAttachmentTableViewCell.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/9/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INAttachmentTableViewCell.h"
10 |
11 | @implementation INAttachmentTableViewCell
12 |
13 | - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
14 | {
15 | self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
16 | if (self) {
17 | [[self imageView] setClipsToBounds: YES];
18 | [[self imageView] setContentMode: UIViewContentModeScaleAspectFill];
19 |
20 | self.progressView = [[UIProgressView alloc] initWithProgressViewStyle: UIProgressViewStyleBar];
21 | [_progressView setProgress: 0.2];
22 | [_progressView setTrackTintColor: [UIColor colorWithWhite:0.9 alpha:1]];
23 | [[self contentView] addSubview: _progressView];
24 |
25 | self.xButton = [UIButton buttonWithType: UIButtonTypeCustom];
26 | [_xButton setTitle:@"X" forState:UIControlStateNormal];
27 | [_xButton addTarget:self action:@selector(triggerDeleteCallback:) forControlEvents:UIControlEventTouchUpInside];
28 | [_xButton setTitleColor:[UIColor lightGrayColor] forState:UIControlStateNormal];
29 | [[self contentView] addSubview: _xButton];
30 |
31 | [self setSelectionStyle: UITableViewCellSelectionStyleNone];
32 | }
33 | return self;
34 | }
35 |
36 | - (void)layoutSubviews
37 | {
38 | [super layoutSubviews];
39 |
40 | float h = self.frame.size.height - 8;
41 | [[self imageView] setFrame: CGRectMake(8, 4, h, h)];
42 | [[self progressView] setFrame: CGRectMake(h + 13, (self.frame.size.height - 3)/2, self.frame.size.width - (h + 13 + 30 + 8), 3)];
43 | [[self xButton] setFrame: CGRectMake(self.frame.size.width - 30, 4, 30, h)];
44 | }
45 |
46 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated
47 | {
48 | [super setSelected:selected animated:animated];
49 | }
50 |
51 | - (void)setAttachment:(INFile*)attachment
52 | {
53 | [[self imageView] setImage: [attachment localPreview]];
54 |
55 | INUploadFileTask * task = [attachment uploadTask];
56 | [[NSNotificationCenter defaultCenter] removeObserver: self];
57 | if (task) {
58 | [[self progressView] setAlpha: 1];
59 | [[self progressView] setProgress: [task percentComplete] animated: NO];
60 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateProgress:) name:INTaskProgressNotification object:task];
61 | } else {
62 | [[self progressView] setAlpha: 0];
63 | }
64 | }
65 |
66 | - (void)updateProgress:(NSNotification*)notif
67 | {
68 | INUploadFileTask * task = [notif object];
69 |
70 | if (([task percentComplete] >= 1.0) && ([[self progressView] progress] < 1)) {
71 | [UIView animateWithDuration:0.3 delay:0.3 options:UIViewAnimationOptionCurveEaseOut animations:^{
72 | [[self progressView] setAlpha: 0];
73 | } completion:NULL];
74 | }
75 |
76 | [UIView animateWithDuration:0.3 animations:^{
77 | [[self progressView] setProgress: [task percentComplete] animated: YES];
78 | }];
79 | }
80 |
81 | - (void)dealloc
82 | {
83 | [[NSNotificationCenter defaultCenter] removeObserver: self];
84 | }
85 |
86 | - (void)prepareForReuse
87 | {
88 | [[NSNotificationCenter defaultCenter] removeObserver: self];
89 | [super prepareForReuse];
90 | }
91 |
92 | - (void)triggerDeleteCallback:(id)sender
93 | {
94 | if (_deleteCallback)
95 | _deleteCallback();
96 | }
97 |
98 |
99 |
100 | @end
101 |
--------------------------------------------------------------------------------
/BigSur/Views/INAutocompletionResultsView.m:
--------------------------------------------------------------------------------
1 | //
2 | // INAutocompletionResultsView.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/9/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INAutocompletionResultsView.h"
10 | #import "UIView+FrameAdditions.h"
11 |
12 | #define ROW_HEIGHT 36
13 |
14 | @implementation INAutocompletionResultsView
15 |
16 | - (id)initWithFrame:(CGRect)frame
17 | {
18 | self = [super initWithFrame:frame];
19 | if (self) {
20 | _tableView = [[UITableView alloc] initWithFrame: CGRectMake(0, 0, self.frame.size.width, 0)];
21 | [_tableView setDelegate: self];
22 | [_tableView setDataSource: self];
23 | [self addSubview: _tableView];
24 |
25 | [[self layer] setShadowOffset: CGSizeMake(0, 3)];
26 | [[self layer] setShadowRadius: 2];
27 | [[self layer] setShadowOpacity: 0.3];
28 | }
29 | return self;
30 | }
31 |
32 | - (void)setProvider:(INModelProvider *)provider
33 | {
34 | [_provider setDelegate: nil];
35 | _provider = provider;
36 | [_provider setDelegate: self];
37 | [_provider setItemRange:NSMakeRange(0, 3)];
38 | [_tableView reloadData];
39 | }
40 |
41 | - (void)setFrame:(CGRect)frame
42 | {
43 | [super setFrame: frame];
44 | [_tableView setFrame: self.bounds];
45 |
46 | CGPathRef path = CGPathCreateWithRect(self.bounds, NULL);
47 | [[self layer] setShadowPath: path];
48 | CGPathRelease(path);
49 | }
50 |
51 | #pragma mark Autocompletion
52 |
53 | - (void)providerDataChanged:(INModelProvider*)provider
54 | {
55 | [_tableView reloadData];
56 |
57 | CGRect worldFrame = [self convertRect:self.bounds toView:self.window];
58 | float worldMaxY = self.window.frame.size.height - 216;
59 | // todo: make this check if keyboard is actually onscreen
60 |
61 | float resultsHeight = [[_provider items] count] * ROW_HEIGHT;
62 | float availableHeight = worldMaxY - worldFrame.origin.y;
63 | float height = fminf(availableHeight, resultsHeight);
64 | [self in_setFrameHeight: height];
65 | [_tableView setScrollEnabled: (height < resultsHeight)];
66 | }
67 |
68 | - (void)providerDataFetchCompleted:(INModelProvider*)provider
69 | {
70 |
71 | }
72 |
73 | - (void)provider:(INModelProvider*)provider dataFetchFailed:(NSError *)error
74 | {
75 |
76 | }
77 |
78 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
79 | {
80 | return [[_provider items] count];
81 | }
82 |
83 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
84 | {
85 | return ROW_HEIGHT;
86 | }
87 |
88 | - (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
89 | {
90 | UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
91 | if (!cell) cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
92 |
93 | INContact * contact = [[_provider items] objectAtIndex: [indexPath row]];
94 | if ([[contact name] length])
95 | [[cell textLabel] setText: [NSString stringWithFormat:@"%@ (%@)", [contact name],[contact email]]];
96 | else
97 | [[cell textLabel] setText: [contact email]];
98 | [[cell textLabel] setFont: [UIFont systemFontOfSize: 14]];
99 | [[cell textLabel] setTextColor: [UIColor grayColor]];
100 |
101 | return cell;
102 | }
103 |
104 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
105 | {
106 | INContact * contact = [[_provider items] objectAtIndex: [indexPath row]];
107 | if ([self.delegate respondsToSelector: @selector(autocompletionResultPicked:)])
108 | [self.delegate autocompletionResultPicked: contact];
109 |
110 | [self.tableView deselectRowAtIndexPath: indexPath animated:YES];
111 | }
112 |
113 | @end
114 |
--------------------------------------------------------------------------------
/BigSur/Views/BPopdownButton.m:
--------------------------------------------------------------------------------
1 | //
2 | // BPopdownButton.m
3 | // Bloganizer
4 | //
5 | // Created by Ben Gotow on 7/11/13.
6 | // Copyright (c) 2013 Bloganizer Inc. All rights reserved.
7 | //
8 |
9 | #import "BPopdownButton.h"
10 | #import "UIView+FrameAdditions.h"
11 |
12 | @implementation BPopdownButton
13 |
14 | - (id)initWithFrame:(CGRect)frame
15 | {
16 | self = [super initWithFrame:frame];
17 | if (self) {
18 | [self setup];
19 | }
20 | return self;
21 | }
22 |
23 | - (void)awakeFromNib
24 | {
25 | [self setup];
26 | }
27 |
28 | - (void)setup
29 | {
30 | _menu = [[BPopdownMenu alloc] init];
31 | _menuBackground = [[UIView alloc] initWithFrame: CGRectMake(0, 0, 1000, 1000)];
32 | [_menuBackground setBackgroundColor: [UIColor colorWithWhite:0 alpha:0.1]];
33 | [_menuBackground addGestureRecognizer: [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissMenu)]];
34 | [self addTarget:self action:@selector(toggleMenu:) forControlEvents:UIControlEventTouchUpInside];
35 | }
36 |
37 | - (void)setMenuOptions:(NSArray*)options
38 | {
39 | [_menu setOptions: options];
40 | [self setEnabled: [options count] > 0];
41 | }
42 |
43 | - (void)setMenuDelegate:(NSObject*)delegate
44 | {
45 | [_menu setDelegate: delegate];
46 | }
47 |
48 | - (void)toggleMenu:(id)sender
49 | {
50 | if ([_menu superview])
51 | [self dismissMenu];
52 | else
53 | [self presentMenu];
54 | }
55 |
56 | - (void)presentMenu
57 | {
58 | if ([_menu.options count] == 0)
59 | return;
60 |
61 | UIView * v = self;
62 | while ([[v superview] isKindOfClass: [UIWindow class]] == NO)
63 | v = [v superview];
64 |
65 | CGRect root = [self convertRect:self.bounds toView:v];
66 | float menuWidth = [_menu frame].size.width;
67 | float menuY = root.origin.y + root.size.height + 10;
68 |
69 | if (root.origin.x + menuWidth > v.frame.size.width) {
70 | float menuX = fminf(v.frame.size.width - 5 - menuWidth, root.origin.x + root.size.width - menuWidth);
71 | [[_menu layer] setAnchorPoint: CGPointMake((menuWidth - self.bounds.size.width/2 + 5)/menuWidth, 0)];
72 | [_menu in_setFrameOrigin: CGPointMake(menuX, menuY)];
73 | } else {
74 | [[_menu layer] setAnchorPoint: CGPointMake((self.bounds.size.width/2)/menuWidth, 0)];
75 | [_menu in_setFrameOrigin: CGPointMake(root.origin.x, menuY)];
76 | }
77 |
78 | [_menuBackground setAlpha: 0];
79 | [v addSubview: _menuBackground];
80 |
81 | [_menu setAlpha: 0];
82 | [_menu setTransform: CGAffineTransformMakeScale(0.9, 0.9)];
83 | [v addSubview: _menu];
84 |
85 | [UIView animateWithDuration:0.25 animations:^{
86 | [UIView setAnimationCurve: UIViewAnimationCurveEaseOut];
87 | [_menu setAlpha: 1];
88 | [_menuBackground setAlpha: 1];
89 | [_menu setTransform: CGAffineTransformMakeScale(1.05, 1.05)];
90 | } completion:^(BOOL finished) {
91 | [UIView animateWithDuration:0.1 animations:^{
92 | [UIView setAnimationCurve: UIViewAnimationCurveEaseOut];
93 | [_menu setTransform: CGAffineTransformIdentity];
94 | }];
95 | }];
96 | }
97 |
98 | - (void)dismissMenu
99 | {
100 | [UIView animateWithDuration:0.08 animations:^{
101 | [UIView setAnimationCurve: UIViewAnimationCurveEaseInOut];
102 | [_menu setTransform: CGAffineTransformMakeScale(1.05, 1.05)];
103 | [_menuBackground setAlpha: 0];
104 | } completion:^(BOOL finished) {
105 | [UIView animateWithDuration:0.15 animations:^{
106 | [UIView setAnimationCurve: UIViewAnimationCurveEaseIn];
107 | [_menu setTransform: CGAffineTransformMakeScale(0.7, 0.7)];
108 | [_menu setAlpha: 0];
109 | [_menu in_shiftFrame: CGPointMake(0, -15)];
110 | } completion:^(BOOL finished) {
111 | [_menu setTransform: CGAffineTransformIdentity];
112 | [_menu removeFromSuperview];
113 | [_menuBackground removeFromSuperview];
114 | }];
115 | }];
116 | }
117 |
118 | @end
119 |
--------------------------------------------------------------------------------
/BigSur/Views/BPopdownMenu.m:
--------------------------------------------------------------------------------
1 | //
2 | // BPopdownMenu.m
3 | // Bloganizer
4 | //
5 | // Created by Ben Gotow on 7/10/13.
6 | // Copyright (c) 2013 Bloganizer Inc. All rights reserved.
7 | //
8 |
9 | #import "BPopdownMenu.h"
10 | #import "UIView+FrameAdditions.h"
11 |
12 | #define ITEM_FONT [UIFont fontWithName:@"HelveticaNeue" size:15]
13 |
14 | @implementation BPopdownMenu
15 |
16 | - (id)init
17 | {
18 | self = [super initWithFrame:CGRectMake(0, 0, 160, 0)];
19 | if (self) {
20 | _tableView = [[UITableView alloc] initWithFrame: self.bounds];
21 | [_tableView setDelegate: self];
22 | [_tableView setDataSource: self];
23 | [_tableView setRowHeight: 36];
24 | [_tableView setScrollEnabled: NO];
25 | [_tableView setBounces: NO];
26 | [_tableView setAlwaysBounceVertical: NO];
27 | [_tableView setBackgroundColor: [UIColor whiteColor]];
28 | [[_tableView layer] setCornerRadius: 2];
29 | [_tableView setClipsToBounds: YES];
30 | [self addSubview: _tableView];
31 |
32 | _optionChecked = -1;
33 |
34 | _arrow = [[UIImageView alloc] initWithFrame: CGRectMake(0, -14, 16, 16)];
35 | [_arrow setImage: [UIImage imageNamed: @"popover_arrow.png"]];
36 | [self setClipsToBounds: NO];
37 | [self addSubview: _arrow];
38 |
39 | [[self layer] setBackgroundColor: [[UIColor whiteColor] CGColor]];
40 | [[self layer] setCornerRadius: 3];
41 | [[self layer] setShadowOffset: CGSizeMake(0, 1)];
42 | [[self layer] setShadowOpacity: 0.3];
43 | [[self layer] setShadowRadius: 5];
44 | }
45 | return self;
46 | }
47 |
48 | - (void)layoutSubviews
49 | {
50 | [super layoutSubviews];
51 | [_tableView setFrame: CGRectInset(self.bounds, 4,4)];
52 | [_arrow in_setFrameX: [[self layer] anchorPoint].x * (self.bounds.size.width) - _arrow.bounds.size.width/2];
53 | }
54 |
55 | - (void)setOptions:(NSArray *)options
56 | {
57 | _options = options;
58 |
59 | float maxWidth = 0;
60 | for (NSString * option in options) {
61 | float optionWidth = 40 + [option boundingRectWithSize:CGSizeMake(1000, 30) options:NSStringDrawingUsesDeviceMetrics attributes:@{NSFontAttributeName: ITEM_FONT} context:nil].size.width;
62 | maxWidth = fmaxf(maxWidth, optionWidth);
63 | }
64 |
65 | [self in_setFrameHeight: [_options count] * 36 + 4];
66 | [self in_setFrameWidth: maxWidth];
67 | [_tableView reloadData];
68 | }
69 |
70 | - (void)setCheckedItemIndex:(int)index
71 | {
72 | _optionChecked = index;
73 | [_tableView reloadData];
74 | }
75 |
76 |
77 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
78 | {
79 | return [_options count];
80 | }
81 |
82 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
83 | {
84 | UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier: @"cell"];
85 | if (!cell)
86 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
87 |
88 | UIView * backView = [[UIView alloc] initWithFrame:cell.frame];
89 | backView.backgroundColor = [UIColor whiteColor];
90 | cell.selectedBackgroundView = backView;
91 |
92 | [cell setAccessoryType: (_optionChecked == [indexPath row]) ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone];
93 |
94 | [[cell textLabel] setHighlightedTextColor: [UIColor blackColor]];
95 | [[cell textLabel] setTextColor: [UIColor colorWithWhite:0.1 alpha:1]];
96 | [[cell textLabel] setFont: ITEM_FONT];
97 | [[cell textLabel] setText: [_options objectAtIndex: [indexPath row]]];
98 | return cell;
99 | }
100 |
101 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
102 | {
103 | if ([_delegate respondsToSelector: @selector(popdownMenu:optionSelected:)])
104 | [_delegate popdownMenu:self optionSelected:[indexPath row]];
105 |
106 | [tableView deselectRowAtIndexPath:indexPath animated:YES];
107 | }
108 |
109 |
110 | @end
111 |
--------------------------------------------------------------------------------
/BigSur/Views/INComposeAttachmentsRowView.m:
--------------------------------------------------------------------------------
1 | //
2 | // INComposeAttachmentsRowView.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/9/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INComposeAttachmentsRowView.h"
10 | #import "INAttachmentTableViewCell.h"
11 | #import "UIView+FrameAdditions.h"
12 |
13 | @implementation INComposeAttachmentsRowView
14 |
15 | - (id)initWithFrame:(CGRect)frame
16 | {
17 | self = [super initWithFrame:frame];
18 | if (self) {
19 | _attachmentsTableView = [[UITableView alloc] initWithFrame: CGRectZero];
20 | [_attachmentsTableView setScrollEnabled: NO];
21 | [_attachmentsTableView setSeparatorStyle: UITableViewCellSeparatorStyleNone];
22 | [_attachmentsTableView setTranslatesAutoresizingMaskIntoConstraints: NO];
23 | [_attachmentsTableView setDataSource: self];
24 | [_attachmentsTableView setBackgroundColor: [UIColor clearColor]];
25 | [self addSubview: _attachmentsTableView];
26 | self.bodyView = _attachmentsTableView;
27 |
28 | self.animatesBottomBorder = YES;
29 | }
30 | return self;
31 | }
32 |
33 | - (CGSize)intrinsicContentSize
34 | {
35 | NSArray * attachments = [self.delegate attachmentsForAttachmentsView: self];
36 | if ([attachments count] == 0)
37 | return CGSizeMake(UIViewNoIntrinsicMetric, 0);
38 |
39 | return CGSizeMake(UIViewNoIntrinsicMetric, [attachments count] * [_attachmentsTableView rowHeight] + 8);
40 | }
41 |
42 | - (void)setDelegate:(NSObject *)delegate
43 | {
44 | _delegate = delegate;
45 | [_attachmentsTableView reloadData];
46 | }
47 |
48 | - (void)layoutSubviews
49 | {
50 | [super layoutSubviews];
51 | [_attachmentsTableView setFrame: CGRectMake(0, 3, self.frame.size.width, 1000)];
52 | }
53 |
54 | - (void)animateAttachmentAdditionAtIndex:(NSInteger)index withBlock:(VoidBlock)block
55 | {
56 | [self animateAttachmentChange:^{
57 | block();
58 | } withTableUpdates:^{
59 | [_attachmentsTableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForItem:index inSection:0]] withRowAnimation:UITableViewRowAnimationTop];
60 | }];
61 | }
62 |
63 | - (void)animateAttachmentRemovalAtIndex:(NSInteger)index withBlock:(VoidBlock)block
64 | {
65 | [self animateAttachmentChange:^{
66 | block();
67 | } withTableUpdates:^{
68 | [_attachmentsTableView deleteRowsAtIndexPaths:@[[NSIndexPath indexPathForItem:index inSection:0]] withRowAnimation:UITableViewRowAnimationRight];
69 | }];
70 | }
71 |
72 | - (void)animateAttachmentChange:(VoidBlock)changeBlock withTableUpdates:(VoidBlock)tableBlock
73 | {
74 | [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
75 | [_attachmentsTableView beginUpdates];
76 | changeBlock();
77 | [self invalidateIntrinsicContentSize];
78 | [self updateConstraints];
79 | [self.superview layoutIfNeeded];
80 | [self positionBottomBorder];
81 | tableBlock();
82 | [_attachmentsTableView endUpdates];
83 | } completion:^(BOOL finished) {
84 | }];
85 | }
86 |
87 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
88 | {
89 | NSArray * attachments = [self.delegate attachmentsForAttachmentsView: self];
90 | return [attachments count];
91 | }
92 |
93 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
94 | {
95 | INAttachmentTableViewCell * cell = (INAttachmentTableViewCell*)[tableView dequeueReusableCellWithIdentifier:@"cell"];
96 | if (!cell) cell = [[INAttachmentTableViewCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier:@"cell"];
97 |
98 | NSArray * attachments = [self.delegate attachmentsForAttachmentsView: self];
99 | INFile * attachment = [attachments objectAtIndex: [indexPath row]];
100 | [cell setAttachment: attachment];
101 | [cell setDeleteCallback: ^{
102 | NSUInteger index = [attachments indexOfObject: attachment];
103 | [[self delegate] attachmentsView:self confirmRemoveAttachmentAtIndex: index];
104 | }];
105 |
106 | return cell;
107 | }
108 |
109 | @end
110 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Inbox App Scaffold - iOS
2 | ======
3 |
4 | ## **NOTE**: This scaffold is currently not actively maintained, and may need some TLC. Please feel free to use it and send us a pull request if you fix anything or add a feature, though. :)
5 |
6 | The Inbox iOS App Scaffold is a full-featured mail client built on top of the Inbox API. It leverages the SQLite cache and model layer of the [Inbox iOS SDK](https://github.com/inboxapp/inbox-ios)'s, and adds the pre-packaged views and controllers you need to build a first-class mail experience. We've created a polished composer with tokenizing recipient fields, collection view and table view cells for displaying mail content, and more. Start your next project with the Inbox App Scaffold and focus on creating delightful interactions!
7 |
8 |
9 |
10 |
11 |
12 |
13 | ## Features
14 |
15 | - Thread List: View threads in a table view with support for popular interactions like pull-to-refresh, swipe-to-archive, and infinite scrolling.
16 | - Thread Detail: View messages in a collection view with mobile-optimized HTML message bodies, gravatar support, and draft actions.
17 | - Composer: Create, edit, and send drafts with a beautiful Gmail-style composer. Includes tokenizing recipient fields powered by UICollectionView, autocompletion with the Inbox Contacts API, and support for uploading attachments with progress indicators.
18 | - Tags: Switch tags from the sidebar. Browse your inbox or view built-in tags like Archive or Flagged and custom tags created via Inbox.
19 | - Offline Access: The app scaffold is backed by the iOS SDK's SQLite cache and automatically queues actions like archiving and sending for completion when internet is available, even complex chained interactions like creating a draft, adding an attachment, and then sending it.
20 |
21 |
22 | ## By Developers, For Developers
23 |
24 | The Inbox App Scaffold is intended for developers - to use it, you need an Inbox Developer Program membership or a copy of the open-source [Inbox Sync Engine](http://github.com/inboxapp/inbox). When you download or fork the Inbox App Scaffold, you'll need to add your Inbox App ID before you can connect your account.
25 |
26 | ### Environment Setup
27 |
28 | The Inbox iOS App Scaffold uses Cocoapods, a dependency management system for iOS apps similiar to npm and rpm. To set up your local development environment, you'll need to install cocoapods and do a pod install.
29 |
30 | 1. `sudo gem install cocoapods`
31 |
32 | 2. `cd `
33 |
34 | 3. `pod install`. After Cocoapods has installed dependencies, open the project's .xcworkspace.
35 |
36 | 4. Open the app's `Info.plist` file in Xcode. Before you can run the app and authenticate an account with Inbox, you need to create an App ID by signing in to your Inbox Developer Account and creating a new application.
37 | - Fill in the `INAppID` with a valid App ID.
38 | - Update the URL Scheme for the `inbox-api` URL Type to be `in-`
39 |
40 |
41 | ### Communication
42 |
43 | - If you need help, use **Stack Overflow**. (Tag 'inbox-ios')
44 | - If you'd like to ask a general question, use **Stack Overflow**.
45 | - If you **found a bug**, open an issue.
46 | - If you **have a feature request**, open an issue.
47 | - If you **want to contribute**, submit a pull request.
48 |
49 | ### Tips
50 |
51 | 1. If you're extending the functionality of the Inbox iOS SDK while developing your application, you may want to check out the Inbox-iOS repository and update the app scaffold `Podfile` to point to your local copy of the SDK. We welcome pull requests against the Inbox iOS SDK repository as well as the app scaffold!
52 |
53 | ```ruby
54 | target "Inbox" do
55 | pod 'InboxKit', :path => "../Inbox-iOS"
56 | end
57 | ```
58 |
59 | 2. If you're debugging network interaction, check out [the Charles HTTP Proxy](http://www.charlesproxy.com/). You can configure it to [intercept SSL requests](http://www.charlesproxy.com/documentation/faqs/ssl-connections-from-within-iphone-applications/) with the iOS Simulator.
60 |
61 | 3. If you're debugging view issues, you can save time (and get bonus points) with the [Spark Inspector](http://sparkinspector.com/), developed by the Inbox iOS lead!
62 |
--------------------------------------------------------------------------------
/BigSur/Controllers/INSidebarViewController.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
41 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/BigSur/INPluginManager.m:
--------------------------------------------------------------------------------
1 | //
2 | // INPluginManager.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 6/5/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INPluginManager.h"
10 |
11 | @implementation INThread (PluginSupport)
12 |
13 | - (NSArray*)messages
14 | {
15 | NSMutableArray * msgs = [NSMutableArray array];
16 | for (NSString * ID in self.messageIDs)
17 | [msgs addObject: [INMessage instanceWithID:ID inNamespaceID:self.namespaceID]];
18 | return msgs;
19 | }
20 |
21 | @end
22 |
23 | @implementation INMessage (PluginSupport)
24 |
25 | @end
26 |
27 |
28 | @implementation INPluginManager
29 |
30 | + (INPluginManager *)shared
31 | {
32 | static INPluginManager * sharedManager = nil;
33 | static dispatch_once_t onceToken;
34 |
35 | dispatch_once(&onceToken, ^{
36 | sharedManager = [[INPluginManager alloc] init];
37 | });
38 | return sharedManager;
39 | }
40 |
41 | - (id)init
42 | {
43 | self = [super init];
44 | if (self) {
45 | _pluginBackgroundQueue = dispatch_queue_create("Plugin Processing", NULL);
46 | _pluginContexts = [[NSMutableDictionary alloc] init];
47 | [self discoverPlugins];
48 | }
49 | return self;
50 | }
51 |
52 | - (NSURL*)webViewBaseURL
53 | {
54 | return [NSURL fileURLWithPath: _pluginCompiledResources];
55 | }
56 |
57 | - (void)discoverPlugins
58 | {
59 | _pluginNamesByRole = [[NSMutableDictionary alloc] init];
60 | _pluginCompiledResources = [[NSString stringWithFormat:@"~/Documents/Web_Plugin_Root"] stringByExpandingTildeInPath];
61 | [[NSFileManager defaultManager] removeItemAtPath:_pluginCompiledResources error:NULL];
62 | [[NSFileManager defaultManager] createDirectoryAtPath:_pluginCompiledResources withIntermediateDirectories:NO attributes:NULL error:NULL];
63 |
64 | NSArray * pluginPaths = [[NSBundle mainBundle] pathsForResourcesOfType:@"plugin" inDirectory:@"plugins"];
65 | for (NSString * path in pluginPaths) {
66 | NSString * pluginName = [[path lastPathComponent] stringByDeletingPathExtension];
67 | NSString * pluginInfoPath = [path stringByAppendingPathComponent:@"package.json"];
68 | NSDictionary * pluginInfo = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfFile:pluginInfoPath] options:0 error:NULL];
69 |
70 | for (NSString * pluginRole in pluginInfo[@"roles"]) {
71 | NSMutableArray * roles = _pluginNamesByRole[pluginRole];
72 | if (!roles) roles = [NSMutableArray array];
73 | [roles addObject: pluginName];
74 | _pluginNamesByRole[pluginRole] = roles;
75 | }
76 |
77 | NSString * resourcesPath = [path stringByAppendingPathComponent: @"resources"];
78 | for (NSString * resourceName in [[NSFileManager defaultManager] contentsOfDirectoryAtPath:resourcesPath error:NULL]) {
79 | NSString * src = [resourcesPath stringByAppendingPathComponent: resourceName];
80 | NSString * dst = [_pluginCompiledResources stringByAppendingPathComponent: resourceName];
81 | [[NSFileManager defaultManager] copyItemAtPath:src toPath:dst error:NULL];
82 | }
83 | }
84 | }
85 |
86 | - (JSContext*)contextForPluginWithName:(NSString*)name
87 | {
88 | if (_pluginContexts[name])
89 | return _pluginContexts[name];
90 |
91 | NSString * bundlePath = [[NSBundle mainBundle] pathForResource:name ofType:@"plugin" inDirectory:@"plugins"];
92 | NSBundle * pluginBundle = [NSBundle bundleWithPath: bundlePath];
93 |
94 | if (!pluginBundle)
95 | return nil;
96 |
97 | NSString * baseJSPath = [[NSBundle mainBundle] pathForResource:@"compiled" ofType:@"js" inDirectory:@"plugins/base"];
98 | NSString * baseJS = [NSString stringWithContentsOfFile:baseJSPath encoding:NSUTF8StringEncoding error:NULL];
99 |
100 | NSString * pluginJSPath = [pluginBundle pathForResource:@"plugin" ofType:@"js"];
101 | NSString * pluginJS = [NSString stringWithContentsOfFile:pluginJSPath encoding:NSUTF8StringEncoding error:NULL];
102 |
103 | JSContext * context = [[JSContext alloc] initWithVirtualMachine: [[JSVirtualMachine alloc] init]];
104 | context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
105 | context.exception = exception;
106 | NSLog(@"%@", exception);
107 | };
108 |
109 | context[@"app"] = self;
110 | [context evaluateScript: @"plugin = {}"];
111 |
112 | [context evaluateScript: baseJS];
113 | if (context.exception) {
114 | NSLog(@"Plugin %@ not loaded due to exception reading compiled.js base code.", name);
115 | context.exception = nil;
116 | return nil;
117 | }
118 | [context evaluateScript: pluginJS];
119 | if (context.exception) {
120 | NSLog(@"Plugin %@ not loaded due to exception reading plugin.js", name);
121 | context.exception = nil;
122 | return nil;
123 | }
124 |
125 | [_pluginContexts setObject:context forKey:name];
126 | return context;
127 | }
128 |
129 | - (NSArray*)pluginNamesForRole:(NSString*)role
130 | {
131 | return _pluginNamesByRole[role];
132 | }
133 |
134 | #pragma Exposed Methods
135 |
136 | - (void)alert:(NSString*)alert
137 | {
138 | [[[UIAlertView alloc] initWithTitle:@"Plugin" message:alert delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
139 | }
140 |
141 | - (void)log:(NSString*)msg
142 | {
143 | NSLog(@"%@", msg);
144 | }
145 |
146 | - (void)openURL:(NSString*)url
147 | {
148 | [[UIApplication sharedApplication] openURL: [NSURL URLWithString: url]];
149 | }
150 |
151 | - (void)getJSON:(NSString*)url withCallback:(JSValue*)block
152 | {
153 | AFHTTPRequestOperationManager * manager = [[AFHTTPRequestOperationManager alloc] init];
154 | AFHTTPRequestOperation * op = [manager GET:url parameters:NULL success:^(AFHTTPRequestOperation *operation, id responseObject) {
155 | [block callWithArguments: @[responseObject]];
156 | } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
157 | [block callWithArguments: @[[NSNull null], error]];
158 | }];
159 | [op setResponseSerializer: [[AFJSONResponseSerializer alloc] init]];
160 |
161 | }
162 |
163 | @end
164 |
--------------------------------------------------------------------------------
/BigSur/Controllers/INSplitViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // INSplitViewController.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/30/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INSplitViewController.h"
10 | #import "UIView+FrameAdditions.h"
11 |
12 | #define NAV_HEIGHT (44 + 20)
13 |
14 | @implementation INSplitViewController
15 |
16 | - (id)init
17 | {
18 | self = [super init];
19 | if (self) {
20 | _paneViewControllers = [NSMutableArray array];
21 | _paneNavigationBars = [NSMutableArray array];
22 | _paneShadows = [NSMutableArray array];
23 | }
24 | return self;
25 | }
26 |
27 | - (void)viewDidLoad
28 | {
29 | [super viewDidLoad];
30 | }
31 |
32 | - (void)didReceiveMemoryWarning
33 | {
34 | [super didReceiveMemoryWarning];
35 | }
36 |
37 | - (void)setViewControllers:(NSArray*)controllers
38 | {
39 | for (UIViewController * controller in controllers)
40 | [self pushViewController: controller animated: NO];
41 | }
42 |
43 | - (void)pushViewController:(UIViewController*)controller animated:(BOOL)animated
44 | {
45 | NSAssert(([_paneViewControllers count] < 3), @"More than three VCs not currently supported.");
46 |
47 | UINavigationBar * navBar = [[UINavigationBar alloc] initWithFrame: CGRectZero];
48 | [navBar setClipsToBounds: NO];
49 | if (controller.navigationItem) {
50 | [navBar pushNavigationItem:controller.navigationItem animated:NO];
51 | } else {
52 | [navBar setHidden: YES];
53 | }
54 | [_paneNavigationBars addObject: navBar];
55 | [self.view addSubview: navBar];
56 |
57 | [self addChildViewController: controller];
58 | [self.view addSubview: controller.view];
59 | [_paneViewControllers addObject: controller];
60 |
61 | UIView * shadow = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1024, 1024)];
62 | UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(popPane)];
63 | [shadow setAlpha: 0];
64 | [shadow setBackgroundColor: [UIColor colorWithWhite:0 alpha:0.15]];
65 | [shadow addGestureRecognizer: tap];
66 | [shadow setUserInteractionEnabled: YES];
67 | UIImageView * shadowImage = [[UIImageView alloc] initWithFrame: CGRectMake(1024-20, 0, 20, 1024)];
68 | [shadowImage setImage: [UIImage imageNamed: @"frontViewControllerDropShadow.png"]];
69 | [shadow addSubview: shadowImage];
70 | [self.view addSubview: shadow];
71 | [_paneShadows addObject: shadow];
72 |
73 | NSArray * paneWidths = [self paneWidths];
74 | float width = [[paneWidths lastObject] floatValue];
75 | float x = self.view.bounds.size.width;
76 | [navBar setFrame: CGRectMake(x, 0, width, NAV_HEIGHT)];
77 | [controller.view setFrame: CGRectMake(x, NAV_HEIGHT, width, self.view.bounds.size.height - NAV_HEIGHT)];
78 | [shadow in_setFrameX: x - shadow.frame.size.width];
79 |
80 | if (animated) {
81 | [UIView animateWithDuration:0.3 animations:^{
82 | [UIView setAnimationCurve: UIViewAnimationCurveEaseOut];
83 | [self recomputeLayout];
84 | }];
85 | } else {
86 | [self recomputeLayout];
87 | }
88 | }
89 |
90 | - (void)popPane
91 | {
92 | [self popPane: YES];
93 | }
94 |
95 | - (void)popPane:(BOOL)animated
96 | {
97 | UIViewController * last = [_paneViewControllers lastObject];
98 | UINavigationBar * lastBar = [_paneNavigationBars lastObject];
99 | UINavigationBar * lastShadow = [_paneShadows lastObject];
100 |
101 | [_paneViewControllers removeLastObject];
102 | [_paneNavigationBars removeLastObject];
103 | [_paneShadows removeLastObject];
104 |
105 | VoidBlock completion = ^{
106 | [last.view removeFromSuperview];
107 | [last removeFromParentViewController];
108 | [lastBar removeFromSuperview];
109 | [lastShadow removeFromSuperview];
110 | };
111 |
112 | if (animated) {
113 | [UIView animateWithDuration:0.3 animations:^{
114 | [UIView setAnimationCurve: UIViewAnimationCurveEaseOut];
115 | [self recomputeLayout];
116 | [last.view in_setFrameX: self.view.bounds.size.width];
117 | [lastBar in_setFrameX: self.view.bounds.size.width];
118 | [lastShadow in_setFrameX: self.view.bounds.size.width - lastShadow.frame.size.width];
119 | [lastShadow setAlpha: 0];
120 |
121 | } completion:^(BOOL finished) {
122 | completion();
123 | }];
124 | } else {
125 | completion();
126 | }
127 | }
128 |
129 | - (NSArray*)paneWidths
130 | {
131 | NSUInteger count = [_paneViewControllers count];
132 | if (count == 2)
133 | return @[@(256), @(512)];
134 | if (count == 3)
135 | return @[@(256), @(512), @(452)];
136 |
137 | return @[@(self.view.bounds.size.width)];
138 | }
139 |
140 | - (BOOL)paneOccluded:(int)index
141 | {
142 | if (index >= [_paneViewControllers count] - 1)
143 | return NO;
144 |
145 | UIViewController * pane = [_paneViewControllers objectAtIndex: index];
146 | UIViewController * next = [_paneViewControllers objectAtIndex: index + 1];
147 | return (next.view.frame.origin.x < (pane.view.frame.origin.x + pane.view.frame.size.width));
148 | }
149 |
150 | - (void)recomputeLayout
151 | {
152 | NSArray * paneWidths = [self paneWidths];
153 | float paneCount = [_paneViewControllers count];
154 | float lastWidth = [[paneWidths lastObject] floatValue];
155 | BOOL lastOccluded = NO;
156 | float x = 0;
157 |
158 | for (int ii = 0; ii < paneCount; ii ++) {
159 | UIViewController * pane = [_paneViewControllers objectAtIndex: ii];
160 | UINavigationBar * navbar = [_paneNavigationBars objectAtIndex: ii];
161 | UIImageView * shadow = [_paneShadows objectAtIndex: ii];
162 |
163 | float w = [[paneWidths objectAtIndex: ii] floatValue];
164 | float y = ([navbar isHidden] ? 0 : NAV_HEIGHT);
165 | [navbar setFrame:CGRectMake(x, 0, w, NAV_HEIGHT)];
166 | [[pane view] setFrame: CGRectMake(x, y, w, self.view.bounds.size.height-y)];
167 | [shadow in_setFrameX: x - [shadow frame].size.width];
168 | [shadow setAlpha: (lastOccluded ? 1.0 : 0.0)];
169 |
170 | float visibleW = fminf(w, ((self.view.bounds.size.width - lastWidth - x) / ([_paneViewControllers count] - 2)));
171 | if ((paneCount == 3) && (ii == 0)) {
172 | visibleW = 60;
173 | }
174 | x += visibleW;
175 | lastOccluded = (w != visibleW);
176 | }
177 | }
178 |
179 | - (void)viewDidLayoutSubviews
180 | {
181 | [self recomputeLayout];
182 | }
183 |
184 | - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
185 | {
186 | [self recomputeLayout];
187 | }
188 |
189 | @end
190 |
--------------------------------------------------------------------------------
/BigSur/Views/INMessageContentView.m:
--------------------------------------------------------------------------------
1 | //
2 | // INMessageContentView.m
3 | // BigSur
4 | //
5 | // Created by Ben Gotow on 5/23/14.
6 | // Copyright (c) 2014 Inbox. All rights reserved.
7 | //
8 |
9 | #import "INMessageContentView.h"
10 | #import "UIView+FrameAdditions.h"
11 | #import "INPluginManager.h"
12 |
13 | static NSString * messageCSS = @"\
14 | html, body {\
15 | font-family: sans-serif;\
16 | font-size:0.9em;\
17 | margin-top:%dpx;\
18 | margin-left:%dpx;\
19 | margin-bottom:%dpx;\
20 | margin-right:%dpx;\
21 | border:0;\
22 | width:%dpx;\
23 | -webkit-text-size-adjust: auto;\
24 | word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;\
25 | }\
26 | a {\
27 | color:rgb(%d,%d,%d);\
28 | }\
29 | div {\
30 | max-width:100%%;\
31 | }\
32 | .gmail_extra {\
33 | display:none;\
34 | }\
35 | blockquote, .gmail_quote {\
36 | display:none;\
37 | }\
38 | img {\
39 | max-width: 100%;\
40 | height:auto;\
41 | }";
42 |
43 | @implementation INMessageContentView
44 |
45 | - (id)initWithFrame:(CGRect)frame
46 | {
47 | self = [super initWithFrame:frame];
48 | if (self) {
49 | // Initialization code
50 | }
51 | return self;
52 | }
53 |
54 | - (void)awakeFromNib
55 | {
56 | [super awakeFromNib];
57 | [self setup];
58 | }
59 |
60 | - (void)setup
61 | {
62 | [self setClipsToBounds: YES];
63 | if (!_tintColor)
64 | [self setTintColor: [UIColor blueColor]];
65 | }
66 |
67 | - (void)layoutSubviews
68 | {
69 | [super layoutSubviews];
70 | if (_contentLoadCompleted) {
71 | [_textView setFrame: self.bounds];
72 | [_webView setFrame: self.bounds];
73 | }
74 | }
75 |
76 | - (void)setFrame:(CGRect)frame
77 | {
78 | BOOL viewportWidthChange = (frame.size.width != self.frame.size.width);
79 | [super setFrame: frame];
80 | if (viewportWidthChange)
81 | [self setContent: _content];
82 | }
83 |
84 | - (void)clearContent
85 | {
86 | [_webView removeFromSuperview];
87 | [_webView setDelegate: nil];
88 | _webView = nil;
89 |
90 | [_textView setText: @""];
91 |
92 | _contentLoadCompleted = NO;
93 | }
94 |
95 | - (void)setContent:(NSString*)content
96 | {
97 | _content = content;
98 | _contentLoadCompleted = NO;
99 |
100 | if ([content rangeOfString:@"<[^<]+>" options:NSRegularExpressionSearch].location != NSNotFound)
101 | [self setContentWebView: content];
102 | else
103 | [self setContentTextView: content];
104 | }
105 |
106 | - (void)setContentMargin:(UIEdgeInsets)margin
107 | {
108 | _contentMargin = margin;
109 | }
110 |
111 | - (void)setContentWebView:(NSString*)content
112 | {
113 | [_textView removeFromSuperview];
114 | _textView = nil;
115 |
116 | if (!_webView) {
117 | /* Note: It's important the web view has a small initial height because it always
118 | reports it's rendered content size to be at least it's height. We'll make it the
119 | appropriate size once it's content loads. This height must be > 0 or it won't load
120 | at all. */
121 | _webView = [[UIWebView alloc] initWithFrame: CGRectMake(0, 0, self.frame.size.width, 5)];
122 | [_webView setDelegate: self];
123 | [_webView setTintColor: _tintColor];
124 | [_webView setScalesPageToFit: YES];
125 | [_webView setDataDetectorTypes: UIDataDetectorTypeAll];
126 | [_webView setBackgroundColor:[UIColor whiteColor]];
127 | [[_webView scrollView] setScrollEnabled: NO];
128 | [[_webView scrollView] setBackgroundColor:[UIColor whiteColor]];
129 | [self addSubview: _webView];
130 | }
131 |
132 | float s = 1.0 / [[UIScreen mainScreen] scale];
133 | int viewportWidth = self.frame.size.width - (_contentMargin.left + _contentMargin.right);
134 |
135 | const CGFloat * components = CGColorGetComponents([[self tintColor] CGColor]);
136 | int tintR = (int)(components[0] * 256);
137 | int tintG = (int)(components[1] * 256);
138 | int tintB = (int)(components[2] * 256);
139 |
140 |
141 | NSString * css = [NSString stringWithFormat: messageCSS, (int)(_contentMargin.top * s), (int)(_contentMargin.left * s), (int)(_contentMargin.bottom * s), (int)(_contentMargin.right * s), viewportWidth, tintR, tintG, tintB];
142 | NSString * html = [NSString stringWithFormat: @"\n%@", css, viewportWidth, content];
143 | [html writeToFile:[@"~/Documents/test_email.html" stringByExpandingTildeInPath] atomically:NO encoding:NSUTF8StringEncoding error:nil];
144 |
145 | [_webView setAlpha: 0.01];
146 | [_webView loadHTMLString:html baseURL: _contentBaseURL];
147 | }
148 |
149 | - (void)setContentTextView:(NSString*)content
150 | {
151 | [_webView removeFromSuperview];
152 | [_webView setDelegate: nil];
153 | _webView = nil;
154 |
155 | if (!_textView) {
156 | _textView = [[UITextView alloc] initWithFrame: CGRectMake(0, 0, self.bounds.size.width, 1000)];
157 | [_textView setEditable: NO];
158 | [_textView setDataDetectorTypes: UIDataDetectorTypeAll];
159 | [_textView setTintColor: _tintColor];
160 | [_textView setFont: [UIFont systemFontOfSize: 13]];
161 | [_textView setTextContainerInset: _contentMargin];
162 | [_textView setScrollEnabled: NO];
163 | [self addSubview: _textView];
164 | }
165 |
166 | [_textView setText: content];
167 | CGSize size = [_textView sizeThatFits: CGSizeMake(self.bounds.size.width, MAXFLOAT)];
168 | [_textView setFrame: CGRectMake(0, 0, size.width, size.height)];
169 |
170 | _contentLoadCompleted = YES;
171 | if ([self.delegate respondsToSelector: @selector(messageContentViewSizeDetermined:)])
172 | [self.delegate messageContentViewSizeDetermined: size];
173 | }
174 |
175 | - (void)webViewDidFinishLoad:(UIWebView *)webView
176 | {
177 | CGSize s = _webView.scrollView.contentSize;
178 | [_webView in_setFrameHeight: s.height];
179 | [_webView setAlpha: 1];
180 |
181 | _contentLoadCompleted = YES;
182 |
183 | if ([self.delegate respondsToSelector: @selector(messageContentViewSizeDetermined:)]) {
184 | [self.delegate messageContentViewSizeDetermined: s];
185 | }
186 | }
187 |
188 | - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
189 | {
190 | if ((navigationType == UIWebViewNavigationTypeOther) || (navigationType == UIWebViewNavigationTypeReload))
191 | return YES;
192 |
193 | [[UIApplication sharedApplication] openURL: [request URL]];
194 | return NO;
195 | }
196 |
197 | @end
198 |
--------------------------------------------------------------------------------