Pushr (master) :  summary log tree commit diff
path: root/MobilePushr.m blob: e687890165e952bde245c2e5894b408340433b7b
1/*
2 * MobilePushr.m
3 * -------------
4 * The main UIApplication subclass - everything starts and ends here.
5 *
6 * Author: Chris Lee <clee@mg8.org>
7 * License: GPL v2 <http://www.opensource.org/licenses/gpl-license.php>
8 */
9#import <Foundation/Foundation.h>
10#import <CoreFoundation/CoreFoundation.h>
11#import <GraphicsServices/GraphicsServices.h>
12#import <UIKit/CDStructures.h>
13#import <UIKit/UIKit.h>
14#import <UIKit/UIPushButton.h>
15#import <UIKit/UIPushButton-Original.h>
16#import <UIKit/UIControl.h>
17#import <UIKit/UITableCell.h>
18#import <UIKit/UIPreferencesTableCell.h>
19#import <UIKit/UIHardware.h>
20#import <UIKit/UIImage.h>
21#import <UIKit/UIImageView.h>
22#import <UIKit/UIView.h>
23#import <UIKit/UIWindow.h>
24#import <UIKit/UIProgressBar.h>
25#import <UIKit/UITextField.h>
26#import <UIKit/UITextTraits.h>
27#import <UIKit/UIAlertSheet.h>
28#import <UIKit/UIValueButton.h>
29#import <UIKit/UIView-Hierarchy.h>
30#import <UIKit/UIView-Rendering.h>
31#import <UIKit/UIThreePartButton.h>
32#import <UIKit/UIThreePartImageView.h>
33
34#import "MobilePushr.h"
35#import "PushrNetUtil.h"
36#import "Flickr.h"
37#import "PushrSettings.h"
38#import "PushablePhotos.h"
39#import "ExtendedAttributes.h"
40
41#pragma mark Island of Misfit Toys
42typedef enum {
43 kUIControlEventMouseDown = 1 << 0,
44 kUIControlEventMouseMovedInside = 1 << 2, // mouse moved inside control target
45 kUIControlEventMouseMovedOutside = 1 << 3, // mouse moved outside control target
46 kUIControlEventMouseUpInside = 1 << 6, // mouse up inside control target
47 kUIControlEventMouseUpOutside = 1 << 7, // mouse up outside control target
48 kUIControlAllEvents = (kUIControlEventMouseDown | kUIControlEventMouseMovedInside | kUIControlEventMouseMovedOutside | kUIControlEventMouseUpInside | kUIControlEventMouseUpOutside)
49} UIControlEventMasks;
50
51@implementation MobilePushr
52
53#pragma mark MobilePushr Methods
54
55- (NSArray *)cameraRollPhotos
56{
57 _settings = [NSUserDefaults standardUserDefaults];
58 NSString *cameraRollDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Media/DCIM"];
59 NSMutableArray *photos = [NSMutableArray array];
60
61 NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath: cameraRollDir];
62 NSString *jpg;
63 while ((jpg = [dirEnum nextObject])) {
64 if ([[jpg pathExtension] isEqualToString: @"JPG"]) {
65 NSArray *attributes = [ExtendedAttributes allKeysAtPath: [cameraRollDir stringByAppendingPathComponent: jpg]];
66 if ([attributes containsObject: MIGRATED_ATTRIBUTE])
67 continue;
68 // TODO: Ignore this file for now, but in the future, support showing previously-ignored files
69 if ([attributes containsObject: IGNORED_ATTRIBUTE])
70 continue;
71 if ([attributes containsObject: PUSHED_ATTRIBUTE])
72 continue;
73 [photos addObject: [cameraRollDir stringByAppendingPathComponent: jpg]];
74 }
75 }
76
77 return [NSArray arrayWithArray: photos];
78}
79
80- (void)popupFailureAlertSheet
81{
82 UIAlertSheet *alertSheet = [[UIAlertSheet alloc] initWithFrame: CGRectMake(0.0f, 0.0f, 320.0f, 240.0f)];
83 [alertSheet setTitle: @"Bad news, everyone"];
84 [alertSheet setBodyText: @"Somewhere, there's a leak in the pipes, and this application's not Plumbr..."];
85 [alertSheet addButtonWithTitle: @"Accept"];
86 [alertSheet setDelegate: self];
87 [alertSheet setRunsModal: YES];
88 [alertSheet popupAlertAnimated: YES];
89}
90
91- (void)popupEmptyCameraRollAlertSheet
92{
93 UIAlertSheet *alertSheet = [[UIAlertSheet alloc] initWithFrame: CGRectMake(0.0f, 0.0f, 320.0f, 240.0f)];
94 [alertSheet setTitle: @"There are no photos to push"];
95 [alertSheet setBodyText: @"Use the Camera application to put photos into the Camera Roll album."];
96 [alertSheet addButtonWithTitle: @"Quit Pushr"];
97 [alertSheet setDelegate: self];
98 [alertSheet setRunsModal: YES];
99 [alertSheet popupAlertAnimated: YES];
100}
101
102- (void)migratePhotoMetadata
103{
104 _settings = [NSUserDefaults standardUserDefaults];
105 NSString *cameraRollDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Media/DCIM"];
106
107 int currentPhotoIndex = 0;
108
109 NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath: cameraRollDir];
110 NSString *jpg;
111 while ((jpg = [dirEnum nextObject])) {
112 if ([[jpg pathExtension] isEqualToString: @"JPG"]) {
113 currentPhotoIndex = [[[[jpg pathComponents] lastObject] substringWithRange: NSMakeRange(4, 4)] intValue];
114 if (currentPhotoIndex <= [_settings integerForKey: @"lastPushedPhotoIndex"])
115 [ExtendedAttributes setString: @"true" forKey: MIGRATED_ATTRIBUTE atPath: [cameraRollDir stringByAppendingPathComponent: jpg]];
116 }
117 }
118
119 [_settings removeObjectForKey: @"lastPushedPhotoIndex"];
120}
121
122- (void)checkNetworkType
123{
124 _netUtil = [[PushrNetUtil alloc] initWithPushr: self];
125
126 if ([_netUtil hasWiFi])
127 return;
128
129 if ([_netUtil hasEDGE])
130 [_netUtil warnUserAboutSlowEDGE];
131 else
132 [_netUtil drownWithoutNetwork];
133}
134
135- (void)checkCameraRoll
136{
137 _settings = [NSUserDefaults standardUserDefaults];
138 if ([_settings integerForKey: @"lastPushedPhotoIndex"]) {
139 [self migratePhotoMetadata];
140 [_settings removeObjectForKey: @"lastPushedPhotoIndex"];
141 }
142
143 if ([[self cameraRollPhotos] count] == 0)
144 [self popupEmptyCameraRollAlertSheet];
145}
146
147- (void)loadConfiguration
148{
149 _settings = [NSUserDefaults standardUserDefaults];
150 _flickr = [[Flickr alloc] initWithPushr: self];
151
152 if ([_settings boolForKey: @"sentToGetToken"] != TRUE) {
153 NSLog(@"Have to send the user to Flickr to get permission to upload pics.");
154 [_flickr sendToGrantPermission];
155 return;
156 }
157
158 if ([_settings stringForKey: @"frob"] != nil) {
159 NSLog(@"We had a frob - trade it in for a token, the user's NSID, and username.");
160 [_flickr tradeFrobForToken];
161 }
162
163 if ([_settings stringForKey: @"token"] != nil) {
164 NSLog(@"We have a token - test it to make sure it works.");
165 [_flickr checkToken];
166 }
167
168 NSLog(@"Our token is: %@", [_settings stringForKey: @"token"]);
169}
170
171- (void)loadUserInterface
172{
173 struct CGRect hwRect = [UIHardware fullScreenApplicationContentRect];
174 _window = [[UIWindow alloc] initWithContentRect: hwRect];
175
176 struct CGRect appRect = CGRectMake(0.0f, 0.0f, hwRect.size.width, hwRect.size.height);
177 UIView *mainView = [[UIView alloc] initWithFrame: appRect];
178
179 [_window orderFront: self];
180 [_window makeKey: self];
181 [_window setContentView: mainView];
182 [_window _setHidden: NO];
183
184 _pushablePhotos = [[PushablePhotos alloc] initWithFrame: appRect application: self inWindow: _window];
185 [mainView addSubview: _pushablePhotos];
186
187 struct CGRect topBarRect = CGRectMake(0.0f, 0.0f, appRect.size.width, 44.0f);
188 UINavigationBar *topBar = [[UINavigationBar alloc] initWithFrame: topBarRect];
189 [topBar setBarStyle: 1];
190 [topBar setDelegate: self];
191 [topBar showLeftButton: nil withStyle: 0 rightButton: @"Settings" withStyle: 0];
192 [mainView addSubview: topBar];
193
194 UINavigationItem *topBarTitle = [[UINavigationItem alloc] initWithTitle: @"Pushr"];
195 GSFontRef titleFont = GSFontCreateWithName("Helvetica", kGSFontTraitBold, 24.0f);
196 [topBarTitle setFont: titleFont];
197 CFRelease(titleFont);
198
199 struct CGRect botBarRect = CGRectMake(0.0f, (appRect.size.height - 96.0f), appRect.size.width, 96.0f);
200 UIThreePartImageView *bottomBar = [[UIThreePartImageView alloc] initWithFrame: botBarRect];
201 [bottomBar setImage: [UIImage imageNamed: @"bottombar.png"]];
202 CDAnonymousStruct4 barSlices = {
203 .left = { .origin = { .x = 0.0f, .y = 0.0f }, .size = { .width = 2.0f, .height = 96.0f } },
204 .middle = { .origin = { .x = 2.0f, .y = 0.0f }, .size = { .width = 2.0f, .height = 96.0f } },
205 .right = { .origin = { .x = 4.0f, .y = 0.0f }, .size = { .width = 2.0f, .height = 96.0f } },
206 };
207 [bottomBar setSlices: barSlices];
208 [mainView addSubview: bottomBar];
209
210 _button = [[[UIThreePartButton alloc] initWithTitle: @"Push to Flickr" autosizesToFit: YES] autorelease];
211
212 GSFontRef buttonFont = GSFontCreateWithName("Helvetica", kGSFontTraitBold, 22.0f);
213 [_button setTitleFont: buttonFont];
214 CFRelease(buttonFont);
215
216 float blackColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
217 struct CGRect buttonRect = CGRectMake(20.0f, (appRect.size.height - 74.0f), appRect.size.width - 40.0f, 52.0f);
218 [_button setFrame: buttonRect];
219 [_button setPressedBackgroundImage: [UIImage imageNamed: @"mainbutton_pressed.png"]];
220 [_button setBackgroundImage: [UIImage imageNamed: @"mainbutton.png"]];
221 // [_button setImage:[UIImage imageNamed:@"refresh.png"]];
222
223 // Pieces as deduced from the ChooseAudioPhone PNG from MobilePhone.app
224 CDAnonymousStruct4 buttonPieces = {
225 .left = { .origin = { .x = 0.0f, .y = 0.0f }, .size = { .width = 14.0f, .height = 52.0f } },
226 .middle = { .origin = { .x = 15.0f, .y = 0.0f }, .size = { .width = 2.0f, .height = 52.0f } },
227 .right = { .origin = { .x = 17.0f, .y = 0.0f }, .size = { .width = 14.0f, .height = 52.0f } },
228 };
229
230 [_button setBackgroundSlices: buttonPieces];
231
232 [_button setShadowColor: CGColorCreate(CGColorSpaceCreateDeviceRGB(), blackColor)];
233 [_button setShadowOffset: -1.0f];
234 [_button setDrawsShadow: YES];
235
236 [_button addTarget: self action: @selector(buttonReleased) forEvents: kUIControlEventMouseUpInside];
237 [_button setDrawContentsCentered: YES];
238 [_button setEnabled: YES];
239
240 [mainView addSubview: _button];
241
242 [UIView beginAnimations: nil];
243 [UIView setAnimationCurve: kUIAnimationCurveEaseIn];
244 [UIView setAnimationDuration: 1.0];
245
246 [topBar pushNavigationItem: topBarTitle];
247 [UIView endAnimations];
248 [topBar release];
249 [topBarTitle release];
250 [bottomBar release];
251 [mainView release];
252 _thumbnailView = nil;
253}
254
255- (void)loadSettings
256{
257 [[PushrSettings alloc] initFromWindow: _window withPushr: self];
258}
259
260- (void)buttonReleased
261{
262 [_button setEnabled: NO];
263 [_button setBackgroundImage: [UIImage imageNamed: @"mainbutton_inactive.png"]];
264 id mainView = [_button superview];
265 float blackColor[4] = { 0.0f, 0.0f, 0.0f, 0.9f };
266 float transparent[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
267 float white[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
268 _shade = [[UIView alloc] initWithFrame: [mainView frame]];
269 [_shade setBackgroundColor: CGColorCreate(CGColorSpaceCreateDeviceRGB(), blackColor)];
270 [mainView addSubview: _shade];
271 struct CGRect hwRect = [UIHardware fullScreenApplicationContentRect];
272 _label = [[UITextLabel alloc] initWithFrame: CGRectMake(hwRect.origin.x + 20.0f, hwRect.origin.y + 60.0f, hwRect.size.width - 40.0f, 20.0f)];
273 [_label setText: @"Please Wait"];
274 [_label setBackgroundColor: CGColorCreate(CGColorSpaceCreateDeviceRGB(), transparent)];
275 [_label setColor: CGColorCreate(CGColorSpaceCreateDeviceRGB(), white)];
276 [_label setCentersHorizontally: YES];
277 [_shade addSubview: _label];
278
279 _progress = [[UIProgressBar alloc] initWithFrame: CGRectMake(hwRect.origin.x + 20.0f, hwRect.origin.y + 80.0f, hwRect.size.width - 40.0f, 60.0f)];
280 [_progress setProgress: 0];
281 [_progress setStyle: 0];
282 [_shade addSubview: _progress];
283
284 [NSThread detachNewThreadSelector: @selector(triggerUpload:) toTarget: _flickr withObject: [self cameraRollPhotos]];
285}
286
287- (void)startingToPush: (NSString *)photoPath
288{
289 struct CGRect hwRect = [UIHardware fullScreenApplicationContentRect];
290 NSString *thumbnailPath = [[photoPath stringByDeletingPathExtension] stringByAppendingPathExtension: @"THM"];
291 UIImage *thumbnailImage = [UIImage imageAtPath: thumbnailPath];
292
293 struct CGRect thumbnailRect = CGRectMake(hwRect.origin.x + ((hwRect.size.width - [thumbnailImage size].width) / 2.0), hwRect.origin.y + ((hwRect.size.height - [thumbnailImage size].height) / 2.0), [thumbnailImage size].width, [thumbnailImage size].height);
294 _thumbnailView = [[UIImageView alloc] initWithFrame: thumbnailRect];
295 [_thumbnailView setImage: thumbnailImage];
296
297 [_shade addSubview: _thumbnailView];
298}
299
300- (void)donePushing: (NSString *)photoPath
301{
302 [_thumbnailView removeFromSuperview];
303 [_thumbnailView release];
304}
305
306- (void)setLabelText: (NSString *)labelText
307{
308 [_label setText: labelText];
309}
310
311- (void)updateProgress: (NSNumber *)currentProgress
312{
313 [_progress setProgress: [currentProgress floatValue]];
314}
315
316- (void)allDone: (NSArray *)responses
317{
318 [_progress removeFromSuperview];
319 [_label removeFromSuperview];
320 [_shade removeFromSuperview];
321 [_pushablePhotos emptyRoll];
322
323 NSMutableArray *photoIDs = [NSMutableArray array];
324 NSEnumerator *enumerator = [responses objectEnumerator];
325 id responseString = nil;
326 while ((responseString = [enumerator nextObject])) {
327 NSLog(@"ResponseData: %@", responseString);
328 [photoIDs addObject: [[[_flickr getXMLNodesNamed: @"photoid" fromResponse: [responseString dataUsingEncoding: NSUTF8StringEncoding]] lastObject] stringValue]];
329 }
330
331 [_pushablePhotos promptUserToEditPhotos: photoIDs];
332 [_button setEnabled: YES];
333 [_button setBackgroundImage: [UIImage imageNamed: @"mainbutton.png"]];
334}
335
336- (void)applicationDidFinishLaunching: (id) unused
337{
338 [self checkNetworkType];
339 [self checkCameraRoll];
340 [self loadConfiguration];
341 [self loadUserInterface];
342}
343
344#pragma mark Delegate functions here
345
346- (void)alertSheet: (UIAlertSheet *)sheet buttonClicked: (int)button
347{
348 [sheet dismiss];
349 [sheet release];
350
351 switch (button) {
352 default: {
353 [self terminate];
354 }
355 }
356}
357
358- (void)navigationBar: (UINavigationBar *)navBar buttonClicked: (int)button
359{
360 switch (button) {
361 // Show the Settings page
362 case 0: {
363 NSLog(@"Calling loadSettings");
364 [self loadSettings];
365 break;
366 }
367 // Shouldn't happen since there is no left navbar button
368 case 1: {
369 NSLog(@"Shouldn't be here...");
370 break;
371 }
372 }
373}
374
375- (void)dealloc
376{
377 [_progress release];
378 [_label release];
379 [_shade release];
380 [_button release];
381 [_window release];
382 [_flickr release];
383 [_netUtil release];
384 [super dealloc];
385
386}
387
388@end
389