diff --git a/.gitignore b/.gitignore index ea560bc5..e61cc099 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ build *.perspectivev3 *.mode1v3 *.xcworkspacedata -*.xcuserstate \ No newline at end of file +*.xcuserstate +*xcuserdata* diff --git a/AppSalesMobile.xcodeproj/project.pbxproj b/AppSalesMobile.xcodeproj/project.pbxproj index 93e53fb0..0a4ba55a 100755 --- a/AppSalesMobile.xcodeproj/project.pbxproj +++ b/AppSalesMobile.xcodeproj/project.pbxproj @@ -138,6 +138,7 @@ 7FEA5B40105B6CD2000A12E6 /* ReviewTemplate.html in Resources */ = {isa = PBXBuildFile; fileRef = 7FEA5B3F105B6CD2000A12E6 /* ReviewTemplate.html */; }; 7FEA5B43105B6D38000A12E6 /* SingleReviewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FEA5B42105B6D38000A12E6 /* SingleReviewController.m */; }; 7FEA5C2C105C1A57000A12E6 /* 5stars_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 7FEA5C2B105C1A57000A12E6 /* 5stars_gray.png */; }; + B03E6A4D134BC34C00532893 /* AppleFiscalCalendar.m in Sources */ = {isa = PBXBuildFile; fileRef = B03E6A4C134BC34C00532893 /* AppleFiscalCalendar.m */; }; F22984BC11DD32700067EFD2 /* Graphs_Highlighted@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F22984BA11DD32700067EFD2 /* Graphs_Highlighted@2x.png */; }; F22984BD11DD32700067EFD2 /* Graphs@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F22984BB11DD32700067EFD2 /* Graphs@2x.png */; }; F22984C911DD343C0067EFD2 /* 5stars_gray@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F22984C711DD343C0067EFD2 /* 5stars_gray@2x.png */; }; @@ -869,6 +870,8 @@ 7FEA5B42105B6D38000A12E6 /* SingleReviewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SingleReviewController.m; sourceTree = ""; }; 7FEA5C2B105C1A57000A12E6 /* 5stars_gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = 5stars_gray.png; sourceTree = ""; }; 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B03E6A4B134BC34C00532893 /* AppleFiscalCalendar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppleFiscalCalendar.h; sourceTree = ""; }; + B03E6A4C134BC34C00532893 /* AppleFiscalCalendar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppleFiscalCalendar.m; sourceTree = ""; }; F22984BA11DD32700067EFD2 /* Graphs_Highlighted@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Graphs_Highlighted@2x.png"; sourceTree = ""; }; F22984BB11DD32700067EFD2 /* Graphs@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Graphs@2x.png"; sourceTree = ""; }; F22984C711DD343C0067EFD2 /* 5stars_gray@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "5stars_gray@2x.png"; sourceTree = ""; }; @@ -2171,6 +2174,14 @@ 7F4A525A116A9F4E00FA2019 /* NSDateFormatter+SharedInstances.m */, 7FB0C4400EB97BC1005E2C45 /* NSDictionary+HTTP.h */, 7FB0C4410EB97BC1005E2C45 /* NSDictionary+HTTP.m */, + 7FB0C4A90EB99D01005E2C45 /* CurrencyManager.h */, + 7FB0C4AA0EB99D01005E2C45 /* CurrencyManager.m */, + B03E6A4B134BC34C00532893 /* AppleFiscalCalendar.h */, + B03E6A4C134BC34C00532893 /* AppleFiscalCalendar.m */, + 34DCF08E0F0461A2009F9929 /* SFHFKeychainUtils.h */, + 34DCF08D0F0461A2009F9929 /* SFHFKeychainUtils.m */, + 7FE3138E1152891A004DEA7C /* ProgressHUD.h */, + 7FE3138F1152891A004DEA7C /* ProgressHUD.m */, 7FE313A0115294C9004DEA7C /* NSData+Compression.h */, 7FE313A1115294C9004DEA7C /* NSData+Compression.m */, FAED39B811E7E86E003061C4 /* NSString+UnescapeHtml.h */, @@ -3128,6 +3139,7 @@ FA009F1211E93E8200242DFF /* AppManager.m in Sources */, FA027E6E12407BF500067812 /* RegexKitLite.m in Sources */, FAA9E92712E22BB200ADD3C9 /* AppSalesUtils.m in Sources */, + B03E6A4D134BC34C00532893 /* AppleFiscalCalendar.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3239,16 +3251,20 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; GCC_C_LANGUAGE_STANDARD = c99; GCC_DYNAMIC_NO_PIC = YES; + GCC_FAST_MATH = YES; GCC_INLINES_ARE_PRIVATE_EXTERN = YES; + GCC_OPTIMIZATION_LEVEL = 3; + GCC_PREPROCESSOR_DEFINITIONS = "NS_BLOCK_ASSERTIONS=1"; + GCC_STRICT_ALIASING = YES; GCC_VERSION = com.apple.compilers.llvmgcc42; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 3.1; - PREBINDING = YES; + IPHONEOS_DEPLOYMENT_TARGET = 4.0; PRODUCT_NAME = AppSales; PROVISIONING_PROFILE = ""; SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Distribution; }; @@ -3259,13 +3275,16 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; GCC_C_LANGUAGE_STANDARD = c99; GCC_DYNAMIC_NO_PIC = YES; + GCC_FAST_MATH = YES; GCC_INLINES_ARE_PRIVATE_EXTERN = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = "APPSALES_DEBUG=1"; + GCC_STRICT_ALIASING = YES; GCC_VERSION = com.apple.compilers.llvmgcc42; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 4.0; - PREBINDING = YES; PRODUCT_NAME = AppSales; PROVISIONING_PROFILE = ""; SDKROOT = iphoneos; @@ -3281,13 +3300,16 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; GCC_C_LANGUAGE_STANDARD = c99; GCC_DYNAMIC_NO_PIC = YES; + GCC_FAST_MATH = YES; GCC_INLINES_ARE_PRIVATE_EXTERN = YES; + GCC_OPTIMIZATION_LEVEL = 3; + GCC_PREPROCESSOR_DEFINITIONS = "NS_BLOCK_ASSERTIONS=1"; + GCC_STRICT_ALIASING = YES; GCC_VERSION = com.apple.compilers.llvmgcc42; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 3.1; - PREBINDING = YES; + IPHONEOS_DEPLOYMENT_TARGET = 4.0; PRODUCT_NAME = AppSales; PROVISIONING_PROFILE = ""; SDKROOT = iphoneos; diff --git a/Classes/App.h b/Classes/App.h index a499706a..6a869087 100644 --- a/Classes/App.h +++ b/Classes/App.h @@ -17,15 +17,21 @@ NSMutableDictionary *reviewsByUser; NSMutableDictionary *lastTimeRegionDownloaded; // mapping of app store to NSDate of last time reviews fetched float averageStars; + float recentStars; + NSString *recentVersion; } @property (readonly) NSString *appID; @property (readonly) NSString *appName; +@property (readonly) NSString *recentVersion; // the current app version @property (readonly) NSDictionary *reviewsByUser; -@property (readonly) NSUInteger totalReviewsCount; +@property (readonly) NSUInteger totalReviewsCount; // all reviews downloaded, for any version (current or old) @property (readonly) NSUInteger newReviewsCount; +@property (readonly) NSUInteger recentReviewsCount; // reviews for the current app version +@property (readonly) NSUInteger newRecentReviewsCount; // freshly downloaded reviews for the current app version @property (readonly) NSArray *allAppNames; @property (readonly) float averageStars; +@property (readonly) float recentStars; // average stars for the current app version - (id) initWithID:(NSString*)identifier name:(NSString*)name; - (void) addOrReplaceReview:(Review*)review; diff --git a/Classes/App.m b/Classes/App.m index b9e04cae..5a4849a0 100644 --- a/Classes/App.m +++ b/Classes/App.m @@ -11,7 +11,28 @@ @implementation App -@synthesize appID, appName, reviewsByUser, averageStars; +@synthesize appID, appName, reviewsByUser, averageStars, recentStars, recentVersion; + +- (void) updateAverages { + double overallSum = 0; + double mostRecentVersionSum = 0; + int mostRecentVersionCount = 0; + for (Review *r in reviewsByUser.allValues) { + overallSum += r.stars; + if (recentVersion == nil || [recentVersion compare:r.version] == NSOrderedAscending) { + [recentVersion release]; + recentVersion = [r.version retain]; + mostRecentVersionCount = 0; + mostRecentVersionSum = 0; + } + if ([r.version isEqualToString:recentVersion]) { + mostRecentVersionCount++; + mostRecentVersionSum += r.stars; + } + } + averageStars = overallSum / reviewsByUser.count; + recentStars = mostRecentVersionSum / mostRecentVersionCount; +} - (id)initWithCoder:(NSCoder *)coder { self = [super init]; @@ -25,6 +46,12 @@ - (id)initWithCoder:(NSCoder *)coder { if (lastTimeRegionDownloaded == nil) { // backwards compatibility with older serialized objects lastTimeRegionDownloaded = [NSMutableDictionary new]; } + if ([coder containsValueForKey:@"recentVersion"] && [coder containsValueForKey:@"recentStars"]) { + recentVersion = [[coder decodeObjectForKey:@"recentVersion"] retain]; + recentStars = [coder decodeFloatForKey:@"recentStars"]; + } else { + [self updateAverages]; // older serialized object + } } return self; } @@ -78,30 +105,47 @@ - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:appID forKey:@"appID"]; [coder encodeObject:appName forKey:@"appName"]; - [coder encodeObject:reviewsByUser forKey:@"reviewsByUser"]; [coder encodeObject:allAppNames forKey:@"allAppNames"]; [coder encodeObject:lastTimeRegionDownloaded forKey:@"lastTimeRegionDownloaded"]; [coder encodeFloat:averageStars forKey:@"averageStars"]; + [coder encodeObject:reviewsByUser forKey:@"reviewsByUser"]; + [coder encodeObject:recentVersion forKey:@"recentVersion"]; + [coder encodeFloat:recentStars forKey:@"recentStars"]; } - (NSString *) description { - return [NSString stringWithFormat:@"App %@ (%@)", self.appName, self.appID]; + return [NSString stringWithFormat:NSLocalizedString(@"App %@ (%@)", nil), self.appName, self.appID]; } - (void) addOrReplaceReview:(Review*)review { [reviewsByUser setObject:review forKey:review.user]; - - double sum = 0; - for (Review *r in reviewsByUser.allValues) { - sum += r.stars; - } - averageStars = sum / reviewsByUser.count; + [self updateAverages]; } - (NSUInteger) totalReviewsCount { return reviewsByUser.count; } +- (NSUInteger) recentReviewsCount { + NSUInteger recentReviewsCount = 0; + for (Review *r in reviewsByUser.allValues) { + if ([r.version isEqualToString:recentVersion]) { + recentReviewsCount++; + } + } + return recentReviewsCount; +} + +- (NSUInteger) newRecentReviewsCount { + NSUInteger newReviewsCount = 0; + for (Review *r in reviewsByUser.allValues) { + if (r.newOrUpdatedReview && [r.version isEqualToString:recentVersion]) { + newReviewsCount++; + } + } + return newReviewsCount; +} + - (NSUInteger) newReviewsCount { NSUInteger newReviewsCount = 0; for (Review *r in reviewsByUser.allValues) { @@ -119,6 +163,7 @@ - (void) dealloc [reviewsByUser release]; [allAppNames release]; [lastTimeRegionDownloaded release]; + [recentVersion release]; [super dealloc]; } diff --git a/Classes/AppCell.m b/Classes/AppCell.m index 6083027d..18836f7d 100644 --- a/Classes/AppCell.m +++ b/Classes/AppCell.m @@ -51,7 +51,9 @@ @implementation AppCellView - (id)initWithCell:(AppCell *)appCell { - [super initWithFrame:appCell.bounds]; + CGRect bounds = appCell.bounds; + bounds.size.height = 60; + [super initWithFrame:bounds]; self.backgroundColor = [UIColor whiteColor]; cell = appCell; return self; @@ -63,7 +65,7 @@ - (void)drawRect:(CGRect)rect App *app = cell.app; [[UIColor colorWithWhite:0.95 alpha:1.0] set]; - CGContextFillRect(c, CGRectMake(0,0,45,44)); + CGContextFillRect(c, CGRectMake(0,0,45,59)); UIImage *appIcon = [[AppIconManager sharedManager] iconForAppID:app.appID]; [appIcon drawInRect:CGRectMake(6, 7, 28, 28)]; @@ -72,7 +74,7 @@ - (void)drawRect:(CGRect)rect [((cell.highlighted) ? [UIColor whiteColor] : [UIColor blackColor]) set]; [app.appName drawInRect:CGRectMake(50, 3, 140, 30) withFont:[UIFont boldSystemFontOfSize:17.0]]; - [[UIImage imageNamed:@"5stars_gray.png"] drawInRect:CGRectMake(200, 15, 90, 15)]; + [[UIImage imageNamed:@"5stars_gray.png"] drawInRect:CGRectMake(200, 8, 90, 15)]; UIImage *starsImage = [UIImage imageNamed:@"5stars.png"]; CGSize size = CGSizeMake(90,15); if (&UIGraphicsBeginImageContextWithOptions) { @@ -82,7 +84,7 @@ - (void)drawRect:(CGRect)rect } CGContextRef ctx = UIGraphicsGetCurrentContext(); [starsImage drawInRect:CGRectMake(0,0,90,15)]; - float averageStars = [app averageStars]; + float averageStars = [app recentStars]; float widthOfStars = 90.0 - (averageStars / 5.0) * 90.0; [[UIColor clearColor] set]; CGContextSetBlendMode(ctx, kCGBlendModeCopy); @@ -90,22 +92,38 @@ - (void)drawRect:(CGRect)rect UIImage *averageStarsImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); - [averageStarsImage drawInRect:CGRectMake(200, 15, 90, 15)]; + [averageStarsImage drawInRect:CGRectMake(200, 8, 90, 15)]; //[[UIImage imageNamed:@"5stars.png"] drawInRect:CGRectMake(200, 15, 88, 15)]; if(cell.highlighted) [[UIColor whiteColor] set]; - else if(app.newReviewsCount) + else if(app.newRecentReviewsCount) [[UIColor redColor] set]; else [[UIColor darkGrayColor] set]; + + [app.recentVersion drawInRect:CGRectMake(50, 25, 140, 15) withFont:[UIFont systemFontOfSize:12.0]]; + NSString *recentSummary = [NSString stringWithFormat:NSLocalizedString(@"%1.2f avg, %i reviews",nil), app.recentStars, app.recentReviewsCount]; + if (app.newRecentReviewsCount) { + recentSummary = [recentSummary stringByAppendingFormat:NSLocalizedString(@" (%i new)",nil), app.newRecentReviewsCount]; + } + CGSize recentSummarySize = [recentSummary sizeWithFont:[UIFont systemFontOfSize:12.0]]; + [recentSummary drawInRect:CGRectMake(290-recentSummarySize.width, 40-recentSummarySize.height, recentSummarySize.width, recentSummarySize.height) withFont:[UIFont systemFontOfSize:12.0]]; - int numberOfReviews = [app.reviewsByUser count]; - NSString *numberOfReviewsDescription = [NSString stringWithFormat:NSLocalizedString(@"%i reviews",nil), numberOfReviews]; - if (app.newReviewsCount) { - numberOfReviewsDescription = [numberOfReviewsDescription stringByAppendingFormat:NSLocalizedString(@" (%i new)",nil), app.newReviewsCount]; + if(cell.highlighted) + [[UIColor whiteColor] set]; + else if(app.newReviewsCount) + [[UIColor redColor] set]; + else + [[UIColor lightGrayColor] set]; + + [@"Overall" drawInRect:CGRectMake(50, 40, 140, 15) withFont:[UIFont italicSystemFontOfSize:12.0]]; + NSString *overallSummary = [NSString stringWithFormat:NSLocalizedString(@"%1.2f avg, %i reviews",nil), app.averageStars, [app.reviewsByUser count]]; + if (app.newRecentReviewsCount) { + overallSummary = [overallSummary stringByAppendingFormat:NSLocalizedString(@" (%i new)",nil), app.newRecentReviewsCount]; } - [numberOfReviewsDescription drawInRect:CGRectMake(50, 25, 140, 15) withFont:[UIFont systemFontOfSize:12.0]]; + CGSize overallSummarySize = [overallSummary sizeWithFont:[UIFont systemFontOfSize:12.0]]; + [overallSummary drawInRect:CGRectMake(290-overallSummarySize.width, 55-overallSummarySize.height, overallSummarySize.width, overallSummarySize.height) withFont:[UIFont italicSystemFontOfSize:12.0]]; } @end diff --git a/Classes/AppManager.h b/Classes/AppManager.h index 2f43b277..430d7581 100644 --- a/Classes/AppManager.h +++ b/Classes/AppManager.h @@ -19,6 +19,7 @@ - (App*) appWithID:(NSString*)appID; - (void) addApp:(App*)app; - (BOOL) createOrUpdateAppIfNeededWithID:(NSString*)appID name:(NSString*)appName; +- (void) removeAppWithID:(NSString*)appID; - (void) saveToDisk; @end diff --git a/Classes/AppManager.m b/Classes/AppManager.m index 758456b8..dcd7cc7a 100644 --- a/Classes/AppManager.m +++ b/Classes/AppManager.m @@ -54,6 +54,10 @@ - (void) addApp:(App*)app { [appsByID setObject:app forKey:app.appID]; } +- (void) removeAppWithID:(NSString*)appID { + [appsByID removeObjectForKey:appID]; +} + - (BOOL) createOrUpdateAppIfNeededWithID:(NSString*)appID name:(NSString*)appName { App *app = [self appWithID:appID]; if (app == nil) { diff --git a/Classes/AppSalesUtils.h b/Classes/AppSalesUtils.h index 9fbeed36..434e98f2 100644 --- a/Classes/AppSalesUtils.h +++ b/Classes/AppSalesUtils.h @@ -8,6 +8,9 @@ #endif +#define ASSERT_IS_MAIN_THREAD() NSAssert([NSThread isMainThread], @"must call from main thread"); +#define ASSERT_NOT_MAIN_THREAD() NSAssert([NSThread isMainThread] == false, @"do not call from main thread"); + __attribute__((constructor)) // run this function run when the app loads static void InitRandom() { srandom(time(NULL)); diff --git a/Classes/AppleFiscalCalendar.h b/Classes/AppleFiscalCalendar.h new file mode 100644 index 00000000..62b15404 --- /dev/null +++ b/Classes/AppleFiscalCalendar.h @@ -0,0 +1,34 @@ +// +// AppleFiscalCalendar.h +// AppSalesMobile +// +// Created by Tim Shadel on 4/5/11. +// Copyright 2011 Shadel Software, Inc. All rights reserved. +// + +#import + +typedef enum { + DayCalendarTypeCalendar, + DayCalendarTypeAppleFiscal +} DayCalendarType; + + +@interface AppleFiscalCalendar : NSObject { + @private + NSArray *sortedFiscalMonthNames; + NSArray *sortedDateStrings; + NSArray *sortedDates; +} + +/** + * Returns the full month name and year of the fiscal month in which the given date falls. + */ +- (NSString *)fiscalMonthForDate:(NSDate *)date; + +/** + * Returns the shared instance for use anywhere fiscal date information is needed. + */ ++ (AppleFiscalCalendar *)sharedFiscalCalendar; + +@end diff --git a/Classes/AppleFiscalCalendar.m b/Classes/AppleFiscalCalendar.m new file mode 100644 index 00000000..15abc899 --- /dev/null +++ b/Classes/AppleFiscalCalendar.m @@ -0,0 +1,122 @@ +// +// AppleFiscalCalendar.m +// AppSalesMobile +// +// Created by Tim Shadel on 4/5/11. +// Copyright 2011 Shadel Software, Inc. All rights reserved. +// + +#import "AppleFiscalCalendar.h" + +@implementation AppleFiscalCalendar + +- (id)init +{ + self = [super init]; + if (self) { + sortedDateStrings = [[NSArray arrayWithObjects: + @"20080928", // Oct 2008 + @"20081102", // Nov 2008 + @"20081130", // Dec 2008 + @"20081228", // Jan 2009 + @"20090201", // Feb 2009 + @"20090301", // Mar 2009 + @"20090329", // Apr 2009 + @"20090503", // May 2009 + @"20090531", // Jun 2009 + @"20090628", // Jul 2009 + @"20090802", // Aug 2009 + @"20090830", // Sep 2009 + @"20090927", // Oct 2009 + @"20091101", // Nov 2009 + @"20091129", // Dec 2009 + @"20091227", // Jan 2010 + @"20100131", // Feb 2010 + @"20100228", // Mar 2010 + @"20100328", // Apr 2010 + @"20100502", // May 2010 + @"20100530", // Jun 2010 + @"20100629", // Jul 2010 + @"20100801", // Aug 2010 + @"20100829", // Sep 2010 + @"20100926", // Oct 2010 + @"20101031", // Nov 2010 + @"20101128", // Dec 2010 + @"20101226", // Jan 2011 + @"20110130", // Feb 2011 + @"20110227", // Mar 2011 + @"20110327", // Apr 2011 + @"20110501", // May 2011 + @"20110529", // Jun 2011 + @"20110626", // Jul 2011 + @"20110731", // Aug 2011 + @"20110828", // Sep 2011 + nil] retain]; + + NSMutableArray *names = [NSMutableArray arrayWithCapacity:[sortedDateStrings count]]; + NSMutableArray *dates = [NSMutableArray arrayWithCapacity:[sortedDateStrings count]]; + + NSDateFormatter *dayStringParser = [[NSDateFormatter alloc] init]; + [dayStringParser setDateFormat:@"YYYYMMdd"]; + NSDateFormatter *sectionTitleFormatter = [NSDateFormatter new]; + [sectionTitleFormatter setDateFormat:@"MMMM yyyy"]; + NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; + for (NSString *dayString in sortedDateStrings) { + NSDate *date = [dayStringParser dateFromString:dayString]; + [dates addObject:date]; + + // Name of the fiscal month can be reliably found by the calendar month of a day 2 weeks after the fiscal month begins + NSDateComponents *components = [[NSDateComponents alloc] init]; + [components setDay:14]; + NSDate *result = [gregorian dateByAddingComponents:components toDate:date options:0]; + [components release]; + + NSString *fiscalMonthName = [sectionTitleFormatter stringFromDate:result]; + [names addObject:fiscalMonthName]; + } + [gregorian release]; + [dayStringParser release]; + [sectionTitleFormatter release]; + sortedFiscalMonthNames = [[NSArray arrayWithArray:names] retain]; + sortedDates = [[NSArray arrayWithArray:dates] retain]; + } + return self; +} + +- (NSString *)fiscalMonthForDate:(NSDate *)requestedDate +{ + NSUInteger indexOfNextMonth = [sortedDates + indexOfObject:requestedDate + inSortedRange:NSMakeRange(0, [sortedDates count]) + options:NSBinarySearchingLastEqual|NSBinarySearchingInsertionIndex + usingComparator: + ^(id obj1, id obj2){ + // Pass the day if equals, so that we can always go back one index + return [obj1 compare:obj2] == NSOrderedAscending ? NSOrderedAscending : NSOrderedDescending; + }]; + + if (indexOfNextMonth > 0) { + return [sortedFiscalMonthNames objectAtIndex:indexOfNextMonth-1]; + } else { + return nil; + } +} + ++ (AppleFiscalCalendar *)sharedFiscalCalendar +{ + static AppleFiscalCalendar *sharedFiscalCalendar = nil; + if (sharedFiscalCalendar == nil) + sharedFiscalCalendar = [AppleFiscalCalendar new]; + return sharedFiscalCalendar; +} + + +- (void)dealloc +{ + [sortedDates release], sortedDates = nil; + [sortedDateStrings release], sortedDateStrings = nil; + [sortedFiscalMonthNames release], sortedFiscalMonthNames = nil; + [super dealloc]; +} + +@end diff --git a/Classes/DaysController.m b/Classes/DaysController.m index cb025de7..c2562292 100644 --- a/Classes/DaysController.m +++ b/Classes/DaysController.m @@ -36,37 +36,111 @@ #import "CurrencyManager.h" #import "ReportManager.h" #import "NSDateFormatter+SharedInstances.h" +#import "AppleFiscalCalendar.h" +#import "Country.h" +#import "Entry.h" + +static Country *newCountry(NSString *countryName, NSMutableDictionary *countries) +{ + Country *country = [countries objectForKey:countryName]; + if (!country) { + country = [[Country alloc] initWithName:countryName day:nil]; + [countries setObject:country forKey:countryName]; + [country release]; + } + return country; +} + +@interface DaysController () +@property (nonatomic, retain) UIBarButtonItem *fiscalButton; +@property (nonatomic, retain) UIBarButtonItem *calendarButton; +@property (nonatomic, assign) DayCalendarType calendarType; +@end @implementation DaysController +@synthesize fiscalButton, calendarButton, calendarType; + - (id)init { self = [super init]; if (self) { self.title = NSLocalizedString(@"Daily Reports",nil); + + self.fiscalButton = [[[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Fiscal", nil) + style:UIBarButtonItemStyleBordered + target:self + action:@selector(showFiscal:)] autorelease]; + + self.calendarButton = [[[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Calendar", nil) + style:UIBarButtonItemStyleBordered + target:self + action:@selector(showCalendar:)] autorelease]; } return self; } +- (void)showFiscal:(id)sender +{ + self.calendarType = DayCalendarTypeAppleFiscal; + self.navigationItem.rightBarButtonItem = self.calendarButton; + [[NSUserDefaults standardUserDefaults] setInteger:self.calendarType forKey:@"DayCalendarType"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + [self reload]; +} + +- (void)showCalendar:(id)sender +{ + self.calendarType = DayCalendarTypeCalendar; + self.navigationItem.rightBarButtonItem = self.fiscalButton; + [[NSUserDefaults standardUserDefaults] setInteger:self.calendarType forKey:@"DayCalendarType"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + [self reload]; +} + - (void)reload { self.daysByMonth = [NSMutableArray array]; + + self.calendarType = [[NSUserDefaults standardUserDefaults] integerForKey:@"DayCalendarType"]; + if (self.calendarType == DayCalendarTypeCalendar) { + self.navigationItem.rightBarButtonItem = self.fiscalButton; + } else { + self.navigationItem.rightBarButtonItem = self.calendarButton; + } + NSSortDescriptor *dateSorter = [[[NSSortDescriptor alloc] initWithKey:@"date" ascending:NO] autorelease]; NSArray *sortedDays = [[[ReportManager sharedManager].days allValues] sortedArrayUsingDescriptors:[NSArray arrayWithObject:dateSorter]]; - int lastMonth = -1; - NSCalendar *calendar = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease]; - - for (Day *d in sortedDays) { - NSDate *date = d.date; - NSDateComponents *components = [calendar components:NSMonthCalendarUnit fromDate:date]; - int month = [components month]; - if (month != lastMonth) { - [daysByMonth addObject:[NSMutableArray array]]; - lastMonth = month; - } - [[daysByMonth lastObject] addObject:d]; - } + if (self.calendarType == DayCalendarTypeCalendar) { + NSCalendar *calendar = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease]; + NSInteger lastMonth = -1; + + for (Day *d in sortedDays) { + NSDate *date = d.date; + NSDateComponents *components = [calendar components:NSMonthCalendarUnit fromDate:date]; + int month = [components month]; + if (month != lastMonth) { + [self.daysByMonth addObject:[NSMutableArray array]]; + lastMonth = month; + } + [[self.daysByMonth lastObject] addObject:d]; + } + } else { + AppleFiscalCalendar *calendar = [AppleFiscalCalendar sharedFiscalCalendar]; + NSString *lastMonth = nil; + + for (Day *d in sortedDays) { + NSDate *date = d.date; + NSString *month = [calendar fiscalMonthForDate:date]; + if (month && [month compare:lastMonth] != NSOrderedSame) { + [self.daysByMonth addObject:[NSMutableArray array]]; + lastMonth = month; + } + [[self.daysByMonth lastObject] addObject:d]; + } + } + [self.tableView reloadData]; } @@ -98,6 +172,64 @@ - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEd - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + NSInteger count = [self.daysByMonth count]; + if(count > 1 && indexPath.section == count){ + static NSString *CellIdentifier = @"CellTotale"; + + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; + if(cell == nil){ + cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil] autorelease]; + } + + float total = 0.0; + for(NSArray *array in self.daysByMonth){ + for(Day *d in array){ + total += [d totalRevenueInBaseCurrency]; + } + } + + cell.selectionStyle = UITableViewCellSelectionStyleNone; + cell.textLabel.text = [NSLocalizedString(@"Total: ", nil) stringByAppendingString:[[CurrencyManager sharedManager] baseCurrencyDescriptionForAmount:[NSNumber numberWithFloat:total] withFraction:YES]]; + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + + return cell; + } + + NSArray *selectedMonth = [self.daysByMonth objectAtIndex:indexPath.section]; + + BOOL onlySum = NO; + if(onlySum || indexPath.row == [selectedMonth count]){ + static NSString *CellIdentifier = @"CellSubtotale"; + + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; + if(cell == nil){ + cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil] autorelease]; + } + + //cell.selectionStyle = UITableViewCellSelectionStyleNone; + float monthTotal = 0.0; + for(Day *d in [self.daysByMonth objectAtIndex:indexPath.section]){ + monthTotal += [d totalRevenueInBaseCurrency]; + } + + if(!onlySum){ + Day *firstDayInSection = [[self.daysByMonth objectAtIndex:indexPath.section] objectAtIndex:0]; + if (self.calendarType == DayCalendarTypeCalendar) { + cell.textLabel.text = [NSString stringWithFormat:@"%@:", [self.sectionTitleFormatter stringFromDate:firstDayInSection.date]]; + } else { + AppleFiscalCalendar *calendar = [AppleFiscalCalendar sharedFiscalCalendar]; + cell.textLabel.text = [calendar fiscalMonthForDate:firstDayInSection.date]; + } + }else + cell.textLabel.text = NSLocalizedString(@"Subtotal:", nil); + + cell.textLabel.font = [UIFont boldSystemFontOfSize:18]; + cell.detailTextLabel.text = [[CurrencyManager sharedManager] baseCurrencyDescriptionForAmount:[NSNumber numberWithFloat:monthTotal] withFraction:YES]; + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + + return cell; + } + static NSString *CellIdentifier = @"Cell"; DayCell *cell = (DayCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; @@ -124,11 +256,133 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N return cell; } +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + NSInteger count = self.daysByMonth.count; + count = (count > 1 ? count + 1 : 1);//total + return count; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + NSInteger count = self.daysByMonth.count; + BOOL onlySum = NO; + if(count > 1 && section == count){ + return 1;//total + } + + if (count > 0) { + if(onlySum) + return 1; + if(section == count) + return 1;//total + count = [[self.daysByMonth objectAtIndex:section] count]; + if(count > 1) + count++;//subtotal + return count; + } + return 0; +} + +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section +{ + NSInteger count = self.daysByMonth.count; + if(count > 1 && section == count){ + return NSLocalizedString(@"Total:", nil); + } + + if (self.daysByMonth.count == 0) + return @""; + + NSArray *sectionArray = [self.daysByMonth objectAtIndex:section]; + if (sectionArray.count == 0) + return @""; + + Day *firstDayInSection = [sectionArray objectAtIndex:0]; + if (self.calendarType == DayCalendarTypeCalendar) { + return [self.sectionTitleFormatter stringFromDate:firstDayInSection.date]; + } else { + AppleFiscalCalendar *calendar = [AppleFiscalCalendar sharedFiscalCalendar]; + return [calendar fiscalMonthForDate:firstDayInSection.date]; + } +} + + - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { int section = [indexPath section]; int row = [indexPath row]; + + NSString *totalRevenueKey = @"totalRevenueInBaseCurrency"; + NSString *sumTotalRevenueKey = @"@sum.totalRevenueInBaseCurrency"; + + NSInteger count = [self.daysByMonth count]; + if(count > 1 && section == count){ + NSMutableDictionary *countries = [NSMutableDictionary dictionary]; + + for(NSArray *array in self.daysByMonth){ + for(Day *d in array){ + for(Country *c in [d children]){ + Country *country = newCountry(c.name, countries); + for (Entry *e in c.entries) { + [country addEntry:e]; + } + } + } + } + + NSSortDescriptor *sorter = [[[NSSortDescriptor alloc] initWithKey:totalRevenueKey ascending:NO] autorelease]; + NSArray *children = [[countries allValues] sortedArrayUsingDescriptors:[NSArray arrayWithObject:sorter]]; + + float total = [[children valueForKeyPath:sumTotalRevenueKey] floatValue]; + + CountriesController *countriesController = [[[CountriesController alloc] initWithStyle:UITableViewStylePlain] autorelease]; + countriesController.totalRevenue = total; + + countriesController.title = NSLocalizedString(@"All time", nil); + countriesController.countries = children; + [countriesController.tableView reloadData]; + + [[self navigationController] pushViewController:countriesController animated:YES]; + + return; + } + NSArray *selectedMonth = [self.daysByMonth objectAtIndex:section]; + + BOOL onlySum = NO; + if(onlySum || row == [selectedMonth count]){ + NSMutableDictionary *countries = [NSMutableDictionary dictionary]; + + for(Day *d in selectedMonth){ + for(Country *c in [d children]){ + Country *country = newCountry(c.name, countries); + for (Entry *e in c.entries) { + [country addEntry:e]; + } + } + } + + NSSortDescriptor *sorter = [[[NSSortDescriptor alloc] initWithKey:totalRevenueKey ascending:NO] autorelease]; + NSArray *children = [[countries allValues] sortedArrayUsingDescriptors:[NSArray arrayWithObject:sorter]]; + + float total = [[children valueForKeyPath:sumTotalRevenueKey] floatValue]; + + CountriesController *countriesController = [[[CountriesController alloc] initWithStyle:UITableViewStylePlain] autorelease]; + countriesController.totalRevenue = total; + + Day *firstDayInSection = [[self.daysByMonth objectAtIndex:section] objectAtIndex:0]; + countriesController.title = [self.sectionTitleFormatter stringFromDate:firstDayInSection.date]; + countriesController.countries = children; + [countriesController.tableView reloadData]; + + [[self navigationController] pushViewController:countriesController animated:YES]; + + return; + } + + +// NSArray *selectedMonth = [self.daysByMonth objectAtIndex:section]; Day *selectedDay = [selectedMonth objectAtIndex:row]; NSArray *children = [selectedDay children]; diff --git a/Classes/ReportManager.m b/Classes/ReportManager.m index 3be0bb56..089a4982 100644 --- a/Classes/ReportManager.m +++ b/Classes/ReportManager.m @@ -520,7 +520,11 @@ - (void) successfullyDownloadedReport:(Day*)report { AppManager *manager = [AppManager sharedManager]; for (Country *c in [report.countries allValues]) { for (Entry *e in c.entries) { - if (e.transactionType==2) { continue; } //skips IAPs in app manager, so IAPs don't duplicate reviews + if (e.transactionType == 2 || e.transactionType == 9) { + //skips IAPs in app manager, so IAPs don't duplicate reviews + [manager removeAppWithID:e.productIdentifier]; + continue; + } [manager createOrUpdateAppIfNeededWithID:e.productIdentifier name:e.productName]; } } @@ -534,6 +538,11 @@ - (void)importReport:(Day *)report AppManager *manager = [AppManager sharedManager]; for (Country *c in [report.countries allValues]) { for (Entry *e in c.entries) { + if (e.transactionType == 2 || e.transactionType == 9) { + //skips IAPs in app manager, so IAPs don't duplicate reviews + [manager removeAppWithID:e.productIdentifier]; + continue; + } [manager createOrUpdateAppIfNeededWithID:e.productIdentifier name:e.productName]; } } diff --git a/Classes/ReviewManager.m b/Classes/ReviewManager.m index 8c3d04ef..5fca7c2d 100644 --- a/Classes/ReviewManager.m +++ b/Classes/ReviewManager.m @@ -178,7 +178,7 @@ - (void) workerDone { } - (void) checkIfReviewsUpToDate:(ReviewUpdateBundle*)bundle { - NSAssert([NSThread isMainThread], nil); + ASSERT_IS_MAIN_THREAD(); App *app = [[AppManager sharedManager] appWithID:bundle.appID]; NSDictionary *existingReviews = app.reviewsByUser; @@ -208,7 +208,7 @@ - (void) checkIfReviewsUpToDate:(ReviewUpdateBundle*)bundle { // called after translating new or updated reviews - (void) addReviews:(ReviewUpdateBundle*)bundle { - NSAssert([NSThread isMainThread], nil); + ASSERT_IS_MAIN_THREAD(); App *app = [[AppManager sharedManager] appWithID:bundle.appID]; for (Review *fetchedReivew in bundle.needsUpdating) { [app addOrReplaceReview:fetchedReivew]; @@ -217,7 +217,7 @@ - (void) addReviews:(ReviewUpdateBundle*)bundle { } - (void) workerThreadFetch { // called by worker threads - NSAssert(! [NSThread isMainThread], nil); + ASSERT_NOT_MAIN_THREAD(); NSAutoreleasePool *outerPool = [NSAutoreleasePool new]; @try { NSMutableURLRequest *request = [[NSMutableURLRequest new] autorelease]; @@ -241,7 +241,7 @@ - (void) workerThreadFetch { // called by worker threads NSString *storeFrontID = storeInfo.storeFrontID; NSString *storeFront = [storeFrontID stringByAppendingString:@"-1"]; - [headers setObject:@"iTunes/4.2 (Macintosh; U; PPC Mac OS X 10.2)" forKey:@"User-Agent"]; + [headers setObject:@"iTunes/9.2.1 (Macintosh; Intel Mac OS X 10.5.8) AppleWebKit/533.16" forKey:@"User-Agent"]; [headers setObject:storeFront forKey:@"X-Apple-Store-Front"]; [request setAllHTTPHeaderFields:headers]; [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]; @@ -305,12 +305,26 @@ - (void) workerThreadFetch { // called by worker threads NSString *date = [dateVersionSplitted objectAtIndex:1]; date = [date stringByTrimmingCharactersInSet:whitespaceCharacterSet]; reviewDate = [dateFormatter dateFromString:date]; + if (reviewDate == nil) { + NSDateFormatter *usDateFormatter = [[[NSDateFormatter alloc] init] autorelease]; + NSLocale *usLocale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en-us"] autorelease]; + [usDateFormatter setLocale:usLocale]; + [usDateFormatter setDateFormat:@"MMM dd, yyyy"]; + reviewDate = [usDateFormatter dateFromString:date]; + } } else if (dateVersionSplitted.count == 3) { NSString *version = [dateVersionSplitted objectAtIndex:1]; reviewVersion = [version stringByTrimmingCharactersInSet:whitespaceCharacterSet]; NSString *date = [dateVersionSplitted objectAtIndex:2]; date = [date stringByTrimmingCharactersInSet:whitespaceCharacterSet]; reviewDate = [dateFormatter dateFromString:date]; + if (reviewDate == nil) { + NSDateFormatter *usDateFormatter = [[[NSDateFormatter alloc] init] autorelease]; + NSLocale *usLocale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en-us"] autorelease]; + [usDateFormatter setLocale:usLocale]; + [usDateFormatter setDateFormat:@"MMM dd, yyyy"]; + reviewDate = [usDateFormatter dateFromString:date]; + } } [scanner scanUpToString:@"" intoString:NULL]; @@ -338,9 +352,7 @@ - (void) workerThreadFetch { // called by worker threads ReviewUpdateBundle *bundle = [[[ReviewUpdateBundle alloc] initWithAppID:appID reviews:input] autorelease]; [self performSelectorOnMainThread:@selector(checkIfReviewsUpToDate:) withObject:bundle waitUntilDone:YES]; if (bundle.needsUpdating.count) { - for (Review *fetchedReview in bundle.needsUpdating) { - [fetchedReview updateTranslations]; - } + [Review updateTranslations:bundle.needsUpdating]; [self performSelectorOnMainThread:@selector(addReviews:) withObject:bundle waitUntilDone:YES]; } } @@ -361,7 +373,7 @@ - (void) workerThreadFetch { // called by worker threads - (void) updateReviews { NSAutoreleasePool *pool = [NSAutoreleasePool new]; - NSAssert(! [NSThread isMainThread], nil); + ASSERT_NOT_MAIN_THREAD(); #if APPSALES_DEBUG NSDate *start = [NSDate date]; #endif @@ -398,7 +410,7 @@ - (void) updateReviews { } - (void) updateReviewDownloadProgress:(NSString*)status { - // NSAssert([NSThread isMainThread], nil); + // ASSERT_IS_MAIN_THREAD(); [status retain]; // must retain first [reviewDownloadStatus release]; reviewDownloadStatus = status; @@ -425,7 +437,7 @@ static NSInteger numStoreReviewsComparator(id arg1, id arg2, void *arg3) { } - (void) downloadReviews { - NSAssert([NSThread isMainThread], nil); + ASSERT_IS_MAIN_THREAD(); if (isDownloadingReviews) { return; } @@ -702,7 +714,7 @@ - (void) downloadReviews { } - (void) finishDownloadingReviews { - NSAssert([NSThread isMainThread], nil); + ASSERT_IS_MAIN_THREAD(); isDownloadingReviews = NO; [[AppManager sharedManager] saveToDisk]; diff --git a/Classes/ReviewsController.m b/Classes/ReviewsController.m index 60f59bc9..1c4b1d50 100644 --- a/Classes/ReviewsController.m +++ b/Classes/ReviewsController.m @@ -37,7 +37,7 @@ - (void)viewDidLoad [super viewDidLoad]; self.sortedApps = [AppManager sharedManager].allAppsSorted; - self.tableView.rowHeight = 45; + self.tableView.rowHeight = 60; self.title = NSLocalizedString(@"Reviews",nil); UIBarButtonItem *downloadButton = [[[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Download",nil) style:UIBarButtonItemStyleBordered diff --git a/Classes/RootViewController.m b/Classes/RootViewController.m index 5405073b..98945676 100644 --- a/Classes/RootViewController.m +++ b/Classes/RootViewController.m @@ -48,6 +48,7 @@ #import "ReviewManager.h" #import "ImportExportViewController.h" #import "UIDevice+iPad.h" +#import "AppleFiscalCalendar.h" @implementation RootViewController @@ -113,6 +114,12 @@ - (void)viewWillAppear:(BOOL)animated name:ReportManagerDownloadedWeeklyReportsNotification object:nil]; } +- (void)viewDidAppear:(BOOL)animated +{ + // Pre-initialize Apple calendar here for UI responsiveness; slight, slight gain, but noticeable + [AppleFiscalCalendar sharedFiscalCalendar]; +} + - (void) viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [[NSNotificationCenter defaultCenter] removeObserver:self]; diff --git a/Classes/WeeksController.h b/Classes/WeeksController.h index 9cf84f7f..35477ea8 100644 --- a/Classes/WeeksController.h +++ b/Classes/WeeksController.h @@ -46,7 +46,7 @@ @end -@interface WeeksController : AbstractDayOrWeekController { +@interface WeeksController : AbstractDayOrWeekController { BOOL onlySum; PrevisionReport *previsionReport; } diff --git a/Classes/WeeksController.m b/Classes/WeeksController.m index 3f223629..edd7d2ef 100644 --- a/Classes/WeeksController.m +++ b/Classes/WeeksController.m @@ -38,6 +38,7 @@ #import "ReportManager.h" #import "Country.h" #import "NSDateFormatter+SharedInstances.h" +#import "AppleFiscalCalendar.h" #define BACK_GROUND_COLOR [UIColor colorWithRed:0.92 green:1.0 blue:0.92 alpha:1.0] @@ -82,7 +83,7 @@ @implementation PrevisionWeekCell @synthesize maxRevenue; - (id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier { - if (self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier]) { + if ((self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier])) { UIColor *calendarBackgroundColor = [UIColor colorWithRed:0.84 green:1.0 blue:0.84 alpha:1.0]; UIView *calendarBackgroundView = [[[UIView alloc] initWithFrame:CGRectMake(0,0,45,44)] autorelease]; calendarBackgroundView.backgroundColor = calendarBackgroundColor; @@ -130,9 +131,13 @@ - (void)setPrevisonReport:(PrevisionReport *)report { @end +@interface WeeksController () +@property (nonatomic, assign) DayCalendarType calendarType; +@end + @implementation WeeksController -@synthesize previsionReport; +@synthesize previsionReport, calendarType; - (id)init { @@ -140,10 +145,13 @@ - (id)init if (self) { self.title = NSLocalizedString(@"Weekly Reports",nil); - UIBarButtonItem *button = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Only sum", nil) - style:UIBarButtonItemStyleBordered - target:self - action:@selector(onlySum:)]; +// UIBarButtonItem *button = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Only sum", nil) +// style:UIBarButtonItemStyleBordered +// target:self +// action:@selector(onlySum:)]; +// self.navigationItem.rightBarButtonItem = button; +// [button release]; + UIBarButtonItem *button = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(showActions:)]; self.navigationItem.rightBarButtonItem = button; [button release]; } @@ -156,6 +164,34 @@ - (void)onlySum:(id)sender { [self.tableView reloadData]; } +- (void)showActions:(id)sender { + NSString *otherCalendar = self.calendarType == DayCalendarTypeCalendar ? @"Use Fiscal Calendar" : @"Use Monthly Calendar"; + UIActionSheet *actions = [[UIActionSheet alloc] initWithTitle:@"" + delegate:self + cancelButtonTitle:@"Cancel" + destructiveButtonTitle:nil + otherButtonTitles:@"Show Only Sums", otherCalendar, nil]; + [actions showInView:self.view]; + [actions release]; +} + +- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex { + if (buttonIndex == actionSheet.cancelButtonIndex) { + return; + } + if (buttonIndex == actionSheet.firstOtherButtonIndex) { + [self onlySum:actionSheet]; + return; + } + if (self.calendarType == DayCalendarTypeCalendar) { + [[NSUserDefaults standardUserDefaults] setInteger:DayCalendarTypeAppleFiscal forKey:@"DayCalendarType"]; + } else { + [[NSUserDefaults standardUserDefaults] setInteger:DayCalendarTypeCalendar forKey:@"DayCalendarType"]; + } + [[NSUserDefaults standardUserDefaults] synchronize]; + [self reload]; +} + - (NSIndexPath*) adjustPathForCurrentView:(NSIndexPath*)path { if(!onlySum && previsionReport){ if(previsionReport.newMonth) @@ -169,6 +205,7 @@ - (NSIndexPath*) adjustPathForCurrentView:(NSIndexPath*)path { - (void)reload { self.daysByMonth = [NSMutableArray array]; + self.calendarType = [[NSUserDefaults standardUserDefaults] integerForKey:@"DayCalendarType"]; NSSortDescriptor *dateSorter = [[[NSSortDescriptor alloc] initWithKey:@"date" ascending:NO] autorelease]; NSArray *sortedWeeks = [[[ReportManager sharedManager].weeks allValues] sortedArrayUsingDescriptors:[NSArray arrayWithObject:dateSorter]]; @@ -177,27 +214,40 @@ - (void)reload int numeberOfMonths = 0; float max = 0; NSCalendar *calendar = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease]; + AppleFiscalCalendar *appleCalendar = [AppleFiscalCalendar sharedFiscalCalendar]; + NSString *lastMonthString = nil; + for (Day *d in sortedWeeks) { float revenue = [d totalRevenueInBaseCurrency]; if (revenue > max) max = revenue; + NSDate *date = d.date; - NSDateComponents *components = [calendar components:NSMonthCalendarUnit fromDate:date]; - int month = [components month]; - if (month != lastMonth) { - if (lastMonth == -1) - firstMonth = month; - [daysByMonth addObject:[NSMutableArray array]]; - lastMonth = month; - } - [[daysByMonth lastObject] addObject:d]; + if (self.calendarType == DayCalendarTypeCalendar) { + NSDateComponents *components = [calendar components:NSMonthCalendarUnit fromDate:date]; + int month = [components month]; + if (month != lastMonth) { + if (lastMonth == -1) + firstMonth = month; + [self.daysByMonth addObject:[NSMutableArray array]]; + lastMonth = month; + } + } else { + NSString *monthString = [appleCalendar fiscalMonthForDate:date]; + if (monthString && [monthString compare:lastMonthString] != NSOrderedSame) { + [self.daysByMonth addObject:[NSMutableArray array]]; + lastMonthString = monthString; + } + } + [[self.daysByMonth lastObject] addObject:d]; + numeberOfMonths++; } - + //Prevision self.previsionReport = nil; if(numeberOfMonths > 0){ - NSDate *firstDayLastWeek = ((Day *)[[daysByMonth objectAtIndex:0] objectAtIndex:0]).date; + NSDate *firstDayLastWeek = ((Day *)[[self.daysByMonth objectAtIndex:0] objectAtIndex:0]).date; NSArray *sortedDays = [[[ReportManager sharedManager].days allValues] sortedArrayUsingDescriptors:[NSArray arrayWithObject:dateSorter]]; if(sortedDays.count > 0 && [((Day *)[sortedDays objectAtIndex:0]).date timeIntervalSinceDate:firstDayLastWeek] >= 691200){ //8 days //days of the current week @@ -242,7 +292,7 @@ - (void)reload revenueNewWeek += [[newWeekDays objectAtIndex:i] totalRevenueInBaseCurrency]; revenueLastWeek += [[lastWeekDays objectAtIndex:i] totalRevenueInBaseCurrency]; } - float revenue = [[[daysByMonth objectAtIndex:0] objectAtIndex:0] totalRevenueInBaseCurrency] * revenueNewWeek / revenueLastWeek; + float revenue = [[[self.daysByMonth objectAtIndex:0] objectAtIndex:0] totalRevenueInBaseCurrency] * revenueNewWeek / revenueLastWeek; if(revenue > max) max = revenue; self.previsionReport = [[PrevisionReport new] autorelease]; @@ -357,8 +407,13 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N } if(!onlySum){ - Day *firstDayInSection = [[self.daysByMonth objectAtIndex:section] objectAtIndex:0]; - cell.textLabel.text = [NSString stringWithFormat:@"%@:", [self.sectionTitleFormatter stringFromDate:firstDayInSection.date]]; + Day *firstDayInSection = [[self.daysByMonth objectAtIndex:indexPath.section] objectAtIndex:0]; + if (self.calendarType == DayCalendarTypeCalendar) { + cell.textLabel.text = [NSString stringWithFormat:@"%@:", [self.sectionTitleFormatter stringFromDate:firstDayInSection.date]]; + } else { + AppleFiscalCalendar *calendar = [AppleFiscalCalendar sharedFiscalCalendar]; + cell.textLabel.text = [calendar fiscalMonthForDate:firstDayInSection.date]; + } }else cell.textLabel.text = NSLocalizedString(@"Subtotal:", nil); @@ -452,7 +507,13 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath countriesController.totalRevenue = total; Day *firstDayInSection = [[self.daysByMonth objectAtIndex:section] objectAtIndex:0]; - countriesController.title = [self.sectionTitleFormatter stringFromDate:firstDayInSection.date]; + if (self.calendarType == DayCalendarTypeCalendar) { + countriesController.title = [self.sectionTitleFormatter stringFromDate:firstDayInSection.date]; + } else { + AppleFiscalCalendar *calendar = [AppleFiscalCalendar sharedFiscalCalendar]; + countriesController.title = [calendar fiscalMonthForDate:firstDayInSection.date]; + } + countriesController.countries = children; [countriesController.tableView reloadData]; @@ -546,12 +607,17 @@ - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInte if (self.daysByMonth.count == 0) return @""; - NSArray *sectionArray = [daysByMonth objectAtIndex:section]; + NSArray *sectionArray = [self.daysByMonth objectAtIndex:section]; if (sectionArray.count == 0) return @""; Day *firstDayInSection = [sectionArray objectAtIndex:0]; - return [self.sectionTitleFormatter stringFromDate:firstDayInSection.date]; + if (self.calendarType == DayCalendarTypeCalendar) { + return [self.sectionTitleFormatter stringFromDate:firstDayInSection.date]; + } else { + AppleFiscalCalendar *calendar = [AppleFiscalCalendar sharedFiscalCalendar]; + return [calendar fiscalMonthForDate:firstDayInSection.date]; + } } - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath