diff --git a/Crashlytics.framework/Crashlytics b/Crashlytics.framework/Crashlytics deleted file mode 120000 index 7074275f..00000000 --- a/Crashlytics.framework/Crashlytics +++ /dev/null @@ -1 +0,0 @@ -Versions/Current/Crashlytics \ No newline at end of file diff --git a/Crashlytics.framework/Crashlytics b/Crashlytics.framework/Crashlytics new file mode 100755 index 00000000..e723ee62 Binary files /dev/null and b/Crashlytics.framework/Crashlytics differ diff --git a/Crashlytics.framework/Headers b/Crashlytics.framework/Headers deleted file mode 120000 index a177d2a6..00000000 --- a/Crashlytics.framework/Headers +++ /dev/null @@ -1 +0,0 @@ -Versions/Current/Headers \ No newline at end of file diff --git a/Crashlytics.framework/Headers/CLSLogging.h b/Crashlytics.framework/Headers/CLSLogging.h new file mode 100644 index 00000000..fe1ece6e --- /dev/null +++ b/Crashlytics.framework/Headers/CLSLogging.h @@ -0,0 +1,62 @@ +// +// CLSLogging.h +// Crashlytics +// +// Copyright (c) 2015 Crashlytics, Inc. All rights reserved. +// + +#import +#ifdef __OBJC__ +#import +#endif + +FAB_START_NONNULL + +/** + * + * The CLS_LOG macro provides as easy way to gather more information in your log messages that are + * sent with your crash data. CLS_LOG prepends your custom log message with the function name and + * line number where the macro was used. If your app was built with the DEBUG preprocessor macro + * defined CLS_LOG uses the CLSNSLog function which forwards your log message to NSLog and CLSLog. + * If the DEBUG preprocessor macro is not defined CLS_LOG uses CLSLog only. + * + * Example output: + * -[AppDelegate login:] line 134 $ login start + * + * If you would like to change this macro, create a new header file, unset our define and then define + * your own version. Make sure this new header file is imported after the Crashlytics header file. + * + * #undef CLS_LOG + * #define CLS_LOG(__FORMAT__, ...) CLSNSLog... + * + **/ +#ifdef __OBJC__ +#ifdef DEBUG +#define CLS_LOG(__FORMAT__, ...) CLSNSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) +#else +#define CLS_LOG(__FORMAT__, ...) CLSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) +#endif +#endif + +/** + * + * Add logging that will be sent with your crash data. This logging will not show up in the system.log + * and will only be visible in your Crashlytics dashboard. + * + **/ + +#ifdef __OBJC__ +OBJC_EXTERN void CLSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2); +OBJC_EXTERN void CLSLogv(NSString *format, va_list ap) NS_FORMAT_FUNCTION(1,0); + +/** + * + * Add logging that will be sent with your crash data. This logging will show up in the system.log + * and your Crashlytics dashboard. It is not recommended for Release builds. + * + **/ +OBJC_EXTERN void CLSNSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2); +OBJC_EXTERN void CLSNSLogv(NSString *format, va_list ap) NS_FORMAT_FUNCTION(1,0); +#endif + +FAB_END_NONNULL diff --git a/Crashlytics.framework/Headers/CLSReport.h b/Crashlytics.framework/Headers/CLSReport.h new file mode 100644 index 00000000..bae34e6c --- /dev/null +++ b/Crashlytics.framework/Headers/CLSReport.h @@ -0,0 +1,103 @@ +// +// CLSReport.h +// Crashlytics +// +// Copyright (c) 2015 Crashlytics, Inc. All rights reserved. +// + +#import +#import + +FAB_START_NONNULL + +/** + * The CLSCrashReport protocol is deprecated. See the CLSReport class and the CrashyticsDelegate changes for details. + **/ +@protocol CLSCrashReport + +@property (nonatomic, copy, readonly) NSString *identifier; +@property (nonatomic, copy, readonly) NSDictionary *customKeys; +@property (nonatomic, copy, readonly) NSString *bundleVersion; +@property (nonatomic, copy, readonly) NSString *bundleShortVersionString; +@property (nonatomic, copy, readonly) NSDate *crashedOnDate; +@property (nonatomic, copy, readonly) NSString *OSVersion; +@property (nonatomic, copy, readonly) NSString *OSBuildVersion; + +@end + +/** + * The CLSReport exposes an interface to the phsyical report that Crashlytics has created. You can + * use this class to get information about the event, and can also set some values after the + * event has occured. + **/ +@interface CLSReport : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +/** + * Returns the session identifier for the report. + **/ +@property (nonatomic, copy, readonly) NSString *identifier; + +/** + * Returns the custom key value data for the report. + **/ +@property (nonatomic, copy, readonly) NSDictionary *customKeys; + +/** + * Returns the CFBundleVersion of the application that generated the report. + **/ +@property (nonatomic, copy, readonly) NSString *bundleVersion; + +/** + * Returns the CFBundleShortVersionString of the application that generated the report. + **/ +@property (nonatomic, copy, readonly) NSString *bundleShortVersionString; + +/** + * Returns the date that the report was created. + **/ +@property (nonatomic, copy, readonly) NSDate *dateCreated; + +/** + * Returns the os version that the application crashed on. + **/ +@property (nonatomic, copy, readonly) NSString *OSVersion; + +/** + * Returns the os build version that the application crashed on. + **/ +@property (nonatomic, copy, readonly) NSString *OSBuildVersion; + +/** + * Returns YES if the report contains any crash information. If the report + * contains only NSErrors, this will return NO. + **/ +@property (nonatomic, assign, readonly) BOOL isCrash; + +/** + * You can use this method to set, after the event, additional custom keys. The rules + * and semantics for this method are the same as those documented in Crashlytics.h. Be aware + * that the maximum size and count of custom keys is still enforced, and you can overwrite keys + * and/or cause excess keys to be deleted by using this method. + **/ +- (void)setObjectValue:(id FAB_NULLABLE)value forKey:(NSString *)key; + +/** + * Record an application-specific user identifier. See Crashlytics.h for details. + **/ +@property (nonatomic, copy) NSString * FAB_NULLABLE userIdentifier; + +/** + * Record a user name. See Crashlytics.h for details. + **/ +@property (nonatomic, copy) NSString * FAB_NULLABLE userName; + +/** + * Record a user email. See Crashlytics.h for details. + **/ +@property (nonatomic, copy) NSString * FAB_NULLABLE userEmail; + +@end + +FAB_END_NONNULL diff --git a/Crashlytics.framework/Headers/CLSStackFrame.h b/Crashlytics.framework/Headers/CLSStackFrame.h new file mode 100644 index 00000000..37447912 --- /dev/null +++ b/Crashlytics.framework/Headers/CLSStackFrame.h @@ -0,0 +1,37 @@ +// +// CLSStackFrame.h +// Crashlytics +// +// Copyright 2015 Crashlytics, Inc. All rights reserved. +// + +#import +#import + +FAB_START_NONNULL + +/** + * + * This class is used in conjunction with -[Crashlytics recordCustomExceptionName:reason:frameArray:] to + * record information about non-ObjC/C++ exceptions. All information included here will be displayed + * in the Crashlytics UI, and can influence crash grouping. Be particularly careful with the use of the + * address property. If set, Crashlytics will attempt symbolication and could overwrite other properities + * in the process. + * + **/ +@interface CLSStackFrame : NSObject + ++ (instancetype)stackFrame; ++ (instancetype)stackFrameWithAddress:(NSUInteger)address; ++ (instancetype)stackFrameWithSymbol:(NSString *)symbol; + +@property (nonatomic, copy) NSString * FAB_NULLABLE symbol; +@property (nonatomic, copy) NSString * FAB_NULLABLE library; +@property (nonatomic, copy) NSString * FAB_NULLABLE fileName; +@property (nonatomic, assign) uint32_t lineNumber; +@property (nonatomic, assign) uint64_t offset; +@property (nonatomic, assign) uint64_t address; + +@end + +FAB_END_NONNULL diff --git a/Crashlytics.framework/Headers/Crashlytics.h b/Crashlytics.framework/Headers/Crashlytics.h new file mode 100644 index 00000000..07ab722b --- /dev/null +++ b/Crashlytics.framework/Headers/Crashlytics.h @@ -0,0 +1,246 @@ +// +// Crashlytics.h +// Crashlytics +// +// Copyright (c) 2015 Crashlytics, Inc. All rights reserved. +// + +#import + +#import +#import "CLSLogging.h" +#import "CLSReport.h" +#import "CLSStackFrame.h" + +#define CLS_DEPRECATED(x) __attribute__ ((deprecated(x))) + +FAB_START_NONNULL + +@protocol CrashlyticsDelegate; + +@interface Crashlytics : NSObject + +@property (nonatomic, readonly, copy) NSString *apiKey; +@property (nonatomic, readonly, copy) NSString *version; +@property (nonatomic, assign) BOOL debugMode; + +/** + * + * The delegate can be used to influence decisions on reporting and behavior, as well as reacting + * to previous crashes. + * + * Make certain that the delegate is setup before starting Crashlytics with startWithAPIKey:... or + * via +[Fabric with:...]. Failure to do will result in missing any delegate callbacks that occur + * synchronously during start. + * + **/ +@property (nonatomic, assign) id FAB_NULLABLE delegate; + +/** + * + * The recommended way to install Crashlytics into your application is to place a call + * to +startWithAPIKey: in your -application:didFinishLaunchingWithOptions:/-applicationDidFinishLaunching: + * method. + * + * Note: Starting with 3.0, the submission process has been significantly improved. The delay parameter + * is no longer required to throttle submissions on launch, performance will be great without it. + * + **/ ++ (Crashlytics *)startWithAPIKey:(NSString *)apiKey; ++ (Crashlytics *)startWithAPIKey:(NSString *)apiKey afterDelay:(NSTimeInterval)delay CLS_DEPRECATED("Crashlytics no longer needs or uses the delay parameter. Please use +startWithAPIKey: instead."); + +/** + * + * If you need the functionality provided by the CrashlyticsDelegate protocol, you can use + * these convenience methods to activate the framework and set the delegate in one call. + * + **/ ++ (Crashlytics *)startWithAPIKey:(NSString *)apiKey delegate:(id FAB_NULLABLE)delegate; ++ (Crashlytics *)startWithAPIKey:(NSString *)apiKey delegate:(id FAB_NULLABLE)delegate afterDelay:(NSTimeInterval)delay CLS_DEPRECATED("Crashlytics no longer needs or uses the delay parameter. Please use +startWithAPIKey:delegate: instead."); + +/** + * + * Access the singleton Crashlytics instance. + * + **/ ++ (Crashlytics *)sharedInstance; + +/** + * + * The easiest ways to cause a crash - great for testing! + * + **/ +- (void)crash; +- (void)throwException; + +/** + * + * Many of our customers have requested the ability to tie crashes to specific end-users of their + * application in order to facilitate responses to support requests or permit the ability to reach + * out for more information. We allow you to specify up to three separate values for display within + * the Crashlytics UI - but please be mindful of your end-user's privacy. + * + * We recommend specifying a user identifier - an arbitrary string that ties an end-user to a record + * in your system. This could be a database id, hash, or other value that is meaningless to a + * third-party observer but can be indexed and queried by you. + * + * Optionally, you may also specify the end-user's name or username, as well as email address if you + * do not have a system that works well with obscured identifiers. + * + * Pursuant to our EULA, this data is transferred securely throughout our system and we will not + * disseminate end-user data unless required to by law. That said, if you choose to provide end-user + * contact information, we strongly recommend that you disclose this in your application's privacy + * policy. Data privacy is of our utmost concern. + * + **/ +- (void)setUserIdentifier:(NSString * FAB_NULLABLE)identifier; +- (void)setUserName:(NSString * FAB_NULLABLE)name; +- (void)setUserEmail:(NSString * FAB_NULLABLE)email; + ++ (void)setUserIdentifier:(NSString * FAB_NULLABLE)identifier CLS_DEPRECATED("Please access this method via +sharedInstance"); ++ (void)setUserName:(NSString * FAB_NULLABLE)name CLS_DEPRECATED("Please access this method via +sharedInstance"); ++ (void)setUserEmail:(NSString * FAB_NULLABLE)email CLS_DEPRECATED("Please access this method via +sharedInstance"); + +/** + * + * Set a value for a key to be associated with your crash data. When setting an object value, the object + * is converted to a string. This is typically done by calling -[NSObject description]. + * + **/ +- (void)setObjectValue:(id FAB_NULLABLE)value forKey:(NSString *)key; +- (void)setIntValue:(int)value forKey:(NSString *)key; +- (void)setBoolValue:(BOOL)value forKey:(NSString *)key; +- (void)setFloatValue:(float)value forKey:(NSString *)key; + ++ (void)setObjectValue:(id FAB_NULLABLE)value forKey:(NSString *)key CLS_DEPRECATED("Please access this method via +sharedInstance"); ++ (void)setIntValue:(int)value forKey:(NSString *)key CLS_DEPRECATED("Please access this method via +sharedInstance"); ++ (void)setBoolValue:(BOOL)value forKey:(NSString *)key CLS_DEPRECATED("Please access this method via +sharedInstance"); ++ (void)setFloatValue:(float)value forKey:(NSString *)key CLS_DEPRECATED("Please access this method via +sharedInstance"); + +/** + * + * This method can be used to record a single exception structure in a report. This is particularly useful + * when your code interacts with non-native languages like Lua, C#, or Javascript. This call can be + # expensive and should only be used shortly before process termination. This API is not intended be to used + * to log NSException objects. All safely-reportable NSExceptions are automatically captured by + * Crashlytics. + * + * The frameArray argument should contain only CLSStackFrame instances. + * + **/ +- (void)recordCustomExceptionName:(NSString *)name reason:(NSString * FAB_NULLABLE)reason frameArray:(NSArray *)frameArray; + + + +/** + * In Beta. Sign up at http://answers.io/labs to get on the list! + * + * @brief Log an event to be sent to Answers. + * @param eventName The event name as it will be shown in the dashboard. + * @discussion Example usage: + * @code [CrashlyticsKit logEvent:@"Tweet Viewed"]; + * + */ +- (void)logEvent:(NSString *)eventName; + +/** + * In Beta. Sign up at http://answers.io/labs to get on the list! + * + * @brief Log an event to be sent to Answers, optionally providing a dictionary of attributes. Attribute keys + * must be NSString and and values must be NSNumber or NSString. + * @param eventName The event name as it will be shown in the dashboard. + * @param attributes An NSDictionary with keys of type NSString, and values of type NSNumber + * or NSString. There may be at most 20 attributes for a particular event. + * @discussion How we treat NSNumber: + * We will provide information about the distribution of values over time. + * + * How we treat NSStrings: + * NSStrings are used as categorical data, allowing comparison across different category values. + * Strings are limited to a maximum length of 100 characters, attributes over this length will be + * truncated. + * + * When tracking the Tweet views to better understand user engagement, sending the tweet's length + * and the type of media present in the tweet allows you to track how tweet length and the type of media influence + * engagement. + * Example usage: + * @code [CrashlyticsKit logEvent:@"Tweet Viewed" attributes:@{ + * @"Media Type": @"Image", + * @"Length": @120 + * }]; + */ +- (void)logEvent:(NSString *)eventName attributes:(NSDictionary * FAB_NULLABLE) attributes; + ++ (void)logEvent:(NSString *)eventName CLS_DEPRECATED("Please refer to -logEvent:"); ++ (void)logEvent:(NSString *)eventName attributes:(NSDictionary * FAB_NULLABLE) attributes CLS_DEPRECATED("Please refer to -logEvent:attributes:"); +@end + +/** + * + * The CrashlyticsDelegate protocol provides a mechanism for your application to take + * action on events that occur in the Crashlytics crash reporting system. You can make + * use of these calls by assigning an object to the Crashlytics' delegate property directly, + * or through the convenience +startWithAPIKey:delegate: method. + * + **/ +@protocol CrashlyticsDelegate +@optional + +/** + * + * Called once a Crashlytics instance has determined that the last execution of the + * application ended in a crash. This is called some time after the crash reporting + * process has begun. If you have specified a delay in one of the + * startWithAPIKey:... calls, this will take at least that long to be invoked. + * + **/ +- (void)crashlyticsDidDetectCrashDuringPreviousExecution:(Crashlytics *)crashlytics CLS_DEPRECATED("Please refer to -crashlyticsDidDetectReportForLastExecution:"); + +/** + * + * Just like crashlyticsDidDetectCrashDuringPreviousExecution this delegate method is + * called once a Crashlytics instance has determined that the last execution of the + * application ended in a crash. A CLSCrashReport is passed back that contains data about + * the last crash report that was generated. See the CLSCrashReport protocol for method details. + * This method is called after crashlyticsDidDetectCrashDuringPreviousExecution. + * + **/ +- (void)crashlytics:(Crashlytics *)crashlytics didDetectCrashDuringPreviousExecution:(id )crash CLS_DEPRECATED("Please refer to -crashlyticsDidDetectReportForLastExecution:"); + +/** + * + * Called when a Crashlytics instance has determined that the last execution of the + * application ended in a crash. This is called synchronously on Crashlytics + * initialization. Your delegate must invoke the completionHandler, but does not need to do so + * synchronously, or even on the main thread. Invoking completionHandler with NO will cause the + * detected report to be deleted and not submitted to Crashlytics. This is useful for + * implementing permission prompts, or other more-complex forms of logic around submitting crashes. + * + * Failure to invoke the completionHandler will prevent submissions from being reported. Watch out. + * + * Just implementing this delegate method will disable all forms of synchronous report submission. This can + * impact the reliability of reporting crashes very early in application launch. + * + **/ + +- (void)crashlyticsDidDetectReportForLastExecution:(CLSReport *)report completionHandler:(void (^)(BOOL submit))completionHandler; + +/** + * + * If your app is running on an OS that supports it (OS X 10.9+, iOS 7.0+), Crashlytics will submit + * most reports using out-of-process background networking operations. This results in a significant + * improvement in reliability of reporting, as well as power and performance wins for your users. + * If you don't want this functionality, you can disable by returning NO from this method. + * + * Note: background submission is not supported for extensions on iOS or OS X. + * + **/ +- (BOOL)crashlyticsCanUseBackgroundSessions:(Crashlytics *)crashlytics; + +@end + +/** + * `CrashlyticsKit` can be used as a parameter to `[Fabric with:@[CrashlyticsKit]];` in Objective-C. In Swift, use Crashlytics.sharedInstance() + */ +#define CrashlyticsKit [Crashlytics sharedInstance] + +FAB_END_NONNULL diff --git a/Crashlytics.framework/Info.plist b/Crashlytics.framework/Info.plist new file mode 100644 index 00000000..b8168d93 --- /dev/null +++ b/Crashlytics.framework/Info.plist @@ -0,0 +1,55 @@ + + + + + BuildMachineOSBuild + 13F34 + CFBundleDevelopmentRegion + English + CFBundleExecutable + Crashlytics + CFBundleIdentifier + com.twitter.crashlytics.ios + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Crashlytics + CFBundlePackageType + FMWK + CFBundleShortVersionString + 3.0.8 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + iPhoneOS + + CFBundleVersion + 50 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 12B411 + DTPlatformName + iphoneos + DTPlatformVersion + 8.1 + DTSDKBuild + 12B411 + DTSDKName + iphoneos8.1 + DTXcode + 0611 + DTXcodeBuild + 6A2008a + MinimumOSVersion + 5.0 + NSHumanReadableCopyright + Copyright © 2015 Crashlytics, Inc. All rights reserved. + UIDeviceFamily + + 1 + 2 + + + diff --git a/Crashlytics.framework/Modules/module.modulemap b/Crashlytics.framework/Modules/module.modulemap index e552e9ca..7c8ea5c0 100644 --- a/Crashlytics.framework/Modules/module.modulemap +++ b/Crashlytics.framework/Modules/module.modulemap @@ -1,6 +1,11 @@ framework module Crashlytics { - umbrella header "Crashlytics.h" + header "Crashlytics.h" + header "CLSLogging.h" + header "CLSReport.h" + header "CLSStackFrame.h" - export * - module * { export * } + export * + + link "z" + link "c++" } diff --git a/Crashlytics.framework/Resources b/Crashlytics.framework/Resources deleted file mode 120000 index 953ee36f..00000000 --- a/Crashlytics.framework/Resources +++ /dev/null @@ -1 +0,0 @@ -Versions/Current/Resources \ No newline at end of file diff --git a/Crashlytics.framework/Versions/A/Crashlytics b/Crashlytics.framework/Versions/A/Crashlytics deleted file mode 100644 index 42d9da7e..00000000 Binary files a/Crashlytics.framework/Versions/A/Crashlytics and /dev/null differ diff --git a/Crashlytics.framework/Versions/A/Headers/Crashlytics.h b/Crashlytics.framework/Versions/A/Headers/Crashlytics.h deleted file mode 100644 index 9683173c..00000000 --- a/Crashlytics.framework/Versions/A/Headers/Crashlytics.h +++ /dev/null @@ -1,225 +0,0 @@ -// -// Crashlytics.h -// Crashlytics -// -// Copyright 2013 Crashlytics, Inc. All rights reserved. -// - -#import - -/** - * - * The CLS_LOG macro provides as easy way to gather more information in your log messages that are - * sent with your crash data. CLS_LOG prepends your custom log message with the function name and - * line number where the macro was used. If your app was built with the DEBUG preprocessor macro - * defined CLS_LOG uses the CLSNSLog function which forwards your log message to NSLog and CLSLog. - * If the DEBUG preprocessor macro is not defined CLS_LOG uses CLSLog only. - * - * Example output: - * -[AppDelegate login:] line 134 $ login start - * - * If you would like to change this macro, create a new header file, unset our define and then define - * your own version. Make sure this new header file is imported after the Crashlytics header file. - * - * #undef CLS_LOG - * #define CLS_LOG(__FORMAT__, ...) CLSNSLog... - * - **/ -#ifdef DEBUG -#define CLS_LOG(__FORMAT__, ...) CLSNSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) -#else -#define CLS_LOG(__FORMAT__, ...) CLSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) -#endif - -/** - * - * Add logging that will be sent with your crash data. This logging will not show up in the system.log - * and will only be visible in your Crashlytics dashboard. - * - **/ -OBJC_EXTERN void CLSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2); -OBJC_EXTERN void CLSLogv(NSString *format, va_list args) NS_FORMAT_FUNCTION(1,0); - -/** - * - * Add logging that will be sent with your crash data. This logging will show up in the system.log - * and your Crashlytics dashboard. It is not recommended for Release builds. - * - **/ -OBJC_EXTERN void CLSNSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2); -OBJC_EXTERN void CLSNSLogv(NSString *format, va_list args) NS_FORMAT_FUNCTION(1,0); - - -@protocol CrashlyticsDelegate; - -@interface Crashlytics : NSObject - -@property (nonatomic, readonly, copy) NSString *apiKey; -@property (nonatomic, readonly, copy) NSString *version; -@property (nonatomic, assign) BOOL debugMode; - -@property (nonatomic, assign) NSObject *delegate; - -/** - * - * The recommended way to install Crashlytics into your application is to place a call - * to +startWithAPIKey: in your -application:didFinishLaunchingWithOptions: method. - * - * This delay defaults to 1 second in order to generally give the application time to - * fully finish launching. - * - **/ -+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey; -+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey afterDelay:(NSTimeInterval)delay; - -/** - * - * If you need the functionality provided by the CrashlyticsDelegate protocol, you can use - * these convenience methods to activate the framework and set the delegate in one call. - * - **/ -+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey delegate:(NSObject *)delegate; -+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey delegate:(NSObject *)delegate afterDelay:(NSTimeInterval)delay; - -/** - * - * Access the singleton Crashlytics instance. - * - **/ -+ (Crashlytics *)sharedInstance; - -/** - * - * The easiest way to cause a crash - great for testing! - * - **/ -- (void)crash; - -/** - * - * Many of our customers have requested the ability to tie crashes to specific end-users of their - * application in order to facilitate responses to support requests or permit the ability to reach - * out for more information. We allow you to specify up to three separate values for display within - * the Crashlytics UI - but please be mindful of your end-user's privacy. - * - * We recommend specifying a user identifier - an arbitrary string that ties an end-user to a record - * in your system. This could be a database id, hash, or other value that is meaningless to a - * third-party observer but can be indexed and queried by you. - * - * Optionally, you may also specify the end-user's name or username, as well as email address if you - * do not have a system that works well with obscured identifiers. - * - * Pursuant to our EULA, this data is transferred securely throughout our system and we will not - * disseminate end-user data unless required to by law. That said, if you choose to provide end-user - * contact information, we strongly recommend that you disclose this in your application's privacy - * policy. Data privacy is of our utmost concern. - * - **/ -- (void)setUserIdentifier:(NSString *)identifier; -- (void)setUserName:(NSString *)name; -- (void)setUserEmail:(NSString *)email; - -+ (void)setUserIdentifier:(NSString *)identifier; -+ (void)setUserName:(NSString *)name; -+ (void)setUserEmail:(NSString *)email; - -/** - * - * Set a value for a key to be associated with your crash data. - * - **/ -- (void)setObjectValue:(id)value forKey:(NSString *)key; -- (void)setIntValue:(int)value forKey:(NSString *)key; -- (void)setBoolValue:(BOOL)value forKey:(NSString *)key; -- (void)setFloatValue:(float)value forKey:(NSString *)key; - -+ (void)setObjectValue:(id)value forKey:(NSString *)key; -+ (void)setIntValue:(int)value forKey:(NSString *)key; -+ (void)setBoolValue:(BOOL)value forKey:(NSString *)key; -+ (void)setFloatValue:(float)value forKey:(NSString *)key; - -@end - -/** - * The CLSCrashReport protocol exposes methods that you can call on crash report objects passed - * to delegate methods. If you want these values or the entire object to stay in memory retain - * them or copy them. - **/ -@protocol CLSCrashReport -@required - -/** - * Returns the session identifier for the crash report. - **/ -@property (nonatomic, readonly) NSString *identifier; - -/** - * Returns the custom key value data for the crash report. - **/ -@property (nonatomic, readonly) NSDictionary *customKeys; - -/** - * Returns the CFBundleVersion of the application that crashed. - **/ -@property (nonatomic, readonly) NSString *bundleVersion; - -/** - * Returns the CFBundleShortVersionString of the application that crashed. - **/ -@property (nonatomic, readonly) NSString *bundleShortVersionString; - -/** - * Returns the date that the application crashed at. - **/ -@property (nonatomic, readonly) NSDate *crashedOnDate; - -/** - * Returns the os version that the application crashed on. - **/ -@property (nonatomic, readonly) NSString *OSVersion; - -/** - * Returns the os build version that the application crashed on. - **/ -@property (nonatomic, readonly) NSString *OSBuildVersion; - -@end - -/** - * - * The CrashlyticsDelegate protocol provides a mechanism for your application to take - * action on events that occur in the Crashlytics crash reporting system. You can make - * use of these calls by assigning an object to the Crashlytics' delegate property directly, - * or through the convenience startWithAPIKey:delegate:... methods. - * - **/ -@protocol CrashlyticsDelegate -@optional - -/** - * - * Called once a Crashlytics instance has determined that the last execution of the - * application ended in a crash. This is called some time after the crash reporting - * process has begun. If you have specified a delay in one of the - * startWithAPIKey:... calls, this will take at least that long to be invoked. - * - **/ -- (void)crashlyticsDidDetectCrashDuringPreviousExecution:(Crashlytics *)crashlytics; - -/** - * - * Just like crashlyticsDidDetectCrashDuringPreviousExecution this delegate method is - * called once a Crashlytics instance has determined that the last execution of the - * application ended in a crash. A CLSCrashReport is passed back that contains data about - * the last crash report that was generated. See the CLSCrashReport protocol for method details. - * This method is called after crashlyticsDidDetectCrashDuringPreviousExecution. - * - **/ -- (void)crashlytics:(Crashlytics *)crashlytics didDetectCrashDuringPreviousExecution:(id )crash; - -@end - -/** - * `CrashlyticsKit` can be used as a parameter to `[Fabric with:@[CrashlyticsKit]];` in Objective-C. In Swift, simply use `Crashlytics()` - */ -#define CrashlyticsKit [Crashlytics sharedInstance] diff --git a/Crashlytics.framework/Versions/A/Resources/Info.plist b/Crashlytics.framework/Versions/A/Resources/Info.plist deleted file mode 100644 index ead462e6..00000000 --- a/Crashlytics.framework/Versions/A/Resources/Info.plist +++ /dev/null @@ -1,30 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - Crashlytics - CFBundleIdentifier - com.crashlytics.ios - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - Crashlytics - CFBundlePackageType - FMWK - CFBundleShortVersionString - 2.2.10 - CFBundleSupportedPlatforms - - iPhoneOS - - CFBundleVersion - 45 - DTPlatformName - iphoneos - MinimumOSVersion - 4.0 - - diff --git a/Crashlytics.framework/Versions/Current b/Crashlytics.framework/Versions/Current deleted file mode 120000 index 8c7e5a66..00000000 --- a/Crashlytics.framework/Versions/Current +++ /dev/null @@ -1 +0,0 @@ -A \ No newline at end of file diff --git a/Crashlytics.framework/run b/Crashlytics.framework/run index c79b0288..c4406565 100755 Binary files a/Crashlytics.framework/run and b/Crashlytics.framework/run differ diff --git a/Crashlytics.framework/submit b/Crashlytics.framework/submit index 65568e31..1a87904d 100755 Binary files a/Crashlytics.framework/submit and b/Crashlytics.framework/submit differ diff --git a/Fabric.framework/Fabric b/Fabric.framework/Fabric new file mode 100755 index 00000000..c4e14672 Binary files /dev/null and b/Fabric.framework/Fabric differ diff --git a/Fabric.framework/Headers/FABAttributes.h b/Fabric.framework/Headers/FABAttributes.h new file mode 100644 index 00000000..7200ea47 --- /dev/null +++ b/Fabric.framework/Headers/FABAttributes.h @@ -0,0 +1,23 @@ +// +// FABAttributes.h +// Fabric +// +// Created by Priyanka Joshi on 3/3/15. +// Copyright (c) 2015 Twitter. All rights reserved. +// + +#pragma once + +#define FAB_UNAVAILABLE(x) __attribute__((unavailable(x))) + +#if __has_feature(nullability) +#define FAB_NONNULL __nonnull +#define FAB_NULLABLE __nullable +#define FAB_START_NONNULL _Pragma("clang assume_nonnull begin") +#define FAB_END_NONNULL _Pragma("clang assume_nonnull end") +#else +#define FAB_NONNULL +#define FAB_NULLABLE +#define FAB_START_NONNULL +#define FAB_END_NONNULL +#endif diff --git a/Fabric.framework/Headers/Fabric.h b/Fabric.framework/Headers/Fabric.h new file mode 100644 index 00000000..7b1b31ab --- /dev/null +++ b/Fabric.framework/Headers/Fabric.h @@ -0,0 +1,75 @@ +// +// Fabric.h +// +// Copyright (c) 2014 Twitter. All rights reserved. +// + +#import +#import "FABAttributes.h" + +FAB_START_NONNULL + +/** + * Fabric Base. Coordinates configuration and starts all provided kits. + */ +@interface Fabric : NSObject + +/** + * Initialize Fabric and all provided kits. Call this method within your App Delegate's + * `application:didFinishLaunchingWithOptions:` and provide the kits you wish to use. + * + * For example, in Objective-C: + * + * `[Fabric with:@[TwitterKit, CrashlyticsKit, MoPubKit]];` + * + * Swift: + * + * `Fabric.with([Twitter(), Crashlytics(), MoPub()])` + * + * Only the first call to this method is honored. Subsequent calls are no-ops. + * + * @param kits An array of kit instances. Kits may provide a macro such as CrashlyticsKit which can be passed in as array elements in objective-c. + * + * @return Returns the shared Fabric instance. In most cases this can be ignored. + */ ++ (instancetype)with:(NSArray *)kits; + +/** + * Returns the Fabric singleton object. + */ ++ (instancetype)sharedSDK; + +/** + * This BOOL enables or disables debug logging, such as kit version information. The default value is NO. + */ +@property (nonatomic, assign) BOOL debug; + +/** + * Unavailable. Use `+sharedSDK` to retrieve the shared Fabric instance. + */ +- (id)init FAB_UNAVAILABLE("Use +sharedSDK to retrieve the shared Fabric instance."); + +/** + * Returns Fabrics's instance of the specified kit. + * + * @param klass The class of the kit. + * + * @return The kit instance of class klass which was provided to with: or nil. + */ +- (id FAB_NULLABLE)kitForClass:(Class)klass; + +/** + * Returns a dictionary containing the kit configuration info for the provided kit. + * The configuration information is parsed from the application's Info.plist. This + * method is primarily intended to be used by kits to retrieve their configuration. + * + * @param kitInstance An instance of the kit whose configuration should be returned. + * + * @return A dictionary containing kit specific configuration information or nil if none exists. + */ +- (NSDictionary * FAB_NULLABLE)configurationDictionaryForKit:(id)kitInstance; + +@end + +FAB_END_NONNULL + diff --git a/Fabric.framework/Info.plist b/Fabric.framework/Info.plist new file mode 100644 index 00000000..36cf6303 --- /dev/null +++ b/Fabric.framework/Info.plist @@ -0,0 +1,55 @@ + + + + + BuildMachineOSBuild + 13F34 + CFBundleDevelopmentRegion + en + CFBundleExecutable + Fabric + CFBundleIdentifier + io.fabric.sdk.ios + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Fabric + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.2.5 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + iPhoneOS + + CFBundleVersion + 16 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 12B411 + DTPlatformName + iphoneos + DTPlatformVersion + 8.1 + DTSDKBuild + 12B411 + DTSDKName + iphoneos8.1 + DTXcode + 0611 + DTXcodeBuild + 6A2008a + MinimumOSVersion + 5.0 + NSHumanReadableCopyright + Copyright © 2015 Twitter. All rights reserved. + UIDeviceFamily + + 1 + 2 + + + diff --git a/Fabric.framework/Modules/module.modulemap b/Fabric.framework/Modules/module.modulemap new file mode 100644 index 00000000..2a312239 --- /dev/null +++ b/Fabric.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module Fabric { + umbrella header "Fabric.h" + + export * + module * { export * } +} \ No newline at end of file diff --git a/Fabric.framework/run b/Fabric.framework/run new file mode 100755 index 00000000..3c479413 Binary files /dev/null and b/Fabric.framework/run differ diff --git a/Podfile b/Podfile index 76242c36..410fcf5c 100644 --- a/Podfile +++ b/Podfile @@ -2,6 +2,7 @@ source 'https://github.com/CocoaPods/Specs.git' platform :ios, '8.0' use_frameworks! +pod 'FXBlurView' pod 'Kingfisher' pod 'WebViewJavascriptBridge' pod 'pop' diff --git a/Podfile.lock b/Podfile.lock index bb697dd0..1e0f5634 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -21,21 +21,23 @@ PODS: - AFNetworking/NSURLConnection - AFNetworking/NSURLSession - Base64 (1.0.1) + - FXBlurView (1.6.3) - Kingfisher (1.4.1) - MZFayeClient (1.0.0): - Base64 (~> 1.0.1) - SocketRocket (~> 0.3.1-beta2) - pop (1.0.7) - - Realm (0.92.3): - - Realm/Headers (= 0.92.3) - - Realm/Headers (0.92.3) - - RealmSwift (0.92.3): - - Realm (= 0.92.3) + - Realm (0.92.4): + - Realm/Headers (= 0.92.4) + - Realm/Headers (0.92.4) + - RealmSwift (0.92.4): + - Realm (= 0.92.4) - SocketRocket (0.3.1-beta2) - WebViewJavascriptBridge (4.1.4) DEPENDENCIES: - AFNetworking + - FXBlurView - Kingfisher - MZFayeClient (from `../CatchLib-iOS/MZFayeClient/`) - pop @@ -49,12 +51,13 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: AFNetworking: 05edc0ac4c4c8cf57bcf4b84be5b0744b6d8e71e Base64: 4924bf3ca6fa559a5161ef717291bd450eb7bd1a + FXBlurView: c6d23f3d35af2c6282296a2930f61c6e2c788d01 Kingfisher: 962a21cdc2a7b0fa63be3b2c45ea6f180feda535 MZFayeClient: 19df756a4c86c8f414668e6edc54fccfee54adbb pop: 628ffc631644601567ee8bfaaaea493ebd7d0923 - Realm: bf604805d5e897850da488260f93889f510924b3 - RealmSwift: a5e48c7d5774eaa299b0b3a843add3b55e7f0061 + Realm: 4267bae2eb7cf4a1e06ef3dfa7fe96024b28074c + RealmSwift: af48a8dea1a60b6e890be94148bece9432487b56 SocketRocket: 7284ab9370a06c99aba92b2fe3a32aedd0f9a6fa WebViewJavascriptBridge: f10ac16f2cd3adf2b941bd79477c55a8f6e01044 -COCOAPODS: 0.37.1 +COCOAPODS: 0.37.2 diff --git a/Yep.xcodeproj/project.pbxproj b/Yep.xcodeproj/project.pbxproj index 41fcf067..293e02fe 100644 --- a/Yep.xcodeproj/project.pbxproj +++ b/Yep.xcodeproj/project.pbxproj @@ -27,8 +27,14 @@ 0A90187F1B01152800AE4B7F /* ProfileSocialAccountCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0A90187D1B01152800AE4B7F /* ProfileSocialAccountCell.xib */; }; 0A9018821B0120A500AE4B7F /* OAuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9018811B0120A500AE4B7F /* OAuthViewController.swift */; }; 0A9018871B01321F00AE4B7F /* WebViewJavascriptBridge.js.txt in Resources */ = {isa = PBXBuildFile; fileRef = 0A9018861B01321F00AE4B7F /* WebViewJavascriptBridge.js.txt */; }; + 0A943A7B1B0F8EAE0022DD67 /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A943A7A1B0F8EAE0022DD67 /* BaseViewController.swift */; }; 0A944C251AC8BB2F00037A06 /* YepStorageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A944C241AC8BB2F00037A06 /* YepStorageService.swift */; }; + 0A98288F1B18D2BD001725B7 /* ChatStateCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A98288D1B18D2BD001725B7 /* ChatStateCell.swift */; }; + 0A9828901B18D2BD001725B7 /* ChatStateCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0A98288E1B18D2BD001725B7 /* ChatStateCell.xib */; }; 0AAFCFF91AC8CA5B00EB0EF8 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AAFCFF81AC8CA5B00EB0EF8 /* MobileCoreServices.framework */; }; + 0AB8F79A1B15D1DC00F4AF09 /* YepNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB8F7991B15D1DC00F4AF09 /* YepNavigationController.swift */; }; + 0AC25F031B1100F0009E6E13 /* YepScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC25F021B1100F0009E6E13 /* YepScrollView.swift */; }; + 0AC25F061B1105D5009E6E13 /* YepChildScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC25F051B1105D5009E6E13 /* YepChildScrollView.swift */; }; 0AD7EA5E1AC3ED1200617758 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AD7EA5D1AC3ED1200617758 /* CFNetwork.framework */; }; 0AD7EA601AC3ED1900617758 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AD7EA5F1AC3ED1900617758 /* CoreFoundation.framework */; }; 0AD7EA621AC3ED2100617758 /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AD7EA611AC3ED2100617758 /* CoreTelephony.framework */; }; @@ -40,9 +46,12 @@ 0AD7EA6E1AC3ED4A00617758 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AD7EA6D1AC3ED4A00617758 /* libz.dylib */; }; 0AD7EA741AC3EE9600617758 /* libPushSDK-1.8.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AD7EA731AC3EE9600617758 /* libPushSDK-1.8.3.a */; }; 0AD7EA771AC3EEB000617758 /* PushConfig.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0AD7EA761AC3EEB000617758 /* PushConfig.plist */; }; - 0AFE75AF1AC43DE0005AA33E /* Crashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AFE75AE1AC43DE0005AA33E /* Crashlytics.framework */; }; + 0AEB1CC21B0E69B500178C9C /* Fabric.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AEB1CC11B0E69B500178C9C /* Fabric.framework */; }; + 0AEB1CC41B0E6A4B00178C9C /* Crashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AEB1CC31B0E6A4B00178C9C /* Crashlytics.framework */; }; + 0AEB1CC61B0E742400178C9C /* Double+Yep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AEB1CC51B0E742400178C9C /* Double+Yep.swift */; }; 15606C18E1487982869C0A42 /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD88F6095DB46DFAD41AB5DB /* Pods.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 50165C451AC2860900C7AEBE /* ConversationLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50165C441AC2860900C7AEBE /* ConversationLayout.swift */; }; + 502048241B0F1BB3002EBFC7 /* SearchedUsersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 502048231B0F1BB3002EBFC7 /* SearchedUsersViewController.swift */; }; 5023DE261AB6856300B3EE96 /* ConversationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5023DE251AB6856300B3EE96 /* ConversationsViewController.swift */; }; 5023DE2A1AB685BA00B3EE96 /* ContactsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5023DE291AB685BA00B3EE96 /* ContactsViewController.swift */; }; 5023DE2D1AB685ED00B3EE96 /* DiscoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5023DE2C1AB685ED00B3EE96 /* DiscoverViewController.swift */; }; @@ -95,6 +104,7 @@ 5053AD561AF83B0F00B3CBFA /* ChatRightLocationCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5053AD541AF83B0F00B3CBFA /* ChatRightLocationCell.xib */; }; 5053AD5A1AF83B4200B3CBFA /* ChatLeftLocationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5053AD581AF83B4200B3CBFA /* ChatLeftLocationCell.swift */; }; 5053AD5B1AF83B4200B3CBFA /* ChatLeftLocationCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5053AD591AF83B4200B3CBFA /* ChatLeftLocationCell.xib */; }; + 505498621B12FB0E0037E3BD /* ConversationMessagePreviewNavigationControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505498611B12FB0E0037E3BD /* ConversationMessagePreviewNavigationControllerDelegate.swift */; }; 50553F8F1ADB8BA200F80B59 /* ChatSectionDateCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50553F8D1ADB8BA200F80B59 /* ChatSectionDateCell.swift */; }; 50553F901ADB8BA200F80B59 /* ChatSectionDateCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 50553F8E1ADB8BA200F80B59 /* ChatSectionDateCell.xib */; }; 50553F931ADBA50600F80B59 /* NSDate+Yep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50553F921ADBA50600F80B59 /* NSDate+Yep.swift */; }; @@ -105,6 +115,11 @@ 505EC8181ADF8151001D27E0 /* SkillCategoryCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 505EC8161ADF8151001D27E0 /* SkillCategoryCell.xib */; }; 505EC81C1ADFBE1B001D27E0 /* SkillSelectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505EC81A1ADFBE1B001D27E0 /* SkillSelectionCell.swift */; }; 505EC81D1ADFBE1B001D27E0 /* SkillSelectionCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 505EC81B1ADFBE1B001D27E0 /* SkillSelectionCell.xib */; }; + 5064F6501B0AE2E60089FAD4 /* AddFriendsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5064F64F1B0AE2E60089FAD4 /* AddFriendsViewController.swift */; }; + 5064F6541B0B0E160089FAD4 /* AddFriendSearchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5064F6521B0B0E160089FAD4 /* AddFriendSearchCell.swift */; }; + 5064F6551B0B0E160089FAD4 /* AddFriendSearchCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5064F6531B0B0E160089FAD4 /* AddFriendSearchCell.xib */; }; + 5064F6591B0B14420089FAD4 /* AddFriendMoreCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5064F6571B0B14420089FAD4 /* AddFriendMoreCell.swift */; }; + 5064F65A1B0B14420089FAD4 /* AddFriendMoreCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5064F6581B0B14420089FAD4 /* AddFriendMoreCell.xib */; }; 506923121ADE025200D27574 /* Waver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506923111ADE025200D27574 /* Waver.swift */; }; 506923141ADE025F00D27574 /* YepRefreshView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506923131ADE025F00D27574 /* YepRefreshView.swift */; }; 506BB7F51AE4E0A500C1A2A0 /* SkillAnnotationHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506BB7F31AE4E0A500C1A2A0 /* SkillAnnotationHeader.swift */; }; @@ -173,6 +188,7 @@ 50E114F91ABC137A00F13000 /* YepServiceSync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E114F81ABC137A00F13000 /* YepServiceSync.swift */; }; 50E114FC1ABC549300F13000 /* ContactsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E114FA1ABC549300F13000 /* ContactsCell.swift */; }; 50E114FD1ABC549300F13000 /* ContactsCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 50E114FB1ABC549300F13000 /* ContactsCell.xib */; }; + 50E18BB71B180F7D0076C3C6 /* SayHiView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E18BB61B180F7D0076C3C6 /* SayHiView.swift */; }; 50E61CEB1AF1F69D00908A9D /* ConversationTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E61CEA1AF1F69D00908A9D /* ConversationTitleView.swift */; }; 50EB8C5E1AE78AB9001AC1EE /* YepAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50EB8C5D1AE78AB9001AC1EE /* YepAsset.swift */; }; 50EB8CBF1AE89ED8001AC1EE /* ChatLeftVideoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50EB8CBD1AE89ED8001AC1EE /* ChatLeftVideoCell.swift */; }; @@ -218,8 +234,14 @@ 0A90187D1B01152800AE4B7F /* ProfileSocialAccountCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = ProfileSocialAccountCell.xib; path = Views/Cells/ProfileSocialAccount/ProfileSocialAccountCell.xib; sourceTree = ""; }; 0A9018811B0120A500AE4B7F /* OAuthViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OAuthViewController.swift; path = ViewControllers/OAuth/OAuthViewController.swift; sourceTree = ""; }; 0A9018861B01321F00AE4B7F /* WebViewJavascriptBridge.js.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = WebViewJavascriptBridge.js.txt; sourceTree = ""; }; + 0A943A7A1B0F8EAE0022DD67 /* BaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BaseViewController.swift; path = ViewControllers/Base/BaseViewController.swift; sourceTree = ""; }; 0A944C241AC8BB2F00037A06 /* YepStorageService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = YepStorageService.swift; path = Services/YepStorageService.swift; sourceTree = ""; }; + 0A98288D1B18D2BD001725B7 /* ChatStateCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ChatStateCell.swift; path = Views/Cells/ChatState/ChatStateCell.swift; sourceTree = ""; }; + 0A98288E1B18D2BD001725B7 /* ChatStateCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = ChatStateCell.xib; path = Views/Cells/ChatState/ChatStateCell.xib; sourceTree = ""; }; 0AAFCFF81AC8CA5B00EB0EF8 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; + 0AB8F7991B15D1DC00F4AF09 /* YepNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = YepNavigationController.swift; path = ViewControllers/Nav/YepNavigationController.swift; sourceTree = ""; }; + 0AC25F021B1100F0009E6E13 /* YepScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = YepScrollView.swift; path = Views/ScrollView/YepScrollView.swift; sourceTree = ""; }; + 0AC25F051B1105D5009E6E13 /* YepChildScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = YepChildScrollView.swift; path = Views/ScrollView/YepChildScrollView.swift; sourceTree = ""; }; 0AD7EA511AC3EA6F00617758 /* JPushSDK.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JPushSDK.framework; path = "Pods/../build/Debug-iphoneos/Pods/JPushSDK.framework"; sourceTree = ""; }; 0AD7EA581AC3EAF300617758 /* Yep-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Yep-Bridging-Header.h"; sourceTree = ""; }; 0AD7EA5B1AC3ECD700617758 /* libPushSDK-1.8.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libPushSDK-1.8.3.a"; path = "Yep/lib/libPushSDK-1.8.3.a"; sourceTree = ""; }; @@ -235,9 +257,12 @@ 0AD7EA721AC3EE9600617758 /* APService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = APService.h; path = JPush/APService.h; sourceTree = ""; }; 0AD7EA731AC3EE9600617758 /* libPushSDK-1.8.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libPushSDK-1.8.3.a"; path = "JPush/libPushSDK-1.8.3.a"; sourceTree = ""; }; 0AD7EA761AC3EEB000617758 /* PushConfig.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PushConfig.plist; path = JPush/PushConfig.plist; sourceTree = ""; }; - 0AFE75AE1AC43DE0005AA33E /* Crashlytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Crashlytics.framework; path = Yep/Crashlytics.framework; sourceTree = ""; }; + 0AEB1CC11B0E69B500178C9C /* Fabric.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Fabric.framework; sourceTree = ""; }; + 0AEB1CC31B0E6A4B00178C9C /* Crashlytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Crashlytics.framework; sourceTree = ""; }; + 0AEB1CC51B0E742400178C9C /* Double+Yep.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Double+Yep.swift"; path = "Extensions/Double+Yep.swift"; sourceTree = ""; }; 2268251A75FF6766DC31CCFB /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; 50165C441AC2860900C7AEBE /* ConversationLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ConversationLayout.swift; path = ViewControllers/Conversation/ConversationLayout.swift; sourceTree = ""; }; + 502048231B0F1BB3002EBFC7 /* SearchedUsersViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SearchedUsersViewController.swift; path = ViewControllers/SearchedUsers/SearchedUsersViewController.swift; sourceTree = ""; }; 5023DE251AB6856300B3EE96 /* ConversationsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ConversationsViewController.swift; path = ViewControllers/Conversations/ConversationsViewController.swift; sourceTree = ""; }; 5023DE291AB685BA00B3EE96 /* ContactsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ContactsViewController.swift; path = ViewControllers/Contacts/ContactsViewController.swift; sourceTree = ""; }; 5023DE2C1AB685ED00B3EE96 /* DiscoverViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DiscoverViewController.swift; path = ViewControllers/Discover/DiscoverViewController.swift; sourceTree = ""; }; @@ -290,6 +315,7 @@ 5053AD541AF83B0F00B3CBFA /* ChatRightLocationCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = ChatRightLocationCell.xib; path = Views/Cells/ChatRightLocation/ChatRightLocationCell.xib; sourceTree = ""; }; 5053AD581AF83B4200B3CBFA /* ChatLeftLocationCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ChatLeftLocationCell.swift; path = Views/Cells/ChatLeftLocation/ChatLeftLocationCell.swift; sourceTree = ""; }; 5053AD591AF83B4200B3CBFA /* ChatLeftLocationCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = ChatLeftLocationCell.xib; path = Views/Cells/ChatLeftLocation/ChatLeftLocationCell.xib; sourceTree = ""; }; + 505498611B12FB0E0037E3BD /* ConversationMessagePreviewNavigationControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ConversationMessagePreviewNavigationControllerDelegate.swift; path = ViewControllers/Conversation/ConversationMessagePreviewNavigationControllerDelegate.swift; sourceTree = ""; }; 50553F8D1ADB8BA200F80B59 /* ChatSectionDateCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ChatSectionDateCell.swift; path = Views/Cells/ChatSectionDate/ChatSectionDateCell.swift; sourceTree = ""; }; 50553F8E1ADB8BA200F80B59 /* ChatSectionDateCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = ChatSectionDateCell.xib; path = Views/Cells/ChatSectionDate/ChatSectionDateCell.xib; sourceTree = ""; }; 50553F921ADBA50600F80B59 /* NSDate+Yep.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "NSDate+Yep.swift"; path = "Extensions/NSDate+Yep.swift"; sourceTree = ""; }; @@ -300,6 +326,11 @@ 505EC8161ADF8151001D27E0 /* SkillCategoryCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = SkillCategoryCell.xib; path = Views/Cells/SkillCategory/SkillCategoryCell.xib; sourceTree = ""; }; 505EC81A1ADFBE1B001D27E0 /* SkillSelectionCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SkillSelectionCell.swift; path = Views/Cells/SkillSelection/SkillSelectionCell.swift; sourceTree = ""; }; 505EC81B1ADFBE1B001D27E0 /* SkillSelectionCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = SkillSelectionCell.xib; path = Views/Cells/SkillSelection/SkillSelectionCell.xib; sourceTree = ""; }; + 5064F64F1B0AE2E60089FAD4 /* AddFriendsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFriendsViewController.swift; path = ViewControllers/AddFriends/AddFriendsViewController.swift; sourceTree = ""; }; + 5064F6521B0B0E160089FAD4 /* AddFriendSearchCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFriendSearchCell.swift; path = Views/Cells/AddFriendSearch/AddFriendSearchCell.swift; sourceTree = ""; }; + 5064F6531B0B0E160089FAD4 /* AddFriendSearchCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = AddFriendSearchCell.xib; path = Views/Cells/AddFriendSearch/AddFriendSearchCell.xib; sourceTree = ""; }; + 5064F6571B0B14420089FAD4 /* AddFriendMoreCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFriendMoreCell.swift; path = Views/Cells/AddFriendMore/AddFriendMoreCell.swift; sourceTree = ""; }; + 5064F6581B0B14420089FAD4 /* AddFriendMoreCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = AddFriendMoreCell.xib; path = Views/Cells/AddFriendMore/AddFriendMoreCell.xib; sourceTree = ""; }; 506923111ADE025200D27574 /* Waver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Waver.swift; path = Views/AudioWaves/Waver.swift; sourceTree = ""; }; 506923131ADE025F00D27574 /* YepRefreshView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = YepRefreshView.swift; path = Views/PullToRefresh/YepRefreshView.swift; sourceTree = ""; }; 506BB7F31AE4E0A500C1A2A0 /* SkillAnnotationHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SkillAnnotationHeader.swift; path = Views/ReusableViews/SkillAnnotationHeader/SkillAnnotationHeader.swift; sourceTree = ""; }; @@ -368,6 +399,7 @@ 50E114F81ABC137A00F13000 /* YepServiceSync.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = YepServiceSync.swift; path = Services/YepServiceSync.swift; sourceTree = ""; }; 50E114FA1ABC549300F13000 /* ContactsCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ContactsCell.swift; path = Views/Cells/Contacts/ContactsCell.swift; sourceTree = ""; }; 50E114FB1ABC549300F13000 /* ContactsCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = ContactsCell.xib; path = Views/Cells/Contacts/ContactsCell.xib; sourceTree = ""; }; + 50E18BB61B180F7D0076C3C6 /* SayHiView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SayHiView.swift; path = Views/SayHi/SayHiView.swift; sourceTree = ""; }; 50E61CEA1AF1F69D00908A9D /* ConversationTitleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ConversationTitleView.swift; path = Views/ConversationTitle/ConversationTitleView.swift; sourceTree = ""; }; 50EB8C5D1AE78AB9001AC1EE /* YepAsset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = YepAsset.swift; path = Helpers/YepAsset.swift; sourceTree = ""; }; 50EB8CBD1AE89ED8001AC1EE /* ChatLeftVideoCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ChatLeftVideoCell.swift; path = Views/Cells/ChatLeftVideo/ChatLeftVideoCell.swift; sourceTree = ""; }; @@ -386,6 +418,7 @@ buildActionMask = 2147483647; files = ( 0AD7EA681AC3ED3600617758 /* Foundation.framework in Frameworks */, + 0AEB1CC41B0E6A4B00178C9C /* Crashlytics.framework in Frameworks */, 0A02E46F1AC9208A00235DBF /* libxml2.dylib in Frameworks */, 0AAFCFF91AC8CA5B00EB0EF8 /* MobileCoreServices.framework in Frameworks */, 0AD7EA741AC3EE9600617758 /* libPushSDK-1.8.3.a in Frameworks */, @@ -396,8 +429,8 @@ 0AD7EA641AC3ED2A00617758 /* SystemConfiguration.framework in Frameworks */, 0AD7EA621AC3ED2100617758 /* CoreTelephony.framework in Frameworks */, 0AD7EA601AC3ED1900617758 /* CoreFoundation.framework in Frameworks */, + 0AEB1CC21B0E69B500178C9C /* Fabric.framework in Frameworks */, 0AD7EA5E1AC3ED1200617758 /* CFNetwork.framework in Frameworks */, - 0AFE75AF1AC43DE0005AA33E /* Crashlytics.framework in Frameworks */, 15606C18E1487982869C0A42 /* Pods.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -559,6 +592,40 @@ name = WebViewJSBridge; sourceTree = ""; }; + 0A943A7C1B0F8EB10022DD67 /* Base */ = { + isa = PBXGroup; + children = ( + 0A943A7A1B0F8EAE0022DD67 /* BaseViewController.swift */, + ); + name = Base; + sourceTree = ""; + }; + 0A9828911B18D2C3001725B7 /* ChatState */ = { + isa = PBXGroup; + children = ( + 0A98288E1B18D2BD001725B7 /* ChatStateCell.xib */, + 0A98288D1B18D2BD001725B7 /* ChatStateCell.swift */, + ); + name = ChatState; + sourceTree = ""; + }; + 0AB8F79B1B15D1DF00F4AF09 /* Nav */ = { + isa = PBXGroup; + children = ( + 0AB8F7991B15D1DC00F4AF09 /* YepNavigationController.swift */, + ); + name = Nav; + sourceTree = ""; + }; + 0AC25F041B1100F3009E6E13 /* ScrollView */ = { + isa = PBXGroup; + children = ( + 0AC25F021B1100F0009E6E13 /* YepScrollView.swift */, + 0AC25F051B1105D5009E6E13 /* YepChildScrollView.swift */, + ); + name = ScrollView; + sourceTree = ""; + }; 0AD7EA751AC3EE9B00617758 /* JPush */ = { isa = PBXGroup; children = ( @@ -569,6 +636,14 @@ name = JPush; sourceTree = ""; }; + 502048251B0F1BB8002EBFC7 /* SearchedUsers */ = { + isa = PBXGroup; + children = ( + 502048231B0F1BB3002EBFC7 /* SearchedUsersViewController.swift */, + ); + name = SearchedUsers; + sourceTree = ""; + }; 5023DE271AB6856600B3EE96 /* ViewControllers */ = { isa = PBXGroup; children = ( @@ -590,6 +665,10 @@ 507CF1691AFC84AA00E261B4 /* CustomNavigationBar */, 0A9018831B0120A800AE4B7F /* OAuth */, 508F8FCD1B01C80500461B0B /* SocialWorks */, + 5064F6511B0AE2ED0089FAD4 /* AddFriends */, + 502048251B0F1BB8002EBFC7 /* SearchedUsers */, + 0A943A7C1B0F8EB10022DD67 /* Base */, + 0AB8F79B1B15D1DF00F4AF09 /* Nav */, ); name = ViewControllers; sourceTree = ""; @@ -640,6 +719,7 @@ 50A9D44A1ACD37A8000B2599 /* NSFileManager+Yep.swift */, 50553F921ADBA50600F80B59 /* NSDate+Yep.swift */, 508F8FE11B04B1F900461B0B /* UIDevice+Yep.swift */, + 0AEB1CC51B0E742400178C9C /* Double+Yep.swift */, ); name = Extensions; sourceTree = ""; @@ -660,6 +740,8 @@ 50E61CEC1AF1F6A600908A9D /* ConversationTitle */, 0A1CAC711AFA481900826B45 /* SkillHomeHeaderView */, 0A1CAC741AFA526400826B45 /* SkillHomeSectionButton */, + 0AC25F041B1100F3009E6E13 /* ScrollView */, + 50E18BB81B180F8E0076C3C6 /* SayHi */, ); name = Views; sourceTree = ""; @@ -698,6 +780,9 @@ 508F8FD21B01D04F00461B0B /* GithubRepo */, 508F8FD91B01EDBC00461B0B /* DribbbleShot */, 508F8FE01B049AD400461B0B /* InstagramMedia */, + 5064F6561B0B0E1B0089FAD4 /* AddFriendSearch */, + 5064F65B1B0B14470089FAD4 /* AddFriendMore */, + 0A9828911B18D2C3001725B7 /* ChatState */, ); name = Cells; sourceTree = ""; @@ -886,6 +971,7 @@ 503CAB421AC00CB100DFE830 /* ConversationViewController.swift */, 50165C441AC2860900C7AEBE /* ConversationLayout.swift */, 5076FC591AF9DF6B00D7381A /* ConversationMessagePreviewTransitionManager.swift */, + 505498611B12FB0E0037E3BD /* ConversationMessagePreviewNavigationControllerDelegate.swift */, ); name = Conversation; sourceTree = ""; @@ -962,6 +1048,32 @@ name = SkillSelection; sourceTree = ""; }; + 5064F6511B0AE2ED0089FAD4 /* AddFriends */ = { + isa = PBXGroup; + children = ( + 5064F64F1B0AE2E60089FAD4 /* AddFriendsViewController.swift */, + ); + name = AddFriends; + sourceTree = ""; + }; + 5064F6561B0B0E1B0089FAD4 /* AddFriendSearch */ = { + isa = PBXGroup; + children = ( + 5064F6521B0B0E160089FAD4 /* AddFriendSearchCell.swift */, + 5064F6531B0B0E160089FAD4 /* AddFriendSearchCell.xib */, + ); + name = AddFriendSearch; + sourceTree = ""; + }; + 5064F65B1B0B14470089FAD4 /* AddFriendMore */ = { + isa = PBXGroup; + children = ( + 5064F6571B0B14420089FAD4 /* AddFriendMoreCell.swift */, + 5064F6581B0B14420089FAD4 /* AddFriendMoreCell.xib */, + ); + name = AddFriendMore; + sourceTree = ""; + }; 506BB7F71AE4E0AA00C1A2A0 /* SkillAnnotationHeader */ = { isa = PBXGroup; children = ( @@ -1200,6 +1312,14 @@ name = Contacts; sourceTree = ""; }; + 50E18BB81B180F8E0076C3C6 /* SayHi */ = { + isa = PBXGroup; + children = ( + 50E18BB61B180F7D0076C3C6 /* SayHiView.swift */, + ); + name = SayHi; + sourceTree = ""; + }; 50E61CEC1AF1F6A600908A9D /* ConversationTitle */ = { isa = PBXGroup; children = ( @@ -1255,7 +1375,8 @@ 67B708B60AAD1DEA82A6A682 /* Frameworks */ = { isa = PBXGroup; children = ( - 0AFE75AE1AC43DE0005AA33E /* Crashlytics.framework */, + 0AEB1CC31B0E6A4B00178C9C /* Crashlytics.framework */, + 0AEB1CC11B0E69B500178C9C /* Fabric.framework */, 0A02E46E1AC9208A00235DBF /* libxml2.dylib */, 0AAFCFF81AC8CA5B00EB0EF8 /* MobileCoreServices.framework */, 0AD7EA6D1AC3ED4A00617758 /* libz.dylib */, @@ -1375,10 +1496,12 @@ 50553F901ADB8BA200F80B59 /* ChatSectionDateCell.xib in Resources */, 502AE5411AB88862005BD199 /* SkillRankCell.xib in Resources */, 508F8FD11B01D04400461B0B /* GithubRepoCell.xib in Resources */, + 0A9828901B18D2BD001725B7 /* ChatStateCell.xib in Resources */, 508F8FD81B01EDB400461B0B /* DribbbleShotCell.xib in Resources */, 502AE5541AB93215005BD199 /* ProfileFooterCell.xib in Resources */, 50CDBE4A1ACBAD3200459CE0 /* ChatLeftImageCell.xib in Resources */, 0A90187F1B01152800AE4B7F /* ProfileSocialAccountCell.xib in Resources */, + 5064F6551B0B0E160089FAD4 /* AddFriendSearchCell.xib in Resources */, 508F8FC91B0161EF00461B0B /* ProfileSeparationLineCell.xib in Resources */, 50894CF61AEA4A01000981AF /* SettingsUserCell.xib in Resources */, 508D17481ACCF4710092D666 /* ChatLeftAudioCell.xib in Resources */, @@ -1407,6 +1530,7 @@ 503315981AC1017E0008A209 /* ChatLeftTextCell.xib in Resources */, 50894D031AEA6433000981AF /* EditProfileLessInfoCell.xib in Resources */, 0AD7EA771AC3EEB000617758 /* PushConfig.plist in Resources */, + 5064F65A1B0B14420089FAD4 /* AddFriendMoreCell.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1508,6 +1632,7 @@ 503315971AC1017E0008A209 /* ChatLeftTextCell.swift in Sources */, 0A8F54B11AB67D11004AD60E /* AppDelegate.swift in Sources */, 508F8FC81B0161EF00461B0B /* ProfileSeparationLineCell.swift in Sources */, + 0AEB1CC61B0E742400178C9C /* Double+Yep.swift in Sources */, 508D174C1ACCF49B0092D666 /* ChatRightAudioCell.swift in Sources */, 502AE53D1AB87C0D005BD199 /* YepUserDefaults.swift in Sources */, 50EB8CC41AE89F12001AC1EE /* ChatRightVideoCell.swift in Sources */, @@ -1523,6 +1648,8 @@ 50E114F41ABBF9CC00F13000 /* Models.swift in Sources */, 5053AD551AF83B0F00B3CBFA /* ChatRightLocationCell.swift in Sources */, 50A9D4571ACD393A000B2599 /* YepWaverView.swift in Sources */, + 0AC25F061B1105D5009E6E13 /* YepChildScrollView.swift in Sources */, + 0A943A7B1B0F8EAE0022DD67 /* BaseViewController.swift in Sources */, 50165C451AC2860900C7AEBE /* ConversationLayout.swift in Sources */, 50EB8C5E1AE78AB9001AC1EE /* YepAsset.swift in Sources */, 508C2AA41AD661B3002B8097 /* UserStateCell.swift in Sources */, @@ -1534,11 +1661,13 @@ 508F8FD41B01E3D100461B0B /* SocialWorkDribbbleViewController.swift in Sources */, 50EB8CBF1AE89ED8001AC1EE /* ChatLeftVideoCell.swift in Sources */, 502AE5331AB86020005BD199 /* RegisterVerifyMobileViewController.swift in Sources */, + 5064F6541B0B0E160089FAD4 /* AddFriendSearchCell.swift in Sources */, 0A1CAC6E1AFA42D400826B45 /* SkillHomeViewController.swift in Sources */, 502AE5401AB88862005BD199 /* SkillRankCell.swift in Sources */, 50C090A01ADE391600CC6389 /* AddSkillsReusableView.swift in Sources */, 5023DE2D1AB685ED00B3EE96 /* DiscoverViewController.swift in Sources */, 0A1CAC701AFA481600826B45 /* SkillHomeHeaderView.swift in Sources */, + 0AB8F79A1B15D1DC00F4AF09 /* YepNavigationController.swift in Sources */, 502AE53A1AB871C1005BD199 /* YepAlert.swift in Sources */, 5039594E1B05E6E000797A3E /* ProfileSocialAccountGithubCell.swift in Sources */, 5053AD4D1AF79EEA00B3CBFA /* UserPickedLocationPin.swift in Sources */, @@ -1560,6 +1689,8 @@ 502AE5271AB72D06005BD199 /* YepNetworking.swift in Sources */, 5076C56A1AC46B9F00B22952 /* FayeService.swift in Sources */, 505EC8131ADE607D001D27E0 /* SkillCategoryButton.swift in Sources */, + 0AC25F031B1100F0009E6E13 /* YepScrollView.swift in Sources */, + 5064F6591B0B14420089FAD4 /* AddFriendMoreCell.swift in Sources */, 5099B9C71AB99F7B002F940B /* RegisterPickAvatarViewController.swift in Sources */, 504F41251ACE2D1D00FBB19A /* SampleView.swift in Sources */, 502AE5291AB81076005BD199 /* YepService.swift in Sources */, @@ -1581,6 +1712,7 @@ 506BB7F91AE5069500C1A2A0 /* RegisterPickSkillsSelectSkillsTransitionManager.swift in Sources */, 502AE52B1AB819AF005BD199 /* YepConfig.swift in Sources */, 0A271FD71ACA055400822DFC /* YepAudioService.swift in Sources */, + 5064F6501B0AE2E60089FAD4 /* AddFriendsViewController.swift in Sources */, 507CF1681AFC84A500E261B4 /* CustomNavigationBarViewController.swift in Sources */, 0A3E60601ACCA9C0003DC853 /* String+Yep.swift in Sources */, 504F41571AD4D35400FBB19A /* ProfileLayout.swift in Sources */, @@ -1588,10 +1720,14 @@ 50A9D44B1ACD37A8000B2599 /* NSFileManager+Yep.swift in Sources */, 502AE5531AB93215005BD199 /* ProfileFooterCell.swift in Sources */, 50EB8CCB1AEA1AC0001AC1EE /* MediaView.swift in Sources */, + 0A98288F1B18D2BD001725B7 /* ChatStateCell.swift in Sources */, 506923121ADE025200D27574 /* Waver.swift in Sources */, 0A5D83991AC5D91E000045BF /* ContactsSearchTableViewController.swift in Sources */, + 502048241B0F1BB3002EBFC7 /* SearchedUsersViewController.swift in Sources */, + 505498621B12FB0E0037E3BD /* ConversationMessagePreviewNavigationControllerDelegate.swift in Sources */, 0A944C251AC8BB2F00037A06 /* YepStorageService.swift in Sources */, 505EC8171ADF8151001D27E0 /* SkillCategoryCell.swift in Sources */, + 50E18BB71B180F7D0076C3C6 /* SayHiView.swift in Sources */, 0A90187E1B01152800AE4B7F /* ProfileSocialAccountCell.swift in Sources */, 50894CF51AEA4A01000981AF /* SettingsUserCell.swift in Sources */, 5053AD5A1AF83B4200B3CBFA /* ChatLeftLocationCell.swift in Sources */, @@ -1742,6 +1878,7 @@ "$(inherited)", "$(PROJECT_DIR)/Yep", "$(PROJECT_DIR)/build/Debug-iphoneos/Pods", + "$(PROJECT_DIR)", ); GCC_OPTIMIZATION_LEVEL = 0; INFOPLIST_FILE = Yep/Info.plist; @@ -1768,6 +1905,7 @@ "$(inherited)", "$(PROJECT_DIR)/Yep", "$(PROJECT_DIR)/build/Debug-iphoneos/Pods", + "$(PROJECT_DIR)", ); INFOPLIST_FILE = Yep/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; diff --git a/Yep/AppDelegate.swift b/Yep/AppDelegate.swift index d8289a87..3206b335 100644 --- a/Yep/AppDelegate.swift +++ b/Yep/AppDelegate.swift @@ -23,7 +23,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { - setSchemaVersion(3, Realm.defaultPath, { migration, oldSchemaVersion in + setSchemaVersion(5, Realm.defaultPath, { migration, oldSchemaVersion in // We haven’t migrated anything yet, so oldSchemaVersion == 0 if oldSchemaVersion < 1 { // Nothing to do! @@ -36,6 +36,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { if oldSchemaVersion < 3 { } + + if oldSchemaVersion < 4 { + } + + if oldSchemaVersion < 5 { + } }) Crashlytics.startWithAPIKey("3030ba006e21bcf8eb4a2127b6a7931ea6667486") @@ -176,11 +182,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } func sync() { - syncFriendshipsAndDoFurtherAction { - syncGroupsAndDoFurtherAction { - syncUnreadMessagesAndDoFurtherAction { - dispatch_async(dispatch_get_main_queue()) { - NSNotificationCenter.defaultCenter().postNotificationName(YepNewMessagesReceivedNotification, object: nil) + syncMyInfoAndDoFurtherAction { + syncFriendshipsAndDoFurtherAction { + syncGroupsAndDoFurtherAction { + syncUnreadMessagesAndDoFurtherAction { + dispatch_async(dispatch_get_main_queue()) { + NSNotificationCenter.defaultCenter().postNotificationName(YepNewMessagesReceivedNotification, object: nil) + } } } } @@ -224,9 +232,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate { NSShadowAttributeName: shadow, NSFontAttributeName: UIFont.navigationBarTitleFont() ] + + let barButtonTextAttributes = [ + NSForegroundColorAttributeName: UIColor.yepTintColor(), + NSFontAttributeName: UIFont.barButtonFont() + ] UINavigationBar.appearance().titleTextAttributes = textAttributes - + UINavigationBar.appearance().barTintColor = UIColor.whiteColor() + UIBarButtonItem.appearance().setTitleTextAttributes(barButtonTextAttributes, forState: UIControlState.Normal) //UINavigationBar.appearance().setBackgroundImage(UIImage(named:"white"), forBarMetrics: .Default) //UINavigationBar.appearance().shadowImage = UIImage() //UINavigationBar.appearance().translucent = false @@ -237,6 +251,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { //UITabBar.appearance().backgroundImage = UIImage(named:"white") //UITabBar.appearance().shadowImage = UIImage() UITabBar.appearance().tintColor = UIColor.yepTintColor() + UITabBar.appearance().barTintColor = UIColor.whiteColor() //UITabBar.appearance().translucent = false } } diff --git a/Yep/Base.lproj/Intro.storyboard b/Yep/Base.lproj/Intro.storyboard index 8c8cfac1..c3033518 100644 --- a/Yep/Base.lproj/Intro.storyboard +++ b/Yep/Base.lproj/Intro.storyboard @@ -1,7 +1,7 @@ - + - + diff --git a/Yep/Base.lproj/Main.storyboard b/Yep/Base.lproj/Main.storyboard index ad37c86b..b0146f90 100644 --- a/Yep/Base.lproj/Main.storyboard +++ b/Yep/Base.lproj/Main.storyboard @@ -1,7 +1,7 @@ - + - + @@ -18,9 +18,11 @@ - + + + @@ -72,8 +74,35 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + @@ -89,8 +118,8 @@ - - - - - - - - - @@ -525,14 +560,14 @@ - + - + @@ -602,7 +637,7 @@ - + @@ -645,7 +680,85 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -692,7 +805,7 @@ - + @@ -817,7 +930,7 @@ - + @@ -840,7 +953,7 @@ - + @@ -863,7 +976,7 @@ - + @@ -886,7 +999,7 @@ - + @@ -1155,10 +1268,12 @@ + - - + + + diff --git a/Yep/Caches/AvatarCache.swift b/Yep/Caches/AvatarCache.swift index afb3ab22..3785f237 100644 --- a/Yep/Caches/AvatarCache.swift +++ b/Yep/Caches/AvatarCache.swift @@ -217,6 +217,10 @@ class AvatarCache { if let url = NSURL(string: user.avatarURLString) { let roundImageKey = "round-\(radius)-\(url.hashValue)" + // 为下面切换线程准备,Realm 不能跨线程访问 + let avatarURLString = user.avatarURLString + let userID = user.userID + // 先看看缓存 if let roundImage = cache.objectForKey(roundImageKey) as? UIImage { completion(roundImage) @@ -225,7 +229,7 @@ class AvatarCache { // 再看看是否已下载 if let avatar = user.avatar { - if avatar.avatarURLString == user.avatarURLString { + if avatar.avatarURLString == avatarURLString { if let avatarFileURL = NSFileManager.yepAvatarURLWithName(avatar.avatarFileName), @@ -245,7 +249,7 @@ class AvatarCache { let realm = Realm() // 不能直接使用 user.avatar, 因为 realm 不同 - if let avatar = avatarWithAvatarURLString(user.avatarURLString, inRealm: realm) { + if let avatar = avatarWithAvatarURLString(avatarURLString, inRealm: realm) { realm.write { realm.delete(avatar) } @@ -263,14 +267,14 @@ class AvatarCache { dispatch_async(dispatch_get_main_queue()) { let realm = Realm() - var avatar = avatarWithAvatarURLString(user.avatarURLString, inRealm: realm) + var avatar = avatarWithAvatarURLString(avatarURLString, inRealm: realm) if avatar == nil { let avatarFileName = NSUUID().UUIDString if let avatarURL = NSFileManager.saveAvatarImage(image, withName: avatarFileName) { let newAvatar = Avatar() - newAvatar.avatarURLString = user.avatarURLString + newAvatar.avatarURLString = avatarURLString newAvatar.avatarFileName = avatarFileName realm.write { @@ -282,7 +286,7 @@ class AvatarCache { } // 这里重新用新 realm 获取 user,避免在不同线程访问,导致 "Realm accessed from incorrect thread" - if let user = userWithUserID(user.userID, inRealm: realm) { + if let user = userWithUserID(userID, inRealm: realm) { if user.avatar == nil, let avatar = avatar { realm.write { user.avatar = avatar diff --git a/Yep/Configs/YepConfig.swift b/Yep/Configs/YepConfig.swift index 5800c2fc..4cb57bb8 100644 --- a/Yep/Configs/YepConfig.swift +++ b/Yep/Configs/YepConfig.swift @@ -82,7 +82,7 @@ class YepConfig { struct Profile { static let leftEdgeInset: CGFloat = UIDevice.matchMarginFrom(20, 38, 40) static let rightEdgeInset: CGFloat = leftEdgeInset - static let introductionLabelFont = UIFont(name: "HelveticaNeue-Thin", size: 12)! + static let introductionLabelFont = UIFont(name: "Helvetica-Light", size: 14)! } struct Settings { diff --git a/Yep/Crashlytics.framework/Crashlytics b/Yep/Crashlytics.framework/Crashlytics deleted file mode 120000 index 7074275f..00000000 --- a/Yep/Crashlytics.framework/Crashlytics +++ /dev/null @@ -1 +0,0 @@ -Versions/Current/Crashlytics \ No newline at end of file diff --git a/Yep/Crashlytics.framework/Headers b/Yep/Crashlytics.framework/Headers deleted file mode 120000 index a177d2a6..00000000 --- a/Yep/Crashlytics.framework/Headers +++ /dev/null @@ -1 +0,0 @@ -Versions/Current/Headers \ No newline at end of file diff --git a/Yep/Crashlytics.framework/Modules/module.modulemap b/Yep/Crashlytics.framework/Modules/module.modulemap deleted file mode 100644 index e552e9ca..00000000 --- a/Yep/Crashlytics.framework/Modules/module.modulemap +++ /dev/null @@ -1,6 +0,0 @@ -framework module Crashlytics { - umbrella header "Crashlytics.h" - - export * - module * { export * } -} diff --git a/Yep/Crashlytics.framework/Resources b/Yep/Crashlytics.framework/Resources deleted file mode 120000 index 953ee36f..00000000 --- a/Yep/Crashlytics.framework/Resources +++ /dev/null @@ -1 +0,0 @@ -Versions/Current/Resources \ No newline at end of file diff --git a/Yep/Crashlytics.framework/Versions/A/Crashlytics b/Yep/Crashlytics.framework/Versions/A/Crashlytics deleted file mode 100644 index 42d9da7e..00000000 Binary files a/Yep/Crashlytics.framework/Versions/A/Crashlytics and /dev/null differ diff --git a/Yep/Crashlytics.framework/Versions/A/Headers/Crashlytics.h b/Yep/Crashlytics.framework/Versions/A/Headers/Crashlytics.h deleted file mode 100644 index 9683173c..00000000 --- a/Yep/Crashlytics.framework/Versions/A/Headers/Crashlytics.h +++ /dev/null @@ -1,225 +0,0 @@ -// -// Crashlytics.h -// Crashlytics -// -// Copyright 2013 Crashlytics, Inc. All rights reserved. -// - -#import - -/** - * - * The CLS_LOG macro provides as easy way to gather more information in your log messages that are - * sent with your crash data. CLS_LOG prepends your custom log message with the function name and - * line number where the macro was used. If your app was built with the DEBUG preprocessor macro - * defined CLS_LOG uses the CLSNSLog function which forwards your log message to NSLog and CLSLog. - * If the DEBUG preprocessor macro is not defined CLS_LOG uses CLSLog only. - * - * Example output: - * -[AppDelegate login:] line 134 $ login start - * - * If you would like to change this macro, create a new header file, unset our define and then define - * your own version. Make sure this new header file is imported after the Crashlytics header file. - * - * #undef CLS_LOG - * #define CLS_LOG(__FORMAT__, ...) CLSNSLog... - * - **/ -#ifdef DEBUG -#define CLS_LOG(__FORMAT__, ...) CLSNSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) -#else -#define CLS_LOG(__FORMAT__, ...) CLSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) -#endif - -/** - * - * Add logging that will be sent with your crash data. This logging will not show up in the system.log - * and will only be visible in your Crashlytics dashboard. - * - **/ -OBJC_EXTERN void CLSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2); -OBJC_EXTERN void CLSLogv(NSString *format, va_list args) NS_FORMAT_FUNCTION(1,0); - -/** - * - * Add logging that will be sent with your crash data. This logging will show up in the system.log - * and your Crashlytics dashboard. It is not recommended for Release builds. - * - **/ -OBJC_EXTERN void CLSNSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2); -OBJC_EXTERN void CLSNSLogv(NSString *format, va_list args) NS_FORMAT_FUNCTION(1,0); - - -@protocol CrashlyticsDelegate; - -@interface Crashlytics : NSObject - -@property (nonatomic, readonly, copy) NSString *apiKey; -@property (nonatomic, readonly, copy) NSString *version; -@property (nonatomic, assign) BOOL debugMode; - -@property (nonatomic, assign) NSObject *delegate; - -/** - * - * The recommended way to install Crashlytics into your application is to place a call - * to +startWithAPIKey: in your -application:didFinishLaunchingWithOptions: method. - * - * This delay defaults to 1 second in order to generally give the application time to - * fully finish launching. - * - **/ -+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey; -+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey afterDelay:(NSTimeInterval)delay; - -/** - * - * If you need the functionality provided by the CrashlyticsDelegate protocol, you can use - * these convenience methods to activate the framework and set the delegate in one call. - * - **/ -+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey delegate:(NSObject *)delegate; -+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey delegate:(NSObject *)delegate afterDelay:(NSTimeInterval)delay; - -/** - * - * Access the singleton Crashlytics instance. - * - **/ -+ (Crashlytics *)sharedInstance; - -/** - * - * The easiest way to cause a crash - great for testing! - * - **/ -- (void)crash; - -/** - * - * Many of our customers have requested the ability to tie crashes to specific end-users of their - * application in order to facilitate responses to support requests or permit the ability to reach - * out for more information. We allow you to specify up to three separate values for display within - * the Crashlytics UI - but please be mindful of your end-user's privacy. - * - * We recommend specifying a user identifier - an arbitrary string that ties an end-user to a record - * in your system. This could be a database id, hash, or other value that is meaningless to a - * third-party observer but can be indexed and queried by you. - * - * Optionally, you may also specify the end-user's name or username, as well as email address if you - * do not have a system that works well with obscured identifiers. - * - * Pursuant to our EULA, this data is transferred securely throughout our system and we will not - * disseminate end-user data unless required to by law. That said, if you choose to provide end-user - * contact information, we strongly recommend that you disclose this in your application's privacy - * policy. Data privacy is of our utmost concern. - * - **/ -- (void)setUserIdentifier:(NSString *)identifier; -- (void)setUserName:(NSString *)name; -- (void)setUserEmail:(NSString *)email; - -+ (void)setUserIdentifier:(NSString *)identifier; -+ (void)setUserName:(NSString *)name; -+ (void)setUserEmail:(NSString *)email; - -/** - * - * Set a value for a key to be associated with your crash data. - * - **/ -- (void)setObjectValue:(id)value forKey:(NSString *)key; -- (void)setIntValue:(int)value forKey:(NSString *)key; -- (void)setBoolValue:(BOOL)value forKey:(NSString *)key; -- (void)setFloatValue:(float)value forKey:(NSString *)key; - -+ (void)setObjectValue:(id)value forKey:(NSString *)key; -+ (void)setIntValue:(int)value forKey:(NSString *)key; -+ (void)setBoolValue:(BOOL)value forKey:(NSString *)key; -+ (void)setFloatValue:(float)value forKey:(NSString *)key; - -@end - -/** - * The CLSCrashReport protocol exposes methods that you can call on crash report objects passed - * to delegate methods. If you want these values or the entire object to stay in memory retain - * them or copy them. - **/ -@protocol CLSCrashReport -@required - -/** - * Returns the session identifier for the crash report. - **/ -@property (nonatomic, readonly) NSString *identifier; - -/** - * Returns the custom key value data for the crash report. - **/ -@property (nonatomic, readonly) NSDictionary *customKeys; - -/** - * Returns the CFBundleVersion of the application that crashed. - **/ -@property (nonatomic, readonly) NSString *bundleVersion; - -/** - * Returns the CFBundleShortVersionString of the application that crashed. - **/ -@property (nonatomic, readonly) NSString *bundleShortVersionString; - -/** - * Returns the date that the application crashed at. - **/ -@property (nonatomic, readonly) NSDate *crashedOnDate; - -/** - * Returns the os version that the application crashed on. - **/ -@property (nonatomic, readonly) NSString *OSVersion; - -/** - * Returns the os build version that the application crashed on. - **/ -@property (nonatomic, readonly) NSString *OSBuildVersion; - -@end - -/** - * - * The CrashlyticsDelegate protocol provides a mechanism for your application to take - * action on events that occur in the Crashlytics crash reporting system. You can make - * use of these calls by assigning an object to the Crashlytics' delegate property directly, - * or through the convenience startWithAPIKey:delegate:... methods. - * - **/ -@protocol CrashlyticsDelegate -@optional - -/** - * - * Called once a Crashlytics instance has determined that the last execution of the - * application ended in a crash. This is called some time after the crash reporting - * process has begun. If you have specified a delay in one of the - * startWithAPIKey:... calls, this will take at least that long to be invoked. - * - **/ -- (void)crashlyticsDidDetectCrashDuringPreviousExecution:(Crashlytics *)crashlytics; - -/** - * - * Just like crashlyticsDidDetectCrashDuringPreviousExecution this delegate method is - * called once a Crashlytics instance has determined that the last execution of the - * application ended in a crash. A CLSCrashReport is passed back that contains data about - * the last crash report that was generated. See the CLSCrashReport protocol for method details. - * This method is called after crashlyticsDidDetectCrashDuringPreviousExecution. - * - **/ -- (void)crashlytics:(Crashlytics *)crashlytics didDetectCrashDuringPreviousExecution:(id )crash; - -@end - -/** - * `CrashlyticsKit` can be used as a parameter to `[Fabric with:@[CrashlyticsKit]];` in Objective-C. In Swift, simply use `Crashlytics()` - */ -#define CrashlyticsKit [Crashlytics sharedInstance] diff --git a/Yep/Crashlytics.framework/Versions/A/Resources/Info.plist b/Yep/Crashlytics.framework/Versions/A/Resources/Info.plist deleted file mode 100644 index ead462e6..00000000 --- a/Yep/Crashlytics.framework/Versions/A/Resources/Info.plist +++ /dev/null @@ -1,30 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - Crashlytics - CFBundleIdentifier - com.crashlytics.ios - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - Crashlytics - CFBundlePackageType - FMWK - CFBundleShortVersionString - 2.2.10 - CFBundleSupportedPlatforms - - iPhoneOS - - CFBundleVersion - 45 - DTPlatformName - iphoneos - MinimumOSVersion - 4.0 - - diff --git a/Yep/Crashlytics.framework/Versions/Current b/Yep/Crashlytics.framework/Versions/Current deleted file mode 120000 index 8c7e5a66..00000000 --- a/Yep/Crashlytics.framework/Versions/Current +++ /dev/null @@ -1 +0,0 @@ -A \ No newline at end of file diff --git a/Yep/Crashlytics.framework/run b/Yep/Crashlytics.framework/run deleted file mode 100755 index c79b0288..00000000 Binary files a/Yep/Crashlytics.framework/run and /dev/null differ diff --git a/Yep/Crashlytics.framework/submit b/Yep/Crashlytics.framework/submit deleted file mode 100755 index 65568e31..00000000 Binary files a/Yep/Crashlytics.framework/submit and /dev/null differ diff --git a/Yep/Extensions/Double+Yep.swift b/Yep/Extensions/Double+Yep.swift new file mode 100644 index 00000000..96d19f06 --- /dev/null +++ b/Yep/Extensions/Double+Yep.swift @@ -0,0 +1,15 @@ +// +// Double+Yep.swift +// Yep +// +// Created by kevinzhow on 15/5/22. +// Copyright (c) 2015年 Catch Inc. All rights reserved. +// + +import Foundation + +extension Double { + func format(f: String) -> String { + return NSString(format: "%\(f)f", self) as String + } +} \ No newline at end of file diff --git a/Yep/Extensions/NSFileManager+Yep.swift b/Yep/Extensions/NSFileManager+Yep.swift index 287b68d5..e8b153a8 100644 --- a/Yep/Extensions/NSFileManager+Yep.swift +++ b/Yep/Extensions/NSFileManager+Yep.swift @@ -130,4 +130,28 @@ extension NSFileManager { return nil } + // MARK: Clean Caches + + class func cleanCachesDirectoryAtURL(cachesDirectoryURL: NSURL) { + let fileManager = NSFileManager.defaultManager() + + if let fileURLs = fileManager.contentsOfDirectoryAtURL(cachesDirectoryURL, includingPropertiesForKeys: nil, options: NSDirectoryEnumerationOptions.allZeros, error: nil) as? [NSURL] { + for fileURL in fileURLs { + fileManager.removeItemAtURL(fileURL, error: nil) + } + } + } + + class func cleanAvatarCaches() { + if let avatarCachesURL = yepAvatarCachesURL() { + cleanCachesDirectoryAtURL(avatarCachesURL) + } + } + + class func cleanMessageCaches() { + if let messageCachesURL = yepMessageCachesURL() { + cleanCachesDirectoryAtURL(messageCachesURL) + } + } + } diff --git a/Yep/Extensions/UIColor+Yep.swift b/Yep/Extensions/UIColor+Yep.swift index bdc67118..723a471d 100644 --- a/Yep/Extensions/UIColor+Yep.swift +++ b/Yep/Extensions/UIColor+Yep.swift @@ -52,5 +52,9 @@ extension UIColor { class func yepDisabledColor() -> UIColor { return UIColor(red:0.95, green:0.95, blue:0.95, alpha:1) } + + class func yepGrayColor() -> UIColor { + return UIColor(red: 142.0/255.0, green: 142.0/255.0, blue: 147.0/255.0, alpha: 1.0) + } } diff --git a/Yep/Extensions/UIFont+Yep.swift b/Yep/Extensions/UIFont+Yep.swift index 6fd1eb22..55c15622 100644 --- a/Yep/Extensions/UIFont+Yep.swift +++ b/Yep/Extensions/UIFont+Yep.swift @@ -14,22 +14,26 @@ extension UIFont { } class func skillTextFont() -> UIFont { - return UIFont(name: "HelveticaNeue-Light", size: 14)! + return UIFont(name: "Helvetica-Light", size: 14)! } class func skillTextLargeFont() -> UIFont { - return UIFont(name: "HelveticaNeue-Light", size: 20)! + return UIFont(name: "Helvetica-Light", size: 20)! } class func skillHomeTextLargeFont() -> UIFont { - return UIFont(name: "HelveticaNeue-Light", size: 18)! + return UIFont(name: "Helvetica-Light", size: 18)! } class func skillHomeButtonFont() -> UIFont { - return UIFont(name: "HelveticaNeue-Light", size: 16)! + return UIFont(name: "Helvetica-Light", size: 16)! + } + + class func barButtonFont() -> UIFont { + return UIFont(name: "Helvetica-Light", size: 14)! } class func navigationBarTitleFont() -> UIFont { - return UIFont(name: "HelveticaNeue-CondensedBlack", size: 20)! + return UIFont(name: "Helvetica-Bold", size: 15)! } } diff --git a/Yep/Helpers/YepAlert.swift b/Yep/Helpers/YepAlert.swift index 049ac86b..26d3f43f 100644 --- a/Yep/Helpers/YepAlert.swift +++ b/Yep/Helpers/YepAlert.swift @@ -50,4 +50,21 @@ class YepAlert { viewController.presentViewController(alertController, animated: true, completion: nil) } + + class func confirmOrCancel(#title: String, message: String, confirmTitle: String, cancelTitle: String, inViewController viewController: UIViewController, withConfirmAction confirmAction: () -> Void, cancelAction: () -> Void) { + let alertController = UIAlertController(title: title, message: message, preferredStyle: .Alert) + + let confirmAction: UIAlertAction = UIAlertAction(title: confirmTitle, style: .Default) { action -> Void in + confirmAction() + } + alertController.addAction(confirmAction) + + let cancelAction: UIAlertAction = UIAlertAction(title: cancelTitle, style: .Cancel) { action -> Void in + cancelAction() + } + alertController.addAction(cancelAction) + + viewController.presentViewController(alertController, animated: true, completion: nil) + } + } \ No newline at end of file diff --git a/Yep/Helpers/YepUserDefaults.swift b/Yep/Helpers/YepUserDefaults.swift index a66c89fb..99d2c1a4 100644 --- a/Yep/Helpers/YepUserDefaults.swift +++ b/Yep/Helpers/YepUserDefaults.swift @@ -16,6 +16,9 @@ let introductionKey = "introduction" let avatarURLStringKey = "avatarURLString" let pusherIDKey = "pusherID" +let areaCodeKey = "areaCode" +let mobileKey = "mobile" + struct Listener: Hashable { let name: String @@ -70,15 +73,22 @@ class YepUserDefaults { // MARK: ReLogin - class func userNeedRelogin() { + class func cleanAll() { let defaults = NSUserDefaults.standardUserDefaults() defaults.removeObjectForKey(v1AccessTokenKey) defaults.removeObjectForKey(userIDKey) defaults.removeObjectForKey(nicknameKey) + defaults.removeObjectForKey(introductionKey) defaults.removeObjectForKey(avatarURLStringKey) defaults.removeObjectForKey(pusherIDKey) + defaults.removeObjectForKey(areaCodeKey) + defaults.removeObjectForKey(mobileKey) + } + + class func userNeedRelogin() { + cleanAll() if let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate { if let rootViewController = appDelegate.window?.rootViewController { @@ -199,6 +209,24 @@ class YepUserDefaults { } }() + static var areaCode: Listenable = { + let defaults = NSUserDefaults.standardUserDefaults() + let areaCode = defaults.stringForKey(areaCodeKey) + + return Listenable(areaCode) { areaCode in + defaults.setObject(areaCode, forKey: areaCodeKey) + } + }() + + static var mobile: Listenable = { + let defaults = NSUserDefaults.standardUserDefaults() + let mobile = defaults.stringForKey(mobileKey) + + return Listenable(mobile) { mobile in + defaults.setObject(mobile, forKey: mobileKey) + } + }() + } diff --git a/Yep/Images.xcassets/icon_back.imageset/Contents.json b/Yep/Images.xcassets/icon_back.imageset/Contents.json new file mode 100644 index 00000000..e12103d0 --- /dev/null +++ b/Yep/Images.xcassets/icon_back.imageset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "icon_back.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode", + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/Yep/Images.xcassets/icon_back.imageset/icon_back.pdf b/Yep/Images.xcassets/icon_back.imageset/icon_back.pdf new file mode 100644 index 00000000..f6c77833 Binary files /dev/null and b/Yep/Images.xcassets/icon_back.imageset/icon_back.pdf differ diff --git a/Yep/Images.xcassets/icon_chat_active_unread.imageset/Contents.json b/Yep/Images.xcassets/icon_chat_active_unread.imageset/Contents.json new file mode 100644 index 00000000..4ecabbbc --- /dev/null +++ b/Yep/Images.xcassets/icon_chat_active_unread.imageset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "icon_chat_active_unread.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode", + "template-rendering-intent" : "original" + } +} \ No newline at end of file diff --git a/Yep/Images.xcassets/icon_chat_active_unread.imageset/icon_chat_active_unread.pdf b/Yep/Images.xcassets/icon_chat_active_unread.imageset/icon_chat_active_unread.pdf new file mode 100644 index 00000000..5e5c393f Binary files /dev/null and b/Yep/Images.xcassets/icon_chat_active_unread.imageset/icon_chat_active_unread.pdf differ diff --git a/Yep/Images.xcassets/icon_chat_unread.imageset/Contents.json b/Yep/Images.xcassets/icon_chat_unread.imageset/Contents.json new file mode 100644 index 00000000..2479c709 --- /dev/null +++ b/Yep/Images.xcassets/icon_chat_unread.imageset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "icon_chat_unread.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode", + "template-rendering-intent" : "original" + } +} \ No newline at end of file diff --git a/Yep/Images.xcassets/icon_chat_unread.imageset/icon_chat_unread.pdf b/Yep/Images.xcassets/icon_chat_unread.imageset/icon_chat_unread.pdf new file mode 100644 index 00000000..fe73911b Binary files /dev/null and b/Yep/Images.xcassets/icon_chat_unread.imageset/icon_chat_unread.pdf differ diff --git a/Yep/Images.xcassets/swipe_up.imageset/Contents.json b/Yep/Images.xcassets/swipe_up.imageset/Contents.json new file mode 100644 index 00000000..344f4182 --- /dev/null +++ b/Yep/Images.xcassets/swipe_up.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "swipe_up.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Yep/Images.xcassets/swipe_up.imageset/swipe_up.pdf b/Yep/Images.xcassets/swipe_up.imageset/swipe_up.pdf new file mode 100644 index 00000000..eb39d765 Binary files /dev/null and b/Yep/Images.xcassets/swipe_up.imageset/swipe_up.pdf differ diff --git a/Yep/Images.xcassets/unread_red_dot.imageset/Contents.json b/Yep/Images.xcassets/unread_red_dot.imageset/Contents.json new file mode 100644 index 00000000..f45f6a9e --- /dev/null +++ b/Yep/Images.xcassets/unread_red_dot.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "unread_red_dot.pdf", + "resizing" : { + "mode" : "3-part-horizontal", + "center" : { + "mode" : "fill", + "width" : 1 + }, + "capInsets" : { + "right" : 7, + "left" : 7 + } + } + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Yep/Images.xcassets/unread_red_dot.imageset/unread_red_dot.pdf b/Yep/Images.xcassets/unread_red_dot.imageset/unread_red_dot.pdf new file mode 100644 index 00000000..39a44057 Binary files /dev/null and b/Yep/Images.xcassets/unread_red_dot.imageset/unread_red_dot.pdf differ diff --git a/Yep/Info.plist b/Yep/Info.plist index bd7d3d24..0f9d6c8d 100644 --- a/Yep/Info.plist +++ b/Yep/Info.plist @@ -19,7 +19,21 @@ CFBundleSignature ???? CFBundleVersion - 16 + 26 + Fabric + + APIKey + 3030ba006e21bcf8eb4a2127b6a7931ea6667486 + Kits + + + KitInfo + + KitName + Crashlytics + + + LSRequiresIPhoneOS NSLocationWhenInUseUsageDescription diff --git a/Yep/Realm/Models.swift b/Yep/Realm/Models.swift index cb576cf6..679e9b2a 100644 --- a/Yep/Realm/Models.swift +++ b/Yep/Realm/Models.swift @@ -210,6 +210,12 @@ class Message: Object { dynamic var conversation: Conversation? } +class Draft: Object { + dynamic var messageToolbarState: Int = MessageToolbarState.Default.rawValue + + dynamic var text: String = "" +} + // MARK: Conversation enum ConversationType: Int { @@ -224,6 +230,8 @@ class Conversation: Object { dynamic var withFriend: User? dynamic var withGroup: Group? + dynamic var draft: Draft? + var messages: [Message] { return linkingObjects(Message.self, forProperty: "conversation") } @@ -259,6 +267,21 @@ func groupWithGroupID(groupID: String, inRealm realm: Realm) -> Group? { return realm.objects(Group).filter(predicate).first } +func countOfUnreadMessagesInRealm(realm: Realm) -> Int { + let predicate = NSPredicate(format: "readed = false AND fromFriend.friendState != %d", UserFriendState.Me.rawValue) + return realm.objects(Message).filter(predicate).count +} + +func countOfUnreadMessagesInConversation(conversation: Conversation) -> Int { + return conversation.messages.filter({ message in + if let fromFriend = message.fromFriend { + return (message.readed == false) && (fromFriend.friendState != UserFriendState.Me.rawValue) + } else { + return false + } + }).count +} + func messageWithMessageID(messageID: String, inRealm realm: Realm) -> Message? { if messageID.isEmpty { return nil diff --git a/Yep/Services/FayeService.swift b/Yep/Services/FayeService.swift index 55318533..de01ba1a 100644 --- a/Yep/Services/FayeService.swift +++ b/Yep/Services/FayeService.swift @@ -73,50 +73,34 @@ class FayeService: NSObject, MZFayeClientDelegate { client.subscribeToChannel(personalChannel, usingBlock: { data in // println("subscribeToChannel: \(data)") - let messageInfo = data as! JSONDictionary + if let + messageInfo = data as? JSONDictionary, + messageType = messageInfo["message_type"] as? String { - if let messageType = messageInfo["message_type"] as? String { + switch messageType { - switch messageType { - - case FayeService.MessageType.Default.rawValue: - if let messageDataInfo = messageInfo["message"] as? JSONDictionary { - self.saveMessageWithMessageInfo(messageDataInfo) - } - - case FayeService.MessageType.Instant.rawValue: - if let messageDataInfo = messageInfo["message"] as? JSONDictionary { - - if let - user = messageDataInfo["user"] as? JSONDictionary, - userID = user["id"] as? String, - state = messageDataInfo["state"] as? Int { - - var instantStateType = InstantStateType.Text - - switch state { - - case InstantStateType.Text.rawValue: - instantStateType = .Text - - case InstantStateType.Audio.rawValue: - instantStateType = .Audio - - // TODO: more InstantStateType - - default: - break - } - - self.delegate?.fayeRecievedInstantStateType(instantStateType, userID: userID) + case FayeService.MessageType.Default.rawValue: + if let messageDataInfo = messageInfo["message"] as? JSONDictionary { + self.saveMessageWithMessageInfo(messageDataInfo) } + + case FayeService.MessageType.Instant.rawValue: + if let messageDataInfo = messageInfo["message"] as? JSONDictionary { + + if let + user = messageDataInfo["user"] as? JSONDictionary, + userID = user["id"] as? String, + state = messageDataInfo["state"] as? Int { + + if let instantStateType = InstantStateType(rawValue: state) { + self.delegate?.fayeRecievedInstantStateType(instantStateType, userID: userID) + } + } + } + + default: + println("Recieved unknow message type") } - - - default: - println("Recieved unknow message type") - - } } }) diff --git a/Yep/Services/YepLocationService.swift b/Yep/Services/YepLocationService.swift index 8d0cf624..1fab58b8 100644 --- a/Yep/Services/YepLocationService.swift +++ b/Yep/Services/YepLocationService.swift @@ -17,6 +17,8 @@ class YepLocationService: NSObject, CLLocationManagerDelegate { var address:String? var geocoder = CLGeocoder() + var userLocationUpdated = false + override init() { super.init() locationManager.delegate = self @@ -34,6 +36,18 @@ class YepLocationService: NSObject, CLLocationManagerDelegate { func locationManager(manager: CLLocationManager!, didUpdateToLocation newLocation: CLLocation!, fromLocation oldLocation: CLLocation!) { + if !userLocationUpdated { + updateMyselfWithInfo(["latitude": newLocation.coordinate.latitude, "longitude": newLocation.coordinate.longitude], failureHandler: { (reason, errorMessage) in + + defaultFailureHandler(reason, errorMessage) + + }, completion: { success in + if success { + self.userLocationUpdated = true + } + }) + } + geocoder.reverseGeocodeLocation(newLocation, completionHandler: { (placemarks, error) in if (error != nil) {println("reverse geodcode fail: \(error.localizedDescription)")} diff --git a/Yep/Services/YepService.swift b/Yep/Services/YepService.swift index ed80b473..0d240e54 100644 --- a/Yep/Services/YepService.swift +++ b/Yep/Services/YepService.swift @@ -225,7 +225,7 @@ func skillsFromSkillsData(skillsData: [JSONDictionary]) -> [Skill] { let coverURLString = skillInfo["cover_url"] as? String - let skill = Skill(category: skillCategory, id: skillID, name: skillName, localName: skillName, coverURLString: coverURLString) + let skill = Skill(category: skillCategory, id: skillID, name: skillName, localName: skillLocalName, coverURLString: coverURLString) skills.append(skill) } @@ -361,54 +361,30 @@ func updateMyselfWithInfo(info: JSONDictionary, #failureHandler: ((Reason, Strin } } -func sendVerifyCode(ofMobile mobile: String, withAreaCode areaCode: String, #failureHandler: ((Reason, String?) -> Void)?, #completion: Bool -> Void) { - - let requestParameters = [ - "mobile": mobile, - "phone_code": areaCode, - ] - - let parse: JSONDictionary -> Bool? = { data in - if let status = data["status"] as? String { - if status == "sms sent" { - return true - } - } - - return false - } - - let resource = jsonResource(path: "/api/v1/auth/send_verify_code", method: .POST, requestParameters: requestParameters, parse: parse) - - if let failureHandler = failureHandler { - apiRequest({_ in}, baseURL, resource, failureHandler, completion) - } else { - apiRequest({_ in}, baseURL, resource, defaultFailureHandler, completion) - } +enum VerifyCodeMethod: String { + case SMS = "sms" + case Call = "call" } -func resendVoiceVerifyCode(ofMobile mobile: String, withAreaCode areaCode: String, #failureHandler: ((Reason, String?) -> Void)?, #completion: Bool -> Void) { +func sendVerifyCodeOfMobile(mobile: String, withAreaCode areaCode: String, useMethod method: VerifyCodeMethod, #failureHandler: ((Reason, String?) -> Void)?, #completion: Bool -> Void) { + let requestParameters = [ "mobile": mobile, "phone_code": areaCode, + "method": method.rawValue ] let parse: JSONDictionary -> Bool? = { data in - if let status = data["state"] as? String { - return true - } - - return false + return true } - let resource = jsonResource(path: "/api/v1/registration/resend_verify_code_by_voice", method: .POST, requestParameters: requestParameters, parse: parse) + let resource = jsonResource(path: "/api/v1/sms_verification_codes", method: .POST, requestParameters: requestParameters, parse: parse) if let failureHandler = failureHandler { apiRequest({_ in}, baseURL, resource, failureHandler, completion) } else { apiRequest({_ in}, baseURL, resource, defaultFailureHandler, completion) } - } func loginByMobile(mobile: String, withAreaCode areaCode: String, #verifyCode: String, #failureHandler: ((Reason, String?) -> Void)?, #completion: LoginUser -> Void) { @@ -512,6 +488,18 @@ private func moreFriendships(inPage page: Int, withPerPage perPage: Int, #failur enum DiscoveredUserSortStyle: String { case Distance = "distance" case LastSignIn = "last_sign_in_at" + case Default = "default" + + var name: String { + switch self { + case .Distance: + return NSLocalizedString("Nearby", comment: "") + case .LastSignIn: + return NSLocalizedString("Time", comment: "") + case .Default: + return NSLocalizedString("Default", comment: "") + } + } } struct DiscoveredUser { @@ -539,6 +527,57 @@ struct DiscoveredUser { let socialAccountProviders: [SocialAccountProvider] } +let parseDiscoveredUsers: JSONDictionary -> [DiscoveredUser]? = { data in + + println("discoverUsers: \(data)") + + if let usersData = data["users"] as? [JSONDictionary] { + + var discoveredUsers = [DiscoveredUser]() + + for userInfo in usersData { + if let + id = userInfo["id"] as? String, + nickname = userInfo["nickname"] as? String, + avatarURLString = userInfo["avatar_url"] as? String, + createdAtString = userInfo["created_at"] as? String, + lastSignInAtString = userInfo["last_sign_in_at"] as? String, + longitude = userInfo["longitude"] as? Double, + latitude = userInfo["latitude"] as? Double, + distance = userInfo["distance"] as? Double, + masterSkillsData = userInfo["master_skills"] as? [JSONDictionary], + learningSkillsData = userInfo["learning_skills"] as? [JSONDictionary], + socialAccountProvidersInfo = userInfo["providers"] as? [String: Bool] { + + let createdAt = NSDate.dateWithISO08601String(createdAtString) + let lastSignInAt = NSDate.dateWithISO08601String(lastSignInAtString) + + let masterSkills = skillsFromSkillsData(masterSkillsData) + let learningSkills = skillsFromSkillsData(learningSkillsData) + + var socialAccountProviders = Array() + + for (name, enabled) in socialAccountProvidersInfo { + let provider = DiscoveredUser.SocialAccountProvider(name: name, enabled: enabled) + + socialAccountProviders.append(provider) + } + + let introduction = userInfo["introduction"] as? String + + let discoverUser = DiscoveredUser(id: id, nickname: nickname, introduction: introduction, avatarURLString: avatarURLString, createdAt: createdAt, lastSignInAt: lastSignInAt, longitude: longitude, latitude: latitude, distance: distance, masterSkills: masterSkills, learningSkills: learningSkills, socialAccountProviders: socialAccountProviders) + + discoveredUsers.append(discoverUser) + } + } + + return discoveredUsers + } + + return nil +} + + func discoverUsers(#masterSkills: [String], #learningSkills: [String], #discoveredUserSortStyle: DiscoveredUserSortStyle, #failureHandler: ((Reason, String?) -> Void)?, #completion: [DiscoveredUser] -> Void) { let requestParameters:[String: AnyObject] = [ @@ -547,55 +586,7 @@ func discoverUsers(#masterSkills: [String], #learningSkills: [String], #discover "sort": discoveredUserSortStyle.rawValue ] - let parse: JSONDictionary -> [DiscoveredUser]? = { data in - - //println("discoverUsers: \(data)") - - if let usersData = data["users"] as? [JSONDictionary] { - - var discoveredUsers = [DiscoveredUser]() - - for userInfo in usersData { - if let - id = userInfo["id"] as? String, - nickname = userInfo["nickname"] as? String, - avatarURLString = userInfo["avatar_url"] as? String, - createdAtString = userInfo["created_at"] as? String, - lastSignInAtString = userInfo["last_sign_in_at"] as? String, - longitude = userInfo["longitude"] as? Double, - latitude = userInfo["latitude"] as? Double, - distance = userInfo["distance"] as? Double, - masterSkillsData = userInfo["master_skills"] as? [JSONDictionary], - learningSkillsData = userInfo["learning_skills"] as? [JSONDictionary], - socialAccountProvidersInfo = userInfo["providers"] as? [String: Bool] { - - let createdAt = NSDate.dateWithISO08601String(createdAtString) - let lastSignInAt = NSDate.dateWithISO08601String(lastSignInAtString) - - let masterSkills = skillsFromSkillsData(masterSkillsData) - let learningSkills = skillsFromSkillsData(learningSkillsData) - - var socialAccountProviders = Array() - - for (name, enabled) in socialAccountProvidersInfo { - let provider = DiscoveredUser.SocialAccountProvider(name: name, enabled: enabled) - - socialAccountProviders.append(provider) - } - - let introduction = userInfo["introduction"] as? String - - let discoverUser = DiscoveredUser(id: id, nickname: nickname, introduction: introduction, avatarURLString: avatarURLString, createdAt: createdAt, lastSignInAt: lastSignInAt, longitude: longitude, latitude: latitude, distance: distance, masterSkills: masterSkills, learningSkills: learningSkills, socialAccountProviders: socialAccountProviders) - - discoveredUsers.append(discoverUser) - } - } - - return discoveredUsers - } - - return nil - } + let parse = parseDiscoveredUsers let resource = authJsonResource(path: "/api/v1/user/discover", method: .GET, requestParameters: requestParameters as JSONDictionary, parse: parse) @@ -606,6 +597,23 @@ func discoverUsers(#masterSkills: [String], #learningSkills: [String], #discover } } +func searchUsersByQ(q: String, #failureHandler: ((Reason, String?) -> Void)?, #completion: [DiscoveredUser] -> Void) { + + let requestParameters = [ + "q": q + ] + + let parse = parseDiscoveredUsers + + let resource = authJsonResource(path: "/api/v1/users/search", method: .GET, requestParameters: requestParameters, parse: parse) + + if let failureHandler = failureHandler { + apiRequest({_ in}, baseURL, resource, failureHandler, completion) + } else { + apiRequest({_ in}, baseURL, resource, defaultFailureHandler, completion) + } +} + func friendships(#completion: [JSONDictionary] -> Void) { headFriendships { result in @@ -835,6 +843,7 @@ func createMessageWithMessageInfo(messageInfo: JSONDictionary, #failureHandler: FayeService.sharedManager.sendGroupMessage(messageInfo, circleID: recipientID, completion: { (success, messageID) in if success, let messageID = messageID { + completion(messageID: messageID) } else { @@ -850,14 +859,21 @@ func createMessageWithMessageInfo(messageInfo: JSONDictionary, #failureHandler: FayeService.sharedManager.sendPrivateMessage(messageInfo, messageType: .Default, userID: recipientID, completion: { (success, messageID) in if success, let messageID = messageID { + + println("Mesasge id is \(messageID)") completion(messageID: messageID) } else { - if let failureHandler = failureHandler { - failureHandler(Reason.CouldNotParseJSON, "Faye Created Message Error") + if success { + println("Mesasgeing packge without message id") } else { - defaultFailureHandler(Reason.CouldNotParseJSON, "Faye Created Message Error") + if let failureHandler = failureHandler { + failureHandler(Reason.CouldNotParseJSON, "Faye Created Message Error") + } else { + defaultFailureHandler(Reason.CouldNotParseJSON, "Faye Created Message Error") + } } + } }) diff --git a/Yep/Services/YepServiceSync.swift b/Yep/Services/YepServiceSync.swift index 44d4c600..02df428b 100644 --- a/Yep/Services/YepServiceSync.swift +++ b/Yep/Services/YepServiceSync.swift @@ -15,24 +15,22 @@ let YepNewMessagesReceivedNotification = "YepNewMessagesReceivedNotification" func downloadAttachmentOfMessage(message: Message) { - func updateAttachmentOfMessage(message: Message, withAttachmentFileName attachmentFileName: String) { - if let realm = message.realm { - realm.beginWrite() + func updateAttachmentOfMessage(message: Message, withAttachmentFileName attachmentFileName: String, inRealm realm: Realm) { + realm.write { message.localAttachmentName = attachmentFileName message.downloadState = MessageDownloadState.Downloaded.rawValue - realm.commitWrite() } } - func updateThumbnailOfMessage(message: Message, withThumbnailFileName thumbnailFileName: String) { - if let realm = message.realm { - realm.beginWrite() + func updateThumbnailOfMessage(message: Message, withThumbnailFileName thumbnailFileName: String, inRealm realm: Realm) { + realm.write { message.localThumbnailName = thumbnailFileName - realm.commitWrite() } } + let messageID = message.messageID let attachmentURLString = message.attachmentURLString + let mediaType = message.mediaType if !attachmentURLString.isEmpty && message.downloadState != MessageDownloadState.Downloaded.rawValue { if let url = NSURL(string: attachmentURLString) { @@ -43,24 +41,30 @@ func downloadAttachmentOfMessage(message: Message) { let fileName = NSUUID().UUIDString dispatch_async(dispatch_get_main_queue()) { - switch message.mediaType { - case MessageMediaType.Image.rawValue: - if let fileURL = NSFileManager.saveMessageImageData(data, withName: fileName) { - updateAttachmentOfMessage(message, withAttachmentFileName: fileName) - } - - case MessageMediaType.Video.rawValue: - if let fileURL = NSFileManager.saveMessageVideoData(data, withName: fileName) { - updateAttachmentOfMessage(message, withAttachmentFileName: fileName) - } - - case MessageMediaType.Audio.rawValue: - if let fileURL = NSFileManager.saveMessageAudioData(data, withName: fileName) { - updateAttachmentOfMessage(message, withAttachmentFileName: fileName) - } + let realm = Realm() + + if let message = messageWithMessageID(messageID, inRealm: realm) { - default: - break + switch mediaType { + + case MessageMediaType.Image.rawValue: + if let fileURL = NSFileManager.saveMessageImageData(data, withName: fileName) { + updateAttachmentOfMessage(message, withAttachmentFileName: fileName, inRealm: realm) + } + + case MessageMediaType.Video.rawValue: + if let fileURL = NSFileManager.saveMessageVideoData(data, withName: fileName) { + updateAttachmentOfMessage(message, withAttachmentFileName: fileName, inRealm: realm) + } + + case MessageMediaType.Audio.rawValue: + if let fileURL = NSFileManager.saveMessageAudioData(data, withName: fileName) { + updateAttachmentOfMessage(message, withAttachmentFileName: fileName, inRealm: realm) + } + + default: + break + } } } } @@ -68,12 +72,15 @@ func downloadAttachmentOfMessage(message: Message) { } } - if message.mediaType == MessageMediaType.Video.rawValue { + if mediaType == MessageMediaType.Video.rawValue { let thumbnailURLString = message.thumbnailURLString if !thumbnailURLString.isEmpty && message.localThumbnailName.isEmpty { + if let url = NSURL(string: thumbnailURLString) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { + let data = NSData(contentsOfURL: url) if let data = data { @@ -81,8 +88,12 @@ func downloadAttachmentOfMessage(message: Message) { dispatch_async(dispatch_get_main_queue()) { - if let fileURL = NSFileManager.saveMessageImageData(data, withName: fileName) { - updateThumbnailOfMessage(message, withThumbnailFileName: fileName) + let realm = Realm() + + if let message = messageWithMessageID(messageID, inRealm: realm) { + if let fileURL = NSFileManager.saveMessageImageData(data, withName: fileName) { + updateThumbnailOfMessage(message, withThumbnailFileName: fileName, inRealm: realm) + } } } } @@ -93,6 +104,86 @@ func downloadAttachmentOfMessage(message: Message) { } +func skillsFromUserSkillList(userSkillList: List) -> [Skill] { + + var userSkills = [UserSkill]() + + for userSkill in userSkillList { + userSkills.append(userSkill) + } + + return userSkills.map({ userSkill -> Skill? in + if let category = userSkill.category { + let skillCategory = SkillCategory(id: category.skillCategoryID, name: category.name, localName: category.localName, skills: []) + + let skill = Skill(category: skillCategory, id: userSkill.skillID, name: userSkill.name, localName: userSkill.localName, coverURLString: userSkill.coverURLString) + + return skill + } + + return nil + + }).filter({ $0 != nil }).map({ skill in skill! }) +} + +func userSkillsFromSkills(skills: [Skill], inRealm realm: Realm) -> [UserSkill] { + + return skills.map({ skill -> UserSkill? in + + let skillID = skill.id + var userSkill = userSkillWithSkillID(skillID, inRealm: realm) + + if userSkill == nil { + let newUserSkill = UserSkill() + newUserSkill.skillID = skillID + newUserSkill.name = skillID + newUserSkill.localName = skill.localName + + if let coverURLString = skill.coverURLString { + newUserSkill.coverURLString = coverURLString + } + + realm.add(newUserSkill) + + userSkill = newUserSkill + } + + if let userSkill = userSkill { + if let skillCategory = skill.category, skillCategoryID = skill.category?.id { + var userSkillCategory = userSkillCategoryWithSkillCategoryID(skillCategoryID, inRealm: realm) + + if userSkillCategory == nil { + let newUserSkillCategory = UserSkillCategory() + newUserSkillCategory.skillCategoryID = skillCategoryID + newUserSkillCategory.name = skillCategory.name + newUserSkillCategory.localName = skillCategory.localName + + realm.add(newUserSkillCategory) + + userSkillCategory = newUserSkillCategory + } + + if let userSkillCategory = userSkillCategory { + userSkill.category = userSkillCategory + } + } + } + + return userSkill + + }).filter({ $0 != nil }).map({ skill in skill! }) +} + +func userSocialAccountProvidersFromSocialAccountProviders(socialAccountProviders: [DiscoveredUser.SocialAccountProvider]) -> [UserSocialAccountProvider] { + return socialAccountProviders.map({ _provider -> UserSocialAccountProvider in + let provider = UserSocialAccountProvider() + provider.name = _provider.name + provider.enabled = _provider.enabled + + return provider + }) +} + func userSkillsFromSkillsData(skillsData: [JSONDictionary], inRealm realm: Realm) -> [UserSkill] { var userSkills = [UserSkill]() @@ -153,6 +244,92 @@ func userSkillsFromSkillsData(skillsData: [JSONDictionary], inRealm realm: Realm return userSkills } +func syncMyInfoAndDoFurtherAction(furtherAction: () -> Void) { + + userInfo(failureHandler: nil) { friendInfo in + + println("my userInfo: \(friendInfo)") + + furtherAction() + + if let myUserID = YepUserDefaults.userID.value { + + let realm = Realm() + + var me = userWithUserID(myUserID, inRealm: realm) + + if me == nil { + let newUser = User() + newUser.userID = myUserID + + newUser.friendState = UserFriendState.Me.rawValue + + if let createdAtString = friendInfo["created_at"] as? String { + newUser.createdAt = NSDate.dateWithISO08601String(createdAtString) + } + + realm.beginWrite() + realm.add(newUser) + realm.commitWrite() + + me = newUser + } + + if let user = me { + realm.beginWrite() + + // 更新用户信息 + + if let lastSignInAtString = friendInfo["last_sign_in_at"] as? String { + user.lastSignInAt = NSDate.dateWithISO08601String(lastSignInAtString) + } + + if let nickname = friendInfo["nickname"] as? String { + user.nickname = nickname + } + + if let introduction = friendInfo["introduction"] as? String { + user.introduction = introduction + } + + if let avatarURLString = friendInfo["avatar_url"] as? String { + user.avatarURLString = avatarURLString + } + + // 更新技能 + + if let learningSkillsData = friendInfo["learning_skills"] as? [JSONDictionary] { + user.learningSkills.removeAll() + let userSkills = userSkillsFromSkillsData(learningSkillsData, inRealm: realm) + user.learningSkills.extend(userSkills) + } + + if let masterSkillsData = friendInfo["master_skills"] as? [JSONDictionary] { + user.masterSkills.removeAll() + let userSkills = userSkillsFromSkillsData(masterSkillsData, inRealm: realm) + user.masterSkills.extend(userSkills) + } + + // 更新 Social Account Provider + + user.socialAccountProviders.removeAll() + + if let providersInfo = friendInfo["providers"] as? [String: Bool] { + for (name, enabled) in providersInfo { + let provider = UserSocialAccountProvider() + provider.name = name + provider.enabled = enabled + + user.socialAccountProviders.append(provider) + } + } + + realm.commitWrite() + } + } + } +} + func syncFriendshipsAndDoFurtherAction(furtherAction: () -> Void) { friendships { allFriendships in //println("\n allFriendships: \(allFriendships)") diff --git a/Yep/ViewControllers/AddFriends/AddFriendsViewController.swift b/Yep/ViewControllers/AddFriends/AddFriendsViewController.swift new file mode 100644 index 00000000..802c58bc --- /dev/null +++ b/Yep/ViewControllers/AddFriends/AddFriendsViewController.swift @@ -0,0 +1,138 @@ +// +// AddFriendsViewController.swift +// Yep +// +// Created by NIX on 15/5/19. +// Copyright (c) 2015年 Catch Inc. All rights reserved. +// + +import UIKit + +class AddFriendsViewController: UIViewController { + + @IBOutlet weak var addFriendsTableView: UITableView! + + let addFriendSearchCellIdentifier = "AddFriendSearchCell" + let addFriendMoreCellIdentifier = "AddFriendMoreCell" + + override func viewDidLoad() { + super.viewDidLoad() + + title = NSLocalizedString("Add Friends", comment: "") + + + addFriendsTableView.rowHeight = 60 + + addFriendsTableView.registerNib(UINib(nibName: addFriendSearchCellIdentifier, bundle: nil), forCellReuseIdentifier: addFriendSearchCellIdentifier) + addFriendsTableView.registerNib(UINib(nibName: addFriendMoreCellIdentifier, bundle: nil), forCellReuseIdentifier: addFriendMoreCellIdentifier) + } + + // MARK: Actions + + @IBAction func done(sender: UIBarButtonItem) { + dismissViewControllerAnimated(true, completion: nil) + // TODO: done add friend + } + + // MARK: Navigation + + override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { + if segue.identifier == "showSearchedUsers" { + if let mobile = sender as? String { + let vc = segue.destinationViewController as! SearchedUsersViewController + vc.mobile = mobile + } + } + } +} + +extension AddFriendsViewController: UITableViewDataSource, UITableViewDelegate { + + enum Section: Int { + case Search = 0 + case More + + static var caseCount: Int { + var max: Int = 0 + while let _ = self(rawValue: ++max) {} + return max + } + } + + enum More: Int, Printable { + case Contacts + case FaceToFace + + static var caseCount: Int { + var max: Int = 0 + while let _ = self(rawValue: ++max) {} + return max + } + + var description: String { + switch self { + + case .Contacts: + return NSLocalizedString("Friends in Contacts", comment: "") + + case .FaceToFace: + return NSLocalizedString("Face to Face", comment: "") + } + } + } + + func numberOfSectionsInTableView(tableView: UITableView) -> Int { + return Section.caseCount + } + + func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + switch section { + + case Section.Search.rawValue: + return 1 + + case Section.More.rawValue: + return More.caseCount + + default: + return 0 + } + } + + func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + switch indexPath.section { + + case Section.Search.rawValue: + let cell = tableView.dequeueReusableCellWithIdentifier(addFriendSearchCellIdentifier) as! AddFriendSearchCell + + cell.searchTextField.returnKeyType = .Search + cell.searchTextField.delegate = self + + return cell + + case Section.More.rawValue: + let cell = tableView.dequeueReusableCellWithIdentifier(addFriendMoreCellIdentifier) as! AddFriendMoreCell + + cell.annotationLabel.text = More(rawValue: indexPath.row)?.description + + return cell + + default: + return UITableViewCell() + } + } +} + +extension AddFriendsViewController: UITextFieldDelegate { + func textFieldShouldReturn(textField: UITextField) -> Bool { + + let text = textField.text + + textField.resignFirstResponder() + + performSegueWithIdentifier("showSearchedUsers", sender: text) + + return true + } +} + diff --git a/Yep/ViewControllers/Base/BaseViewController.swift b/Yep/ViewControllers/Base/BaseViewController.swift new file mode 100644 index 00000000..3ff5bd6d --- /dev/null +++ b/Yep/ViewControllers/Base/BaseViewController.swift @@ -0,0 +1,63 @@ +// +// BaseViewController.swift +// Yep +// +// Created by kevinzhow on 15/5/23. +// Copyright (c) 2015年 Catch Inc. All rights reserved. +// + +import UIKit + +class BaseViewController: UIViewController { + + var animatedOnNavigationBar = true + + override func viewDidLoad() { + super.viewDidLoad() + + self.view.backgroundColor = UIColor.whiteColor() + + // Do any additional setup after loading the view. + } + + override func viewWillAppear(animated: Bool) { + super.viewWillAppear(animated) + + if let navigationController = navigationController { + + navigationController.navigationBar.backgroundColor = nil + navigationController.navigationBar.translucent = true + navigationController.navigationBar.shadowImage = nil + navigationController.navigationBar.barStyle = UIBarStyle.Default + navigationController.navigationBar.setBackgroundImage(nil, forBarMetrics: UIBarMetrics.Default) + + let textAttributes = [ + NSForegroundColorAttributeName: UIColor.yepTintColor(), + NSFontAttributeName: UIFont.navigationBarTitleFont() + ] + + navigationController.navigationBar.titleTextAttributes = textAttributes + navigationController.navigationBar.tintColor = nil + } + + if let hidden = self.navigationController?.navigationBarHidden { + + if hidden { + self.navigationController?.setNavigationBarHidden(false, animated: animatedOnNavigationBar) + } + + } + + } + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { + // Get the new view controller using segue.destinationViewController. + // Pass the selected object to the new view controller. + } + */ + +} diff --git a/Yep/ViewControllers/Contacts/ContactsViewController.swift b/Yep/ViewControllers/Contacts/ContactsViewController.swift index d959756c..0dd8405f 100644 --- a/Yep/ViewControllers/Contacts/ContactsViewController.swift +++ b/Yep/ViewControllers/Contacts/ContactsViewController.swift @@ -9,7 +9,7 @@ import UIKit import RealmSwift -class ContactsViewController: UIViewController { +class ContactsViewController: BaseViewController { @IBOutlet weak var contactsTableView: UITableView! @@ -20,6 +20,7 @@ class ContactsViewController: UIViewController { deinit { NSNotificationCenter.defaultCenter().removeObserver(self) } + override func viewDidLoad() { super.viewDidLoad() @@ -40,6 +41,12 @@ class ContactsViewController: UIViewController { contactsTableView.reloadData() } + // MARK: Actions + + @IBAction func presentAddFriends(sender: UIBarButtonItem) { + performSegueWithIdentifier("presentAddFriends", sender: nil) + } + // MARK: Navigation override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { @@ -47,10 +54,14 @@ class ContactsViewController: UIViewController { let vc = segue.destinationViewController as! ProfileViewController if let user = sender as? User { - vc.profileUser = ProfileUser.UserType(user) + if user.userID != YepUserDefaults.userID.value { + vc.profileUser = ProfileUser.UserType(user) + } } vc.hidesBottomBarWhenPushed = true + + vc.setBackButtonWithTitle() } } } @@ -74,7 +85,7 @@ extension ContactsViewController: UITableViewDataSource, UITableViewDelegate { } cell.nameLabel.text = friend.nickname - cell.joinedDateLabel.text = friend.createdAt.timeAgo + cell.joinedDateLabel.text = friend.introduction cell.lastTimeSeenLabel.text = friend.createdAt.timeAgo return cell diff --git a/Yep/ViewControllers/Conversation/ConversationMessagePreviewNavigationControllerDelegate.swift b/Yep/ViewControllers/Conversation/ConversationMessagePreviewNavigationControllerDelegate.swift new file mode 100644 index 00000000..5e161f22 --- /dev/null +++ b/Yep/ViewControllers/Conversation/ConversationMessagePreviewNavigationControllerDelegate.swift @@ -0,0 +1,205 @@ +// +// ConversationMessagePreviewNavigationControllerDelegate.swift +// Yep +// +// Created by NIX on 15/5/25. +// Copyright (c) 2015年 Catch Inc. All rights reserved. +// + +import UIKit + +class ConversationMessagePreviewNavigationControllerDelegate: NSObject, UINavigationControllerDelegate, UIViewControllerAnimatedTransitioning { + + // MARK: UINavigationControllerDelegate + + func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { + + if operation == .Push { + if fromVC.isKindOfClass(ConversationViewController.self) && toVC.isKindOfClass(MessageMediaViewController.self) { + isPresentation = true + + return self + } + + } else if operation == .Pop { + if fromVC.isKindOfClass(MessageMediaViewController.self) && toVC.isKindOfClass(ConversationViewController.self) { + isPresentation = false + + return self + } + } + + return nil + } + + // MARK: UIViewControllerAnimatedTransitioning + + var frame = CGRectZero + var transitionView: UIView? { + didSet { + if let transitionView = transitionView { + transitionViewSnapshot = transitionView.snapshotViewAfterScreenUpdates(false) + } + } + } + var transitionViewSnapshot: UIView? + + var isPresentation = true + + var transitionContext: UIViewControllerContextTransitioning? + + func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval { + return isPresentation ? 0.5 : 0.5 + } + + func animateTransition(transitionContext: UIViewControllerContextTransitioning) { + + self.transitionContext = transitionContext + + if isPresentation { + presentTransition(transitionContext) + } else { + dismissTransition(transitionContext) + } + } + + + let largerOffset: CGFloat = 80 + + func presentTransition(transitionContext: UIViewControllerContextTransitioning) { + let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) as? ConversationsViewController + let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) as? MessageMediaViewController + + let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey) + let toView = transitionContext.viewForKey(UITransitionContextToViewKey) + + let containerView = transitionContext.containerView() + + containerView.addSubview(toView!) + + let animatingVC = toVC! + let animatingView = toView! + + if let transitionViewSnapshot = transitionViewSnapshot { + + animatingView.addSubview(transitionViewSnapshot) + transitionViewSnapshot.frame = frame + + animatingVC.view.backgroundColor = UIColor.clearColor() + animatingVC.mediaView.alpha = 0 + animatingVC.mediaControlView.alpha = 0 + + let fullDuration = transitionDuration(transitionContext) + + UIView.animateKeyframesWithDuration(fullDuration, delay: 0.0, options: .CalculationModeCubic, animations: { () -> Void in + + UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: fullDuration, animations: { () -> Void in + animatingVC.view.backgroundColor = UIColor.blackColor() + }) + + UIView.addKeyframeWithRelativeStartTime(0.2, relativeDuration: 0.5, animations: { () -> Void in + transitionViewSnapshot.center = animatingView.center + }) + + + UIView.addKeyframeWithRelativeStartTime(0.7, relativeDuration: 0.2, animations: { () -> Void in + let targetWidth = animatingView.bounds.width + self.largerOffset + + let dw = targetWidth - transitionViewSnapshot.bounds.width + let ratio = targetWidth / transitionViewSnapshot.bounds.width + let height = ratio * transitionViewSnapshot.bounds.height + let dh = height - transitionViewSnapshot.bounds.height + + let frame = CGRectInset(transitionViewSnapshot.frame, -dw * 0.5, -dh * 0.5) + + transitionViewSnapshot.frame = frame + }) + + UIView.addKeyframeWithRelativeStartTime(0.9, relativeDuration: 0.0, animations: { () -> Void in + let ratio = (animatingView.bounds.width + self.largerOffset) / animatingView.bounds.width + animatingVC.mediaView.transform = CGAffineTransformMakeScale(ratio, ratio) + animatingVC.mediaView.alpha = 1 + animatingVC.mediaControlView.alpha = 1 + + transitionViewSnapshot.alpha = 0 + }) + + UIView.addKeyframeWithRelativeStartTime(0.9, relativeDuration: 0.1, animations: { () -> Void in + animatingVC.mediaView.transform = CGAffineTransformMakeScale(1.0, 1.0) + }) + + }, completion: { (finished) -> Void in + transitionViewSnapshot.removeFromSuperview() + + transitionContext.completeTransition(true) + }) + + } else { + transitionContext.completeTransition(false) + } + } + + func dismissTransition(transitionContext: UIViewControllerContextTransitioning) { + let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) as? MessageMediaViewController + + let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey) + let toView = transitionContext.viewForKey(UITransitionContextToViewKey) + + let containerView = transitionContext.containerView() + + containerView.addSubview(toView!) + containerView.addSubview(fromView!) + + let animatingVC = fromVC! + let animatingView = fromView! + + let fullDuration = transitionDuration(transitionContext) + + if let transitionViewSnapshot = transitionViewSnapshot { + + if let transitionView = self.transitionView { + transitionView.alpha = 0 + } + + UIView.animateKeyframesWithDuration(fullDuration, delay: 0.0, options: .CalculationModeCubic, animations: { () -> Void in + + + UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: fullDuration, animations: { () -> Void in + animatingVC.view.backgroundColor = UIColor.clearColor() + }) + + + UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 0.2, animations: { () -> Void in + let ratio = (animatingView.bounds.width + self.largerOffset) / animatingView.bounds.width + animatingVC.mediaView.transform = CGAffineTransformMakeScale(ratio, ratio) + animatingVC.mediaControlView.alpha = 0 + }) + + UIView.addKeyframeWithRelativeStartTime(0.2, relativeDuration: 0.0, animations: { () -> Void in + animatingView.addSubview(transitionViewSnapshot) + transitionViewSnapshot.center = animatingView.center + transitionViewSnapshot.alpha = 1 + animatingVC.mediaView.alpha = 0 + }) + + UIView.addKeyframeWithRelativeStartTime(0.2, relativeDuration: 0.6, animations: { () -> Void in + transitionViewSnapshot.frame = self.frame + }) + + + }, completion: { (finished) -> Void in + + if let transitionView = self.transitionView { + transitionView.alpha = 1 + } + + transitionViewSnapshot.removeFromSuperview() + + transitionContext.completeTransition(true) + }) + + } else { + transitionContext.completeTransition(false) + } + } +} \ No newline at end of file diff --git a/Yep/ViewControllers/Conversation/ConversationViewController.swift b/Yep/ViewControllers/Conversation/ConversationViewController.swift index ac9d978f..597c10c3 100644 --- a/Yep/ViewControllers/Conversation/ConversationViewController.swift +++ b/Yep/ViewControllers/Conversation/ConversationViewController.swift @@ -11,8 +11,10 @@ import RealmSwift import AVFoundation import MobileCoreServices -class ConversationViewController: UIViewController { +class ConversationViewController: BaseViewController { + @IBOutlet weak var swipeUpView: UIView! + struct Notification { static let MessageSent = "MessageSentNotification" } @@ -46,6 +48,7 @@ class ConversationViewController: UIViewController { }() var messagePreviewTransitionManager: ConversationMessagePreviewTransitionManager? + var navigationControllerDelegate: ConversationMessagePreviewNavigationControllerDelegate? var conversationCollectionViewHasBeenMovedToBottomOnce = false @@ -74,10 +77,15 @@ class ConversationViewController: UIViewController { lazy var titleView: ConversationTitleView = { let titleView = ConversationTitleView(frame: CGRect(origin: CGPointZero, size: CGSize(width: 150, height: 44))) - titleView.nameLabel.text = nameOfConversation(self.conversation) + + let name = nameOfConversation(self.conversation) + + titleView.nameLabel.text = name self.updateStateInfoOfTitleView(titleView) + self.navigationItem.title = name + return titleView }() @@ -160,13 +168,15 @@ class ConversationViewController: UIViewController { NSNotificationCenter.defaultCenter().removeObserver(self) } + override func viewDidLoad() { super.viewDidLoad() - + self.swipeUpView.hidden = true realm = Realm() navigationController?.interactivePopGestureRecognizer.delaysTouchesBegan = false +// navigationController?.interactivePopGestureRecognizer.delegate = self if messages.count >= messagesBunchCount { displayedMessagesRange = NSRange(location: Int(messages.count) - messagesBunchCount, length: messagesBunchCount) @@ -283,8 +293,10 @@ class ConversationViewController: UIViewController { // MARK: Audio Send messageToolbar.voiceSendBeginAction = { messageToolbar in - self.view.window?.addSubview(self.waverView) - + self.view.addSubview(self.waverView) + self.swipeUpView.hidden = false + self.view.bringSubviewToFront(self.swipeUpView) + let audioFileName = NSUUID().UUIDString self.waverView.waver.resetWaveSamples() @@ -304,11 +316,13 @@ class ConversationViewController: UIViewController { } messageToolbar.voiceSendCancelAction = { messageToolbar in + self.swipeUpView.hidden = true self.waverView.removeFromSuperview() YepAudioService.sharedManager.endRecord() } messageToolbar.voiceSendEndAction = { messageToolbar in + self.swipeUpView.hidden = true self.waverView.removeFromSuperview() if YepAudioService.sharedManager.audioRecorder?.currentTime < 0.5 { YepAudioService.sharedManager.endRecord() @@ -406,13 +420,15 @@ class ConversationViewController: UIViewController { // MARK: MessageToolbar State Transitions - messageToolbar.stateTransitionAction = { (previousState, currentState) in + messageToolbar.stateTransitionAction = { (messageToolbar, previousState, currentState) in switch (previousState, currentState) { + case (.MoreMessages, .Default): if !self.isKeyboardVisible { self.adjustBackCollectionViewWithHeight(0, animationDuration: 0.3, animationCurveValue: 7) - }else{ + + } else { self.hideKeyboardAndShowMoreMessageView() } @@ -421,6 +437,30 @@ class ConversationViewController: UIViewController { self.hideKeyboardAndShowMoreMessageView() } } + + + // 尝试保留草稿 + + let realm = Realm() + + if let draft = self.conversation.draft { + realm.write { + draft.messageToolbarState = currentState.rawValue + + if currentState == .TextInputing { + draft.text = messageToolbar.messageTextView.text + } + } + + } else { + let draft = Draft() + draft.messageToolbarState = currentState.rawValue + + realm.write { + self.conversation.draft = draft + } + } + } @@ -455,6 +495,24 @@ class ConversationViewController: UIViewController { addLocationButton.tapAction = { self.performSegueWithIdentifier("presentPickLocation", sender: nil) } + + } + + func prepareTextInputView() { + // 尝试恢复 messageToolbar 的状态 + if let + draft = conversation.draft, + state = MessageToolbarState(rawValue: draft.messageToolbarState) { + + if state == .TextInputing { + messageToolbar.messageTextView.text = draft.text + + // messageToolbar.messageTextView.becomeFirstResponder() + } + + // 这句要放在最后,因为它会触发 + messageToolbar.state = state + } } override func viewDidAppear(animated: Bool) { @@ -477,6 +535,7 @@ class ConversationViewController: UIViewController { }) } } + } override func viewDidDisappear(animated: Bool) { @@ -505,6 +564,7 @@ class ConversationViewController: UIViewController { //以前的方法不能保证边界情况滚到底部 scrollToLastMessage() + prepareTextInputView() } self.waverView.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height - self.messageToolbar.frame.size.height) @@ -853,6 +913,53 @@ class ConversationViewController: UIViewController { } } + func playMessageAudioWithMessage(message: Message?) { + + if let audioPlayer = YepAudioService.sharedManager.audioPlayer { + if let playingMessage = YepAudioService.sharedManager.playingMessage { + if audioPlayer.playing { + + audioPlayer.pause() + + if let playbackTimer = YepAudioService.sharedManager.playbackTimer { + playbackTimer.invalidate() + } + + if let sender = playingMessage.fromFriend, playingMessageIndex = messages.indexOf(playingMessage) { + + let indexPath = NSIndexPath(forItem: playingMessageIndex - displayedMessagesRange.location, inSection: 0) + + if sender.friendState != UserFriendState.Me.rawValue { + if let cell = conversationCollectionView.cellForItemAtIndexPath(indexPath) as? ChatLeftAudioCell { + cell.playing = false + } + + } else { + if let cell = conversationCollectionView.cellForItemAtIndexPath(indexPath) as? ChatRightAudioCell { + cell.playing = false + } + } + } + + if let message = message { + if message.messageID == playingMessage.messageID { + return + } + } + } + } + } + + if let message = message { + let audioPlayedDuration = audioPlayedDurationOfMessage(message) as NSTimeInterval + YepAudioService.sharedManager.playAudioWithMessage(message, beginFromTime: audioPlayedDuration, delegate: self) { + let playbackTimer = NSTimer.scheduledTimerWithTimeInterval(0.02, target: self, selector: "updateAudioPlaybackProgress:", userInfo: nil, repeats: true) + YepAudioService.sharedManager.playbackTimer = playbackTimer + } + } + } + + // MARK: Keyboard func handleKeyboardWillShowNotification(notification: NSNotification) { @@ -944,29 +1051,32 @@ class ConversationViewController: UIViewController { func adjustBackCollectionViewWithHeight(newHeight: CGFloat, animationDuration: NSTimeInterval, animationCurveValue: UInt) { self.conversationCollectionViewContentOffsetBeforeKeyboardWillShow = CGPointZero + if (conversationCollectionViewContentOffsetBeforeKeyboardWillHide == CGPointZero) { conversationCollectionViewContentOffsetBeforeKeyboardWillHide = conversationCollectionView.contentOffset } - UIView.animateWithDuration(animationDuration, delay: 0, options: UIViewAnimationOptions(animationCurveValue << 16), animations: { () -> Void in var contentOffset = self.conversationCollectionViewContentOffsetBeforeKeyboardWillHide + self.messageToolbarBottomConstraint.constant = 0 + if self.messageToolbar.state != .MoreMessages { contentOffset.y -= newHeight - }else { + + } else { contentOffset.y -= (newHeight - self.moreMessageTypesViewHeightConstraintConstant) } //println("\(self.conversationCollectionViewContentOffsetBeforeKeyboardWillHide.y) \(contentOffset.y) \(self.conversationCollectionViewContentOffsetBeforeKeyboardWillHide.y-contentOffset.y)") self.conversationCollectionView.setContentOffset(contentOffset, animated: false) self.conversationCollectionView.contentInset.bottom = CGRectGetHeight(self.messageToolbar.bounds) + self.view.layoutIfNeeded() - }, completion: { (finished) -> Void in - + + }, completion: { (finished) -> Void in }) - } func handleKeyboardDidHideNotification(notification: NSNotification) { @@ -982,13 +1092,97 @@ class ConversationViewController: UIViewController { let vc = segue.destinationViewController as! ProfileViewController if let withFriend = conversation?.withFriend { - let profileUser = ProfileUser.UserType(withFriend) - - vc.profileUser = profileUser + if withFriend.userID != YepUserDefaults.userID.value { + vc.profileUser = ProfileUser.UserType(withFriend) + } + vc.isFromConversation = true + + vc.setBackButtonWithTitle() } - } - if segue.identifier == "presentMessageMedia" { + } else if segue.identifier == "showMessageMedia" { + + let vc = segue.destinationViewController as! MessageMediaViewController + + if let message = sender as? Message, messageIndex = messages.indexOf(message) { + + vc.message = message + + let indexPath = NSIndexPath(forRow: messageIndex - displayedMessagesRange.location , inSection: 0) + + if let cell = conversationCollectionView.cellForItemAtIndexPath(indexPath) { + + var frame = CGRectZero + var transitionView: UIView? + + if let sender = message.fromFriend { + if sender.friendState != UserFriendState.Me.rawValue { + switch message.mediaType { + + case MessageMediaType.Image.rawValue: + let cell = cell as! ChatLeftImageCell + transitionView = cell.messageImageView + frame = cell.convertRect(cell.messageImageView.frame, toView: view) + + case MessageMediaType.Video.rawValue: + let cell = cell as! ChatLeftVideoCell + transitionView = cell.thumbnailImageView + frame = cell.convertRect(cell.thumbnailImageView.frame, toView: view) + + case MessageMediaType.Location.rawValue: + let cell = cell as! ChatLeftLocationCell + transitionView = cell.mapImageView + frame = cell.convertRect(cell.mapImageView.frame, toView: view) + + default: + break + } + + } else { + switch message.mediaType { + + case MessageMediaType.Image.rawValue: + let cell = cell as! ChatRightImageCell + transitionView = cell.messageImageView + frame = cell.convertRect(cell.messageImageView.frame, toView: view) + + case MessageMediaType.Video.rawValue: + let cell = cell as! ChatRightVideoCell + transitionView = cell.thumbnailImageView + frame = cell.convertRect(cell.thumbnailImageView.frame, toView: view) + + case MessageMediaType.Location.rawValue: + let cell = cell as! ChatRightLocationCell + transitionView = cell.mapImageView + frame = cell.convertRect(cell.mapImageView.frame, toView: view) + + default: + break + } + } + } + +// vc.modalPresentationStyle = UIModalPresentationStyle.Custom +// +// let transitionManager = ConversationMessagePreviewTransitionManager() +// transitionManager.frame = frame +// transitionManager.transitionView = transitionView +// +// vc.transitioningDelegate = transitionManager +// +// messagePreviewTransitionManager = transitionManager + + let delegate = ConversationMessagePreviewNavigationControllerDelegate() + delegate.frame = frame + delegate.transitionView = transitionView + + navigationControllerDelegate = delegate + + navigationController?.delegate = delegate + } + } + + } else if segue.identifier == "presentMessageMedia" { let vc = segue.destinationViewController as! MessageMediaViewController @@ -1106,6 +1300,28 @@ class ConversationViewController: UIViewController { } } } + + +} + +// MARK: UIGestureRecognizerDelegate + +extension ConversationViewController: UIGestureRecognizerDelegate { + func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool { + if let isAnimated = navigationController?.transitionCoordinator()?.isAnimated() { + return !isAnimated + } + + if navigationController?.viewControllers.count < 2 { + return false + } + + if gestureRecognizer == navigationController?.interactivePopGestureRecognizer { + return true + } + + return false + } } // MARK: UICollectionViewDataSource, UICollectionViewDelegate @@ -1143,16 +1359,19 @@ extension ConversationViewController: UICollectionViewDataSource, UICollectionVi // TODO: 需要更好的下载与 mark as read 逻辑:也许未下载的也可以 mark as read downloadAttachmentOfMessage(message) - markAsReadMessage(message, failureHandler: nil) { success in - dispatch_async(dispatch_get_main_queue()) { - let realm = Realm() - - if let message = messageWithMessageID(message.messageID, inRealm: realm) { - realm.write { - message.readed = true - } + // 防止未在此界面时被标记 + if navigationController?.topViewController == self { + markAsReadMessage(message, failureHandler: nil) { success in + dispatch_async(dispatch_get_main_queue()) { + let realm = Realm() - println("\(message.messageID) mark as read") + if let message = messageWithMessageID(message.messageID, inRealm: realm) { + realm.write { + message.readed = true + } + + println("\(message.messageID) mark as read") + } } } } @@ -1161,7 +1380,10 @@ extension ConversationViewController: UICollectionViewDataSource, UICollectionVi case MessageMediaType.Image.rawValue: let cell = collectionView.dequeueReusableCellWithReuseIdentifier(chatLeftImageCellIdentifier, forIndexPath: indexPath) as! ChatLeftImageCell - cell.configureWithMessage(message, messageImagePreferredWidth: messageImagePreferredWidth, messageImagePreferredHeight: messageImagePreferredHeight, messageImagePreferredAspectRatio: messageImagePreferredAspectRatio) + cell.configureWithMessage(message, messageImagePreferredWidth: messageImagePreferredWidth, messageImagePreferredHeight: messageImagePreferredHeight, messageImagePreferredAspectRatio: messageImagePreferredAspectRatio, mediaTapAction: { + + self.performSegueWithIdentifier("showMessageMedia", sender: message) + }) return cell @@ -1169,14 +1391,21 @@ extension ConversationViewController: UICollectionViewDataSource, UICollectionVi let cell = collectionView.dequeueReusableCellWithReuseIdentifier(chatLeftAudioCellIdentifier, forIndexPath: indexPath) as! ChatLeftAudioCell let audioPlayedDuration = audioPlayedDurationOfMessage(message) - cell.configureWithMessage(message, audioPlayedDuration: audioPlayedDuration) + + cell.configureWithMessage(message, audioPlayedDuration: audioPlayedDuration, audioBubbleTapAction: { message in + + self.playMessageAudioWithMessage(message) + }) return cell case MessageMediaType.Video.rawValue: let cell = collectionView.dequeueReusableCellWithReuseIdentifier(chatLeftVideoCellIdentifier, forIndexPath: indexPath) as! ChatLeftVideoCell - cell.configureWithMessage(message, messageImagePreferredWidth: messageImagePreferredWidth, messageImagePreferredHeight: messageImagePreferredHeight, messageImagePreferredAspectRatio: messageImagePreferredAspectRatio) + cell.configureWithMessage(message, messageImagePreferredWidth: messageImagePreferredWidth, messageImagePreferredHeight: messageImagePreferredHeight, messageImagePreferredAspectRatio: messageImagePreferredAspectRatio, mediaTapAction: { + + self.performSegueWithIdentifier("showMessageMedia", sender: message) + }) return cell @@ -1201,7 +1430,10 @@ extension ConversationViewController: UICollectionViewDataSource, UICollectionVi case MessageMediaType.Image.rawValue: let cell = collectionView.dequeueReusableCellWithReuseIdentifier(chatRightImageCellIdentifier, forIndexPath: indexPath) as! ChatRightImageCell - cell.configureWithMessage(message, messageImagePreferredWidth: messageImagePreferredWidth, messageImagePreferredHeight: messageImagePreferredHeight, messageImagePreferredAspectRatio: messageImagePreferredAspectRatio) + cell.configureWithMessage(message, messageImagePreferredWidth: messageImagePreferredWidth, messageImagePreferredHeight: messageImagePreferredHeight, messageImagePreferredAspectRatio: messageImagePreferredAspectRatio, mediaTapAction: { + + self.performSegueWithIdentifier("showMessageMedia", sender: message) + }) return cell @@ -1209,14 +1441,21 @@ extension ConversationViewController: UICollectionViewDataSource, UICollectionVi let cell = collectionView.dequeueReusableCellWithReuseIdentifier(chatRightAudioCellIdentifier, forIndexPath: indexPath) as! ChatRightAudioCell let audioPlayedDuration = audioPlayedDurationOfMessage(message) - cell.configureWithMessage(message, audioPlayedDuration: audioPlayedDuration) + + cell.configureWithMessage(message, audioPlayedDuration: audioPlayedDuration, audioBubbleTapAction: { message in + + self.playMessageAudioWithMessage(message) + }) return cell case MessageMediaType.Video.rawValue: let cell = collectionView.dequeueReusableCellWithReuseIdentifier(chatRightVideoCellIdentifier, forIndexPath: indexPath) as! ChatRightVideoCell - cell.configureWithMessage(message, messageImagePreferredWidth: messageImagePreferredWidth, messageImagePreferredHeight: messageImagePreferredHeight, messageImagePreferredAspectRatio: messageImagePreferredAspectRatio) + cell.configureWithMessage(message, messageImagePreferredWidth: messageImagePreferredWidth, messageImagePreferredHeight: messageImagePreferredHeight, messageImagePreferredAspectRatio: messageImagePreferredAspectRatio, mediaTapAction: { + + self.performSegueWithIdentifier("showMessageMedia", sender: message) + }) return cell @@ -1261,18 +1500,21 @@ extension ConversationViewController: UICollectionViewDataSource, UICollectionVi } func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { - if isKeyboardVisible { + if messageToolbar.state != .Default { messageToolbar.state = .Default - - } else { + } + /* + else { let message = messages[displayedMessagesRange.location + indexPath.item] switch message.mediaType { case MessageMediaType.Image.rawValue: - performSegueWithIdentifier("presentMessageMedia", sender: message) + //performSegueWithIdentifier("presentMessageMedia", sender: message) + performSegueWithIdentifier("showMessageMedia", sender: message) case MessageMediaType.Video.rawValue: - performSegueWithIdentifier("presentMessageMedia", sender: message) + //performSegueWithIdentifier("presentMessageMedia", sender: message) + performSegueWithIdentifier("showMessageMedia", sender: message) case MessageMediaType.Audio.rawValue: @@ -1302,7 +1544,7 @@ extension ConversationViewController: UICollectionViewDataSource, UICollectionVi } } - if message == playingMessage { + if message.messageID == playingMessage.messageID { return } } @@ -1320,8 +1562,8 @@ extension ConversationViewController: UICollectionViewDataSource, UICollectionVi } } + */ } - // MARK: UIScrollViewDelegate diff --git a/Yep/ViewControllers/Conversations/ConversationsViewController.swift b/Yep/ViewControllers/Conversations/ConversationsViewController.swift index cad24e3a..66be9a5a 100644 --- a/Yep/ViewControllers/Conversations/ConversationsViewController.swift +++ b/Yep/ViewControllers/Conversations/ConversationsViewController.swift @@ -17,6 +17,24 @@ class ConversationsViewController: UIViewController { var realm: Realm! + var unreadMessagesToken: NotificationToken? + var haveUnreadMessages = false { + didSet { + if haveUnreadMessages != oldValue { + if haveUnreadMessages { + navigationController?.tabBarItem.image = UIImage(named: "icon_chat_unread") + navigationController?.tabBarItem.selectedImage = UIImage(named: "icon_chat_active_unread") + + } else { + navigationController?.tabBarItem.image = UIImage(named: "icon_chat") + navigationController?.tabBarItem.selectedImage = UIImage(named: "icon_chat_active") + } + + reloadConversationsTableView() + } + } + } + lazy var conversations: Results = { return self.realm.objects(Conversation).sorted("updatedAt", ascending: false) }() @@ -32,6 +50,7 @@ class ConversationsViewController: UIViewController { realm = Realm() NSNotificationCenter.defaultCenter().addObserver(self, selector: "reloadConversationsTableView", name: YepNewMessagesReceivedNotification, object: nil) + NSNotificationCenter.defaultCenter().addObserver(self, selector: "reloadConversationsTableView", name: ConversationViewController.Notification.MessageSent, object: nil) YepUserDefaults.nickname.bindListener("ConversationsViewController.Nickname") { _ in @@ -46,6 +65,10 @@ class ConversationsViewController: UIViewController { conversationsTableView.registerNib(UINib(nibName: cellIdentifier, bundle: nil), forCellReuseIdentifier: cellIdentifier) conversationsTableView.rowHeight = 80 + + unreadMessagesToken = realm.addNotificationBlock { notification, realm in + self.haveUnreadMessages = countOfUnreadMessagesInRealm(realm) > 0 + } } override func viewDidAppear(animated: Bool) { diff --git a/Yep/ViewControllers/CustomNavigationBar/CustomNavigationBarViewController.swift b/Yep/ViewControllers/CustomNavigationBar/CustomNavigationBarViewController.swift index e935c47d..362fb806 100644 --- a/Yep/ViewControllers/CustomNavigationBar/CustomNavigationBarViewController.swift +++ b/Yep/ViewControllers/CustomNavigationBar/CustomNavigationBarViewController.swift @@ -10,10 +10,12 @@ import UIKit class CustomNavigationBarViewController: UIViewController { + override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) if let navigationController = navigationController { + navigationController.navigationBar.backgroundColor = UIColor.clearColor() navigationController.navigationBar.translucent = true navigationController.navigationBar.shadowImage = UIImage() @@ -30,24 +32,4 @@ class CustomNavigationBarViewController: UIViewController { } } - override func viewWillDisappear(animated: Bool) { - super.viewWillDisappear(animated) - - if let navigationController = navigationController { - - navigationController.navigationBar.backgroundColor = nil - navigationController.navigationBar.translucent = true - navigationController.navigationBar.shadowImage = nil - navigationController.navigationBar.barStyle = UIBarStyle.Default - navigationController.navigationBar.setBackgroundImage(nil, forBarMetrics: UIBarMetrics.Default) - - let textAttributes = [ - NSForegroundColorAttributeName: UIColor.yepTintColor(), - NSFontAttributeName: UIFont.navigationBarTitleFont() - ] - - navigationController.navigationBar.titleTextAttributes = textAttributes - navigationController.navigationBar.tintColor = nil - } - } } diff --git a/Yep/ViewControllers/Discover/DiscoverViewController.swift b/Yep/ViewControllers/Discover/DiscoverViewController.swift index 7fc797ec..ba53b4df 100644 --- a/Yep/ViewControllers/Discover/DiscoverViewController.swift +++ b/Yep/ViewControllers/Discover/DiscoverViewController.swift @@ -8,42 +8,94 @@ import UIKit -class DiscoverViewController: UIViewController { +class DiscoverViewController: BaseViewController { @IBOutlet weak var discoverTableView: UITableView! + @IBOutlet weak var filterButtonItem: UIBarButtonItem! + + @IBOutlet weak var activityIndicator: UIActivityIndicatorView! + + let cellIdentifier = "ContactsCell" + + var discoveredUserSortStyle: DiscoveredUserSortStyle = .Default { + didSet { + filterButtonItem.title = discoveredUserSortStyle.name + + activityIndicator.startAnimating() + + discoverUsers(masterSkills: [], learningSkills: [], discoveredUserSortStyle: discoveredUserSortStyle, failureHandler: { (reason, errorMessage) in + defaultFailureHandler(reason, errorMessage) + + dispatch_async(dispatch_get_main_queue()) { + self.activityIndicator.stopAnimating() + } + + }, completion: { discoveredUsers in + dispatch_async(dispatch_get_main_queue()) { + self.discoveredUsers = discoveredUsers + self.activityIndicator.stopAnimating() + } + }) + } + } + + var discoveredUsers = [DiscoveredUser]() { + didSet { + updateDiscoverTableView() + } + } - var discoveredUsers = [DiscoveredUser]() override func viewDidLoad() { super.viewDidLoad() - // Do any additional setup after loading the view. view.backgroundColor = UIColor.whiteColor() discoverTableView.registerNib(UINib(nibName: cellIdentifier, bundle: nil), forCellReuseIdentifier: cellIdentifier) discoverTableView.rowHeight = 80 - - discoverTableView.dataSource = self - - discoverTableView.delegate = self + + discoverTableView.tableFooterView = UIView() + + discoveredUserSortStyle = .Default + } - discoverUsers(masterSkills: [], learningSkills: [], discoveredUserSortStyle: .LastSignIn, failureHandler: { (reason, errorMessage) in - defaultFailureHandler(reason, errorMessage) + // MARK: Actions - }, completion: { discoveredUsers in - self.discoveredUsers = discoveredUsers - - dispatch_async(dispatch_get_main_queue()) { - self.reloadDiscoverTableView() - } - }) + @IBAction func showFilters(sender: UIBarButtonItem) { + moreAction() } - func reloadDiscoverTableView() { - self.discoverTableView.reloadData() + func moreAction() { + let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .ActionSheet) + + let nearbyAction: UIAlertAction = UIAlertAction(title: DiscoveredUserSortStyle.Distance.name, style: .Default) { action -> Void in + self.discoveredUserSortStyle = .Distance + } + alertController.addAction(nearbyAction) + + let timeAction: UIAlertAction = UIAlertAction(title: DiscoveredUserSortStyle.LastSignIn.name, style: .Default) { action -> Void in + self.discoveredUserSortStyle = .LastSignIn + } + alertController.addAction(timeAction) + + let defaultAction: UIAlertAction = UIAlertAction(title: DiscoveredUserSortStyle.Default.name, style: .Default) { action -> Void in + self.discoveredUserSortStyle = .Default + } + alertController.addAction(defaultAction) + + let cancelAction: UIAlertAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .Cancel) { action -> Void in + } + alertController.addAction(cancelAction) + + self.presentViewController(alertController, animated: true, completion: nil) + } + + func updateDiscoverTableView() { + //discoverTableView.reloadData() + discoverTableView.reloadSections(NSIndexSet(index: 0), withRowAnimation: UITableViewRowAnimation.Automatic) } @@ -56,8 +108,12 @@ class DiscoverViewController: UIViewController { let discoveredUser = discoveredUsers[indexPath.row] let vc = segue.destinationViewController as! ProfileViewController + + if discoveredUser.id != YepUserDefaults.userID.value { + vc.profileUser = ProfileUser.DiscoveredUserType(discoveredUser) + } - vc.profileUser = ProfileUser.DiscoveredUserType(discoveredUser) + vc.setBackButtonWithTitle() vc.hidesBottomBarWhenPushed = true } @@ -87,8 +143,10 @@ extension DiscoverViewController: UITableViewDataSource, UITableViewDelegate { } } - cell.joinedDateLabel.text = discoveredUser.createdAt.timeAgo - cell.lastTimeSeenLabel.text = discoveredUser.lastSignInAt.timeAgo + cell.joinedDateLabel.text = discoveredUser.introduction + + let distance = discoveredUser.distance.format(".1") + cell.lastTimeSeenLabel.text = "\(distance) km | \(discoveredUser.lastSignInAt.timeAgo)" cell.nameLabel.text = discoveredUser.nickname diff --git a/Yep/ViewControllers/EditProfile/EditProfileViewController.swift b/Yep/ViewControllers/EditProfile/EditProfileViewController.swift index 7469ef45..0c8f248e 100644 --- a/Yep/ViewControllers/EditProfile/EditProfileViewController.swift +++ b/Yep/ViewControllers/EditProfile/EditProfileViewController.swift @@ -7,6 +7,7 @@ // import UIKit +import RealmSwift class EditProfileViewController: UIViewController { @@ -41,6 +42,8 @@ class EditProfileViewController: UIViewController { updateAvatar() { } + mobileLabel.text = YepUserDefaults.mobile.value + editProfileTableView.registerNib(UINib(nibName: editProfileLessInfoCellIdentifier, bundle: nil), forCellReuseIdentifier: editProfileLessInfoCellIdentifier) editProfileTableView.registerNib(UINib(nibName: editProfileMoreInfoCellIdentifier, bundle: nil), forCellReuseIdentifier: editProfileMoreInfoCellIdentifier) editProfileTableView.registerNib(UINib(nibName: editProfileColoredTitleCellIdentifier, bundle: nil), forCellReuseIdentifier: editProfileColoredTitleCellIdentifier) @@ -272,7 +275,34 @@ extension EditProfileViewController: UITableViewDataSource, UITableViewDelegate default: break } - + + case Section.LogOut.rawValue: + YepAlert.confirmOrCancel(title: NSLocalizedString("Notice", comment: ""), message: NSLocalizedString("Do you want to logout?", comment: ""), confirmTitle: NSLocalizedString("Yes", comment: ""), cancelTitle: NSLocalizedString("Cancel", comment: ""), inViewController: self, withConfirmAction: { () -> Void in + + YepUserDefaults.cleanAll() + + if let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate { + appDelegate.startIntroStory() + } + + // clean Realm + + let realm = Realm() + realm.write { + realm.deleteAll() + } + + // clean Message caches + + NSFileManager.cleanMessageCaches() + + // clean Avatar caches + + NSFileManager.cleanAvatarCaches() + + }, cancelAction: { () -> Void in + }) + default: break } diff --git a/Yep/ViewControllers/Login/LoginByMobileViewController.swift b/Yep/ViewControllers/Login/LoginByMobileViewController.swift index 363077e6..965a3c4d 100644 --- a/Yep/ViewControllers/Login/LoginByMobileViewController.swift +++ b/Yep/ViewControllers/Login/LoginByMobileViewController.swift @@ -67,7 +67,7 @@ class LoginByMobileViewController: UIViewController { let mobile = mobileNumberTextField.text let areaCode = areaCodeTextField.text - sendVerifyCode(ofMobile: mobile, withAreaCode: areaCode, failureHandler: { (reason, errorMessage) in + sendVerifyCodeOfMobile(mobile, withAreaCode: areaCode, useMethod: .SMS, failureHandler: { (reason, errorMessage) in defaultFailureHandler(reason, errorMessage) if let errorMessage = errorMessage { diff --git a/Yep/ViewControllers/Login/LoginVerifyMobileViewController.swift b/Yep/ViewControllers/Login/LoginVerifyMobileViewController.swift index 039af9ba..7637c725 100644 --- a/Yep/ViewControllers/Login/LoginVerifyMobileViewController.swift +++ b/Yep/ViewControllers/Login/LoginVerifyMobileViewController.swift @@ -84,7 +84,7 @@ class LoginVerifyMobileViewController: UIViewController { func callMe() { nextButton.setTitle(NSLocalizedString("Calling", comment: ""), forState: .Normal) - resendVoiceVerifyCode(ofMobile: mobile, withAreaCode: areaCode, failureHandler: { (reason, errorMessage) -> () in + sendVerifyCodeOfMobile(mobile, withAreaCode: areaCode, useMethod: .Call, failureHandler: { (reason, errorMessage) in defaultFailureHandler(reason, errorMessage) if let errorMessage = errorMessage { diff --git a/Yep/ViewControllers/MessageMedia/MessageMediaViewController.swift b/Yep/ViewControllers/MessageMedia/MessageMediaViewController.swift index d67a0add..bc261fa7 100644 --- a/Yep/ViewControllers/MessageMedia/MessageMediaViewController.swift +++ b/Yep/ViewControllers/MessageMedia/MessageMediaViewController.swift @@ -21,9 +21,22 @@ class MessageMediaViewController: UIViewController { NSNotificationCenter.defaultCenter().removeObserver(self) } + override func viewWillAppear(animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(true, animated: false) + } + override func viewDidLoad() { super.viewDidLoad() + title = NSLocalizedString("Preview", comment: "") + + self.view.backgroundColor = UIColor.blackColor() + self.mediaView.backgroundColor = UIColor.blackColor() + + automaticallyAdjustsScrollViewInsets = false + + if let message = message { switch message.mediaType { @@ -35,18 +48,12 @@ class MessageMediaViewController: UIViewController { if let imageFileURL = NSFileManager.yepMessageImageURLWithName(message.localAttachmentName), let image = UIImage(contentsOfFile: imageFileURL.path!) { - mediaView.imageView.image = image + mediaView.image = image mediaControlView.shareAction = { - let presentingViewController = self.presentingViewController + let activityViewController = UIActivityViewController(activityItems: [image], applicationActivities: nil) - self.dismissViewControllerAnimated(true, completion: { () -> Void in - - let activityViewController = UIActivityViewController(activityItems: [image], applicationActivities: nil) - - presentingViewController?.presentViewController(activityViewController, animated: true, completion: { () -> Void in - - }) + self.presentViewController(activityViewController, animated: true, completion: { () -> Void in }) } } @@ -102,15 +109,9 @@ class MessageMediaViewController: UIViewController { mediaControlView.shareAction = { - let presentingViewController = self.presentingViewController + let activityViewController = UIActivityViewController(activityItems: [videoFileURL], applicationActivities: nil) - self.dismissViewControllerAnimated(true, completion: { () -> Void in - - let activityViewController = UIActivityViewController(activityItems: [videoFileURL], applicationActivities: nil) - - presentingViewController?.presentViewController(activityViewController, animated: true, completion: { () -> Void in - - }) + self.presentViewController(activityViewController, animated: true, completion: { () -> Void in }) } } @@ -135,7 +136,8 @@ class MessageMediaViewController: UIViewController { } } - dismissViewControllerAnimated(true, completion: nil) +// dismissViewControllerAnimated(true, completion: nil) + navigationController?.popViewControllerAnimated(true) } override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer) { @@ -170,4 +172,8 @@ class MessageMediaViewController: UIViewController { playerItem.seekToTime(kCMTimeZero) } } + + override func prefersStatusBarHidden() -> Bool { + return true + } } diff --git a/Yep/ViewControllers/Nav/YepNavigationController.swift b/Yep/ViewControllers/Nav/YepNavigationController.swift new file mode 100644 index 00000000..50631cc1 --- /dev/null +++ b/Yep/ViewControllers/Nav/YepNavigationController.swift @@ -0,0 +1,90 @@ +// +// YepNavigationController.swift +// Yep +// +// Created by kevinzhow on 15/5/27. +// Copyright (c) 2015年 Catch Inc. All rights reserved. +// + +import UIKit + +class YepNavigationController: UINavigationController, UIGestureRecognizerDelegate, UINavigationControllerDelegate { + + override func viewDidLoad() { + super.viewDidLoad() + + if respondsToSelector("interactivePopGestureRecognizer") { + interactivePopGestureRecognizer.delegate = self + + delegate = self + } + // Do any additional setup after loading the view. + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + override init(rootViewController: UIViewController) { + super.init(rootViewController: rootViewController) + } + + required init(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + + override func pushViewController(viewController: UIViewController, animated: Bool) { + if respondsToSelector("interactivePopGestureRecognizer") && animated { + interactivePopGestureRecognizer.enabled = false + } + + super.pushViewController(viewController, animated: animated) + } + + override func popToRootViewControllerAnimated(animated: Bool) -> [AnyObject]? { + if respondsToSelector("interactivePopGestureRecognizer") && animated { + interactivePopGestureRecognizer.enabled = false + } + + return super.popToRootViewControllerAnimated(animated) + } + + override func popToViewController(viewController: UIViewController, animated: Bool) -> [AnyObject]? { + if respondsToSelector("interactivePopGestureRecognizer") && animated { + interactivePopGestureRecognizer.enabled = false + } + + return super.popToViewController(viewController, animated: false) + } + + func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool) { + if respondsToSelector("interactivePopGestureRecognizer") { + interactivePopGestureRecognizer.enabled = true + } + } + + func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool { + if gestureRecognizer == interactivePopGestureRecognizer + { + if self.viewControllers.count < 2 || self.visibleViewController == self.viewControllers[0] as! UIViewController + { + return false + } + } + + return true + } + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { + // Get the new view controller using segue.destinationViewController. + // Pass the selected object to the new view controller. + } + */ + +} diff --git a/Yep/ViewControllers/OAuth/OAuthViewController.swift b/Yep/ViewControllers/OAuth/OAuthViewController.swift index c7f71da1..2c0a60e9 100644 --- a/Yep/ViewControllers/OAuth/OAuthViewController.swift +++ b/Yep/ViewControllers/OAuth/OAuthViewController.swift @@ -9,7 +9,7 @@ import UIKit import WebViewJavascriptBridge -class OAuthViewController: UIViewController, UIWebViewDelegate, NSURLConnectionDelegate, NSURLConnectionDataDelegate { +class OAuthViewController: BaseViewController, UIWebViewDelegate, NSURLConnectionDelegate, NSURLConnectionDataDelegate { var socialAccount: SocialAccount! var afterOAuthAction: ((socialAccount: SocialAccount) -> Void)? @@ -17,6 +17,7 @@ class OAuthViewController: UIViewController, UIWebViewDelegate, NSURLConnectionD @IBOutlet weak var webView: UIWebView! @IBOutlet weak var activityIndicator: UIActivityIndicatorView! + var bridge: WebViewJavascriptBridge! var authenticated = false @@ -25,6 +26,11 @@ class OAuthViewController: UIViewController, UIWebViewDelegate, NSURLConnectionD override func viewDidLoad() { super.viewDidLoad() + + + self.view.backgroundColor = UIColor.whiteColor() + + animatedOnNavigationBar = false title = NSLocalizedString("OAuth", comment: "") diff --git a/Yep/ViewControllers/Profile/ProfileLayout.swift b/Yep/ViewControllers/Profile/ProfileLayout.swift index 745bc507..dafb51e2 100644 --- a/Yep/ViewControllers/Profile/ProfileLayout.swift +++ b/Yep/ViewControllers/Profile/ProfileLayout.swift @@ -10,6 +10,9 @@ import UIKit class ProfileLayout: UICollectionViewFlowLayout { + var scrollUpAction: ((progress: CGFloat) -> Void)? + + let leftEdgeInset: CGFloat = YepConfig.Profile.leftEdgeInset override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? { @@ -22,8 +25,8 @@ class ProfileLayout: UICollectionViewFlowLayout { if contentOffset.y < minY { let deltaY = abs(contentOffset.y - minY) - for (index, attributes) in enumerate(layoutAttributes) { - if index == 0 { + for attributes in layoutAttributes { + if attributes.indexPath.section == ProfileViewController.ProfileSection.Header.rawValue { var frame = attributes.frame frame.size.height = max(minY, CGRectGetWidth(collectionView!.bounds) * profileAvatarAspectRatio + deltaY) frame.origin.y = CGRectGetMinY(frame) - deltaY @@ -32,11 +35,39 @@ class ProfileLayout: UICollectionViewFlowLayout { break } } + + } else { + let coverHeight = CGRectGetWidth(collectionView!.bounds) * profileAvatarAspectRatio + let coverHideHeight = coverHeight - 64 + + if contentOffset.y > coverHideHeight { + + let deltaY = abs(contentOffset.y - minY) + + for attributes in layoutAttributes { + if attributes.indexPath.section == ProfileViewController.ProfileSection.Header.rawValue { + var frame = attributes.frame + frame.origin.y = deltaY - coverHideHeight + attributes.frame = frame + attributes.zIndex = 1000 + + break + } + } + } + + if coverHideHeight > contentOffset.y { + scrollUpAction?(progress: 1.0 - (coverHideHeight - contentOffset.y) / coverHideHeight) + + } else { + scrollUpAction?(progress: 1.0) + } + } // 先按照每个 item 的 centerY 分组 var rowCollections = [CGFloat: [UICollectionViewLayoutAttributes]]() - for (index, attributes) in enumerate(layoutAttributes) { + for attributes in layoutAttributes { let centerY = CGRectGetMidY(attributes.frame) if let rowCollection = rowCollections[centerY] { @@ -77,9 +108,9 @@ class ProfileLayout: UICollectionViewFlowLayout { } else { itemFrame.origin.x = CGRectGetMaxX(previousFrame) + minimumInteritemSpacing } - } - attributes.frame = itemFrame + attributes.frame = itemFrame + } previousFrame = itemFrame } diff --git a/Yep/ViewControllers/Profile/ProfileViewController.swift b/Yep/ViewControllers/Profile/ProfileViewController.swift index 7cdb04c3..70019868 100644 --- a/Yep/ViewControllers/Profile/ProfileViewController.swift +++ b/Yep/ViewControllers/Profile/ProfileViewController.swift @@ -78,18 +78,80 @@ enum SocialAccount: Int, Printable { enum ProfileUser { case DiscoveredUserType(DiscoveredUser) case UserType(User) + + func enabledSocialAccount(socialAccount: SocialAccount) -> Bool { + var accountEnabled = false + + let providerName = socialAccount.description.lowercaseString + + switch self { + + case .DiscoveredUserType(let discoveredUser): + for provider in discoveredUser.socialAccountProviders { + if (provider.name == providerName) && provider.enabled { + + accountEnabled = true + + break + } + } + + case .UserType(let user): + for provider in user.socialAccountProviders { + if (provider.name == providerName) && provider.enabled { + + accountEnabled = true + + break + } + } + } + + return accountEnabled + } } -class ProfileViewController: CustomNavigationBarViewController { +class ProfileViewController: UIViewController { + + var isFromConversation = false var profileUser: ProfileUser? + var profileUserIsMe = true { + didSet { + if !profileUserIsMe { + + let moreBarButtonItem = UIBarButtonItem(image: UIImage(named: "icon_more"), style: UIBarButtonItemStyle.Plain, target: self, action: "moreAction") + + customNavigationItem.rightBarButtonItem = moreBarButtonItem + if isFromConversation { + sayHiView.hidden = true + + } else { + sayHiView.sayHiAction = { + self.sayHi() + } + + profileCollectionView.contentInset.bottom = sayHiView.bounds.height + } + + } else { + sayHiView.hidden = true + + let settingsBarButtonItem = UIBarButtonItem(image: UIImage(named: "icon_settings"), style: UIBarButtonItemStyle.Plain, target: self, action: "showSettings") + + customNavigationItem.rightBarButtonItem = settingsBarButtonItem + } + } + } + + @IBOutlet weak var topShadowImageView: UIImageView! @IBOutlet weak var profileCollectionView: UICollectionView! - @IBOutlet weak var sayHiView: UIView! - @IBOutlet weak var sayHiButton: UIButton! - + @IBOutlet weak var sayHiView: SayHiView! + + var customNavigationBar: UINavigationBar! let skillCellIdentifier = "SkillCell" let headerCellIdentifier = "ProfileHeaderCell" @@ -123,18 +185,18 @@ class ProfileViewController: CustomNavigationBarViewController { } case .UserType(let user): + if !user.introduction.isEmpty { introduction = user.introduction } - } - } else { - introduction = YepUserDefaults.introduction.value - - YepUserDefaults.introduction.bindListener("Profile.introductionText") { introduction in - if let introduction = introduction { - self.introductionText = introduction - self.updateProfileCollectionView() + if user.friendState == UserFriendState.Me.rawValue { + YepUserDefaults.introduction.bindListener("Profile.introductionText") { introduction in + if let introduction = introduction { + self.introductionText = introduction + self.updateProfileCollectionView() + } + } } } } @@ -142,11 +204,39 @@ class ProfileViewController: CustomNavigationBarViewController { return introduction ?? NSLocalizedString("No Introduction yet.", comment: "") }() - var masterSkills = [Skill]() - var learningSkills = [Skill]() + var masterSkills = [Skill]() { + didSet { + let realm = Realm() - typealias SocialWorkProviderInfo = [String: Bool] - var socialWorkProviderInfo = SocialWorkProviderInfo() + if let + myUserID = YepUserDefaults.userID.value, + me = userWithUserID(myUserID, inRealm: realm) { + realm.write { + me.masterSkills.removeAll() + let userSkills = userSkillsFromSkills(self.masterSkills, inRealm: realm) + me.masterSkills.extend(userSkills) + } + } + } + } + var learningSkills = [Skill]() { + didSet { + let realm = Realm() + + if let + myUserID = YepUserDefaults.userID.value, + me = userWithUserID(myUserID, inRealm: realm) { + realm.write { + me.learningSkills.removeAll() + let userSkills = userSkillsFromSkills(self.learningSkills, inRealm: realm) + me.learningSkills.extend(userSkills) + } + } + } + } + + //typealias SocialWorkProviderInfo = [String: Bool] + //var socialWorkProviderInfo = SocialWorkProviderInfo() var dribbbleWork: DribbbleWork? var instagramWork: InstagramWork? @@ -163,11 +253,54 @@ class ProfileViewController: CustomNavigationBarViewController { return ceil(rect.height) + 4 } } + + var customNavigationItem: UINavigationItem = UINavigationItem(title: "Details") + func setBackButtonWithTitle() { + let backBarButtonItem = UIBarButtonItem(image: UIImage(named: "icon_back"), style: UIBarButtonItemStyle.Plain, target: self, action: "popBack") + backBarButtonItem.tintColor = UIColor.whiteColor() + + customNavigationItem.leftBarButtonItem = backBarButtonItem + } + + func popBack() { + self.navigationController?.popViewControllerAnimated(true) + } override func viewDidLoad() { super.viewDidLoad() + if profileUser == nil { + if let + myUserID = YepUserDefaults.userID.value, + me = userWithUserID(myUserID, inRealm: Realm()) { + profileUser = ProfileUser.UserType(me) + profileUserIsMe = true + + masterSkills = skillsFromUserSkillList(me.masterSkills) + learningSkills = skillsFromUserSkillList(me.learningSkills) + } + } + + if let profileLayout = profileCollectionView.collectionViewLayout as? ProfileLayout { + + profileLayout.scrollUpAction = { progress in + + let indexPath = NSIndexPath(forItem: 0, inSection: ProfileSection.Header.rawValue) + + if let coverCell = self.profileCollectionView.cellForItemAtIndexPath(indexPath) as? ProfileHeaderCell { + + let beginModifyPercentage: CGFloat = 0.9 + let modifablePercentage: CGFloat = 1.0 - 0.9 + let modifyPercentage: CGFloat = (progress - beginModifyPercentage)/modifablePercentage + + coverCell.locationLabel.alpha = progress > beginModifyPercentage ? 0 : modifyPercentage + coverCell.avatarBlurImageView.alpha = progress < beginModifyPercentage ? 0 : modifyPercentage + self.topShadowImageView.alpha = progress < beginModifyPercentage ? 1.0 : 1 - modifyPercentage + } + } + } + profileCollectionView.registerNib(UINib(nibName: skillCellIdentifier, bundle: nil), forCellWithReuseIdentifier: skillCellIdentifier) profileCollectionView.registerNib(UINib(nibName: headerCellIdentifier, bundle: nil), forCellWithReuseIdentifier: headerCellIdentifier) profileCollectionView.registerNib(UINib(nibName: footerCellIdentifier, bundle: nil), forCellWithReuseIdentifier: footerCellIdentifier) @@ -181,6 +314,40 @@ class ProfileViewController: CustomNavigationBarViewController { profileCollectionView.alwaysBounceVertical = true automaticallyAdjustsScrollViewInsets = false + + + customNavigationBar = UINavigationBar(frame: CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 64.0)) + customNavigationBar.alpha = 0 + customNavigationBar.setItems([customNavigationItem], animated: false) + view.addSubview(customNavigationBar) + + customNavigationBar.backgroundColor = UIColor.clearColor() + customNavigationBar.translucent = true + customNavigationBar.shadowImage = UIImage() + customNavigationBar.barStyle = UIBarStyle.BlackTranslucent + customNavigationBar.setBackgroundImage(UIImage(), forBarMetrics: UIBarMetrics.Default) + + let textAttributes = [ + NSForegroundColorAttributeName: UIColor.whiteColor(), + NSFontAttributeName: UIFont.navigationBarTitleFont() + ] + + customNavigationBar.titleTextAttributes = textAttributes + customNavigationBar.tintColor = UIColor.whiteColor() + + + //Make sure when pan edge screen collectionview not scroll + if let gestures = navigationController?.view.gestureRecognizers { + for recognizer in gestures + { + if recognizer.isKindOfClass(UIScreenEdgePanGestureRecognizer) + { + profileCollectionView.panGestureRecognizer.requireGestureRecognizerToFail(recognizer as! UIScreenEdgePanGestureRecognizer) + println("Require UIScreenEdgePanGestureRecognizer to failed") + break + } + } + } if let tabBarController = tabBarController { profileCollectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: CGRectGetHeight(tabBarController.tabBar.bounds), right: 0) @@ -189,64 +356,51 @@ class ProfileViewController: CustomNavigationBarViewController { if let profileUser = profileUser { switch profileUser { case .DiscoveredUserType(let discoveredUser): - self.navigationItem.title = discoveredUser.nickname + customNavigationItem.title = discoveredUser.nickname case .UserType(let user): - self.navigationItem.title = user.nickname - } + customNavigationItem.title = user.nickname - } else { - YepUserDefaults.nickname.bindAndFireListener("ProfileViewController.Title") { nickname in - self.navigationItem.title = nickname + if user.friendState == UserFriendState.Me.rawValue { + YepUserDefaults.nickname.bindListener("ProfileViewController.Title") { nickname in + self.customNavigationItem.title = nickname + } + } } } if let profileUser = profileUser { - let moreBarButtonItem = UIBarButtonItem(image: UIImage(named: "icon_more"), style: UIBarButtonItemStyle.Plain, target: self, action: "moreAction") - navigationItem.rightBarButtonItem = moreBarButtonItem - sayHiButton.setTitle(NSLocalizedString("Say Hi", comment: ""), forState: .Normal) - sayHiButton.layer.cornerRadius = 5 - sayHiButton.backgroundColor = UIColor.yepTintColor() - profileCollectionView.contentInset.bottom = sayHiView.bounds.height + switch profileUser { - } else { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { - userInfo(failureHandler: nil) { userInfo in + case .DiscoveredUserType(let discoveredUser): + profileUserIsMe = false - //println("userInfo: \(userInfo)") + case .UserType(let user): + if user.friendState == UserFriendState.Me.rawValue { + profileUserIsMe = true - if let introduction = userInfo["introduction"] as? String { - YepUserDefaults.introduction.value = introduction - } - - if let skillsData = userInfo["master_skills"] as? [JSONDictionary] { - self.masterSkills = skillsFromSkillsData(skillsData) - } - - if let skillsData = userInfo["learning_skills"] as? [JSONDictionary] { - self.learningSkills = skillsFromSkillsData(skillsData) - } - - if let providerInfo = userInfo["providers"] as? SocialWorkProviderInfo { - self.socialWorkProviderInfo = providerInfo - } - - dispatch_async(dispatch_get_main_queue()) { - self.profileCollectionView.reloadData() - } + } else { + profileUserIsMe = false } } - - sayHiView.hidden = true } } + func showSettings() { + self.performSegueWithIdentifier("showSettings", sender: self) + } + override func viewWillAppear(animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(true, animated: true) + + customNavigationBar.alpha = 1.0 + self.setNeedsStatusBarAppearanceUpdate() } - + override func preferredStatusBarStyle() -> UIStatusBarStyle { return UIStatusBarStyle.LightContent } @@ -275,7 +429,24 @@ class ProfileViewController: CustomNavigationBarViewController { vc.afterOAuthAction = { socialAccount in // 更新自己的 provider enabled 状态 let providerName = socialAccount.description.lowercaseString - self.socialWorkProviderInfo[providerName] = true +// self.socialWorkProviderInfo[providerName] = true + + let realm = Realm() + + if let + myUserID = YepUserDefaults.userID.value, + me = userWithUserID(myUserID, inRealm: realm) { + + for socialAccountProvider in me.socialAccountProviders { + if socialAccountProvider.name == providerName { + realm.write { + socialAccountProvider.enabled = true + } + + break + } + } + } } } @@ -320,10 +491,12 @@ class ProfileViewController: CustomNavigationBarViewController { // MARK: Actions func updateProfileCollectionView() { - self.profileCollectionView.reloadData() + profileCollectionView.collectionViewLayout.invalidateLayout() + profileCollectionView.reloadData() + profileCollectionView.layoutIfNeeded() } - @IBAction func sayHi(sender: UIButton) { + func sayHi() { if let profileUser = profileUser { @@ -338,8 +511,6 @@ class ProfileViewController: CustomNavigationBarViewController { let newUser = User() newUser.userID = discoveredUser.id - newUser.nickname = discoveredUser.nickname - newUser.avatarURLString = discoveredUser.avatarURLString newUser.friendState = UserFriendState.Stranger.rawValue @@ -350,19 +521,53 @@ class ProfileViewController: CustomNavigationBarViewController { stranger = newUser } - if let stranger = stranger { - if stranger.conversation == nil { + if let user = stranger { + + realm.beginWrite() + + // 更新用户信息 + + user.lastSignInAt = discoveredUser.lastSignInAt + + user.nickname = discoveredUser.nickname + + if let introduction = discoveredUser.introduction { + user.introduction = introduction + } + + user.avatarURLString = discoveredUser.avatarURLString + + // 更新技能 + + user.learningSkills.removeAll() + let learningUserSkills = userSkillsFromSkills(discoveredUser.learningSkills, inRealm: realm) + user.learningSkills.extend(learningUserSkills) + + user.masterSkills.removeAll() + let masterUserSkills = userSkillsFromSkills(discoveredUser.masterSkills, inRealm: realm) + user.masterSkills.extend(masterUserSkills) + + // 更新 Social Account Provider + + user.socialAccountProviders.removeAll() + let socialAccountProviders = userSocialAccountProvidersFromSocialAccountProviders(discoveredUser.socialAccountProviders) + user.socialAccountProviders.extend(socialAccountProviders) + + realm.commitWrite() + + + if user.conversation == nil { let newConversation = Conversation() newConversation.type = ConversationType.OneToOne.rawValue - newConversation.withFriend = stranger + newConversation.withFriend = user realm.beginWrite() realm.add(newConversation) realm.commitWrite() } - if let conversation = stranger.conversation { + if let conversation = user.conversation { performSegueWithIdentifier("showConversation", sender: conversation) NSNotificationCenter.defaultCenter().postNotificationName(YepNewMessagesReceivedNotification, object: nil) @@ -370,21 +575,25 @@ class ProfileViewController: CustomNavigationBarViewController { } case .UserType(let user): - if user.conversation == nil { - let newConversation = Conversation() - newConversation.type = ConversationType.OneToOne.rawValue - newConversation.withFriend = user + if user.friendState != UserFriendState.Me.rawValue { - realm.beginWrite() - realm.add(newConversation) - realm.commitWrite() - } + if user.conversation == nil { + let newConversation = Conversation() - if let conversation = user.conversation { - performSegueWithIdentifier("showConversation", sender: conversation) + newConversation.type = ConversationType.OneToOne.rawValue + newConversation.withFriend = user - NSNotificationCenter.defaultCenter().postNotificationName(YepNewMessagesReceivedNotification, object: nil) + realm.beginWrite() + realm.add(newConversation) + realm.commitWrite() + } + + if let conversation = user.conversation { + performSegueWithIdentifier("showConversation", sender: conversation) + + NSNotificationCenter.defaultCenter().postNotificationName(YepNewMessagesReceivedNotification, object: nil) + } } } } @@ -420,7 +629,7 @@ class ProfileViewController: CustomNavigationBarViewController { // MARK: UICollectionView -extension ProfileViewController: UICollectionViewDataSource, UICollectionViewDelegate { +extension ProfileViewController: UICollectionViewDataSource, UICollectionViewDelegate, UIGestureRecognizerDelegate { enum ProfileSection: Int { case Header = 0 @@ -430,7 +639,6 @@ extension ProfileViewController: UICollectionViewDataSource, UICollectionViewDel case SeparationLine case SocialAccount } - func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { return 6 @@ -451,11 +659,10 @@ extension ProfileViewController: UICollectionViewDataSource, UICollectionViewDel case .UserType(let user): return Int(user.masterSkills.count) } - - } else { - return masterSkills.count } + return 0 + case ProfileSection.Learning.rawValue: if let profileUser = profileUser { @@ -465,11 +672,10 @@ extension ProfileViewController: UICollectionViewDataSource, UICollectionViewDel case .UserType(let user): return Int(user.learningSkills.count) } - - } else { - return learningSkills.count } + return 0 + case ProfileSection.Footer.rawValue: return 1 @@ -498,9 +704,6 @@ extension ProfileViewController: UICollectionViewDataSource, UICollectionViewDel case .UserType(let user): cell.configureWithUser(user) } - - } else { - cell.configureWithMyInfo() } return cell @@ -517,10 +720,6 @@ extension ProfileViewController: UICollectionViewDataSource, UICollectionViewDel let userSkill = user.masterSkills[indexPath.item] cell.skillLabel.text = userSkill.localName } - - } else { - let skill = masterSkills[indexPath.item] - cell.skillLabel.text = skill.localName } return cell @@ -537,13 +736,8 @@ extension ProfileViewController: UICollectionViewDataSource, UICollectionViewDel let userSkill = user.learningSkills[indexPath.item] cell.skillLabel.text = userSkill.localName } - - } else { - let skill = learningSkills[indexPath.item] - cell.skillLabel.text = skill.localName } - return cell case ProfileSection.Footer.rawValue: @@ -565,7 +759,7 @@ extension ProfileViewController: UICollectionViewDataSource, UICollectionViewDel if socialAccount == .Github { let cell = collectionView.dequeueReusableCellWithReuseIdentifier(socialAccountGithubCellIdentifier, forIndexPath: indexPath) as! ProfileSocialAccountGithubCell - cell.configureWithProfileUser(profileUser, orSocialWorkProviderInfo: socialWorkProviderInfo, socialAccount: socialAccount, githubWork: githubWork, completion: { githubWork in + cell.configureWithProfileUser(profileUser, socialAccount: socialAccount, githubWork: githubWork, completion: { githubWork in self.githubWork = githubWork }) @@ -593,10 +787,12 @@ extension ProfileViewController: UICollectionViewDataSource, UICollectionViewDel break } - cell.configureWithProfileUser(profileUser, orSocialWorkProviderInfo: socialWorkProviderInfo, socialAccount: socialAccount, socialWork: socialWork, completion: { socialWork in + cell.configureWithProfileUser(profileUser, socialAccount: socialAccount, socialWork: socialWork, completion: { socialWork in switch socialWork { + case .Dribbble(let dribbbleWork): self.dribbbleWork = dribbbleWork + case .Instagram(let instagramWork): self.instagramWork = instagramWork } @@ -623,8 +819,6 @@ extension ProfileViewController: UICollectionViewDataSource, UICollectionViewDel let header = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: sectionHeaderIdentifier, forIndexPath: indexPath) as! ProfileSectionHeaderReusableView - var needEnabledTapAction = (profileUser == nil) - switch indexPath.section { case ProfileSection.Master.rawValue: @@ -635,11 +829,9 @@ extension ProfileViewController: UICollectionViewDataSource, UICollectionViewDel default: header.titleLabel.text = "" - - needEnabledTapAction = false } - if needEnabledTapAction { + if profileUserIsMe { header.tapAction = { let storyboard = UIStoryboard(name: "Intro", bundle: nil) @@ -702,56 +894,80 @@ extension ProfileViewController: UICollectionViewDataSource, UICollectionViewDel switch indexPath.section { case ProfileSection.Header.rawValue: - return CGSizeMake(collectionViewWidth, collectionViewWidth * profileAvatarAspectRatio) + return CGSize(width: collectionViewWidth, height: collectionViewWidth * profileAvatarAspectRatio) case ProfileSection.Master.rawValue: var skillLocalName = "" if let profileUser = profileUser { switch profileUser { + case .DiscoveredUserType(let discoveredUser): skillLocalName = discoveredUser.masterSkills[indexPath.item].localName + case .UserType(let user): let userSkill = user.masterSkills[indexPath.item] skillLocalName = userSkill.localName } - - } else { - skillLocalName = masterSkills[indexPath.item].localName } let rect = skillLocalName.boundingRectWithSize(CGSize(width: CGFloat(FLT_MAX), height: SkillCell.height), options: .UsesLineFragmentOrigin | .UsesFontLeading, attributes: skillTextAttributes, context: nil) - return CGSizeMake(rect.width + 24, SkillCell.height) + return CGSize(width: rect.width + 24, height: SkillCell.height) case ProfileSection.Learning.rawValue: var skillLocalName = "" if let profileUser = profileUser { switch profileUser { + case .DiscoveredUserType(let discoveredUser): skillLocalName = discoveredUser.learningSkills[indexPath.item].localName + case .UserType(let user): let userSkill = user.learningSkills[indexPath.item] skillLocalName = userSkill.localName } - - } else { - skillLocalName = learningSkills[indexPath.item].localName } let rect = skillLocalName.boundingRectWithSize(CGSize(width: CGFloat(FLT_MAX), height: SkillCell.height), options: .UsesLineFragmentOrigin | .UsesFontLeading, attributes: skillTextAttributes, context: nil) - return CGSizeMake(rect.width + 24, SkillCell.height) + return CGSize(width: rect.width + 24, height: SkillCell.height) case ProfileSection.Footer.rawValue: - return CGSizeMake(collectionViewWidth, footerCellHeight) + return CGSize(width: collectionViewWidth, height: footerCellHeight) case ProfileSection.SeparationLine.rawValue: - return CGSizeMake(collectionViewWidth, 1) + var enabled = true + + if let profileUser = profileUser { + switch profileUser { + + case .DiscoveredUserType(let discoveredUser): + enabled = discoveredUser.socialAccountProviders.filter({ socialAccountProvider in + socialAccountProvider.enabled + }).count > 0 + + case .UserType(let user): + if user.friendState != UserFriendState.Me.rawValue { + enabled = user.socialAccountProviders.filter("enabled = true").count > 0 + } + } + } + + return enabled ? CGSize(width: collectionViewWidth, height: 1) : CGSizeZero case ProfileSection.SocialAccount.rawValue: - return CGSizeMake(collectionViewWidth, 40) + var enabled = true + + // 对于他人,只看其绑定的 SocialAccount + if !profileUserIsMe, let profileUser = profileUser { + if let socialAccount = SocialAccount(rawValue: indexPath.row) { + enabled = profileUser.enabledSocialAccount(socialAccount) + } + } + + return enabled ? CGSize(width: collectionViewWidth, height: 40) : CGSizeZero default: return CGSizeZero @@ -764,12 +980,12 @@ extension ProfileViewController: UICollectionViewDataSource, UICollectionViewDel return CGSizeMake(collectionViewWidth, 40) } else { - return CGSizeMake(collectionViewWidth, 0) + return CGSizeZero } } func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize { - return CGSizeMake(collectionViewWidth, 0) + return CGSizeZero } func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { @@ -783,45 +999,19 @@ extension ProfileViewController: UICollectionViewDataSource, UICollectionViewDel if let socialAccount = SocialAccount(rawValue: indexPath.item) { - let providerName = socialAccount.description.lowercaseString - if let profileUser = profileUser { - switch profileUser { - - case .DiscoveredUserType(let discoveredUser): - for provider in discoveredUser.socialAccountProviders { - if (provider.name == providerName) && provider.enabled { - performSegueWithIdentifier("showSocialWork\(socialAccount)", sender: indexPath.item) + if profileUser.enabledSocialAccount(socialAccount) { + performSegueWithIdentifier("showSocialWork\(socialAccount)", sender: indexPath.item) - break - } - } - - case .UserType(let user): - for provider in user.socialAccountProviders { - if (provider.name == providerName) && provider.enabled { - performSegueWithIdentifier("showSocialWork\(socialAccount)", sender: indexPath.item) - - break - } + } else { + if profileUserIsMe { + performSegueWithIdentifier("presentOAuth", sender: indexPath.item) } } - - } else { - if let enabled = socialWorkProviderInfo[providerName] { - if enabled { - performSegueWithIdentifier("showSocialWork\(socialAccount)", sender: indexPath.item) - - return - } - } - - performSegueWithIdentifier("presentOAuth", sender: indexPath.item) } } } - } } diff --git a/Yep/ViewControllers/Register/RegisterPickSkillsViewController.swift b/Yep/ViewControllers/Register/RegisterPickSkillsViewController.swift index 89254b45..65230e0d 100644 --- a/Yep/ViewControllers/Register/RegisterPickSkillsViewController.swift +++ b/Yep/ViewControllers/Register/RegisterPickSkillsViewController.swift @@ -8,7 +8,7 @@ import UIKit -class RegisterPickSkillsViewController: UIViewController { +class RegisterPickSkillsViewController: BaseViewController { var isRegister = true var afterChangeSkillsAction: ((masterSkills: [Skill], learningSkills: [Skill]) -> Void)? @@ -35,7 +35,6 @@ class RegisterPickSkillsViewController: UIViewController { var skillCategories: [SkillCategory]? - lazy var selectSkillsTransitionManager = RegisterPickSkillsSelectSkillsTransitionManager() override func viewDidLoad() { @@ -67,6 +66,10 @@ class RegisterPickSkillsViewController: UIViewController { // MARK: Actions + func updateSkillsCollectionView() { + skillsCollectionView.reloadData() + } + @IBAction func saveSkills(sender: AnyObject) { let addSkillsGroup = dispatch_group_create() @@ -194,7 +197,7 @@ class RegisterPickSkillsViewController: UIViewController { break } - self.skillsCollectionView.reloadData() + self.updateSkillsCollectionView() return success } diff --git a/Yep/ViewControllers/Register/RegisterSelectSkillsViewController.swift b/Yep/ViewControllers/Register/RegisterSelectSkillsViewController.swift index 63271853..d95e87ab 100644 --- a/Yep/ViewControllers/Register/RegisterSelectSkillsViewController.swift +++ b/Yep/ViewControllers/Register/RegisterSelectSkillsViewController.swift @@ -110,11 +110,11 @@ class RegisterSelectSkillsViewController: UIViewController { allSkillCategories(failureHandler: { (reason, errorMessage) -> Void in defaultFailureHandler(reason, errorMessage) - }, completion: { skillCategories -> Void in + }, completion: { skillCategories in self.skillCategories = skillCategories dispatch_async(dispatch_get_main_queue()) { - self.skillsCollectionView.reloadData() + self.updateSkillsCollectionView() } }) } @@ -126,6 +126,14 @@ class RegisterSelectSkillsViewController: UIViewController { skillsCollectionViewBottomConstrain.constant = -CGRectGetHeight(skillsCollectionView.bounds) } + // MARK: Actions + + func updateSkillsCollectionView() { + skillsCollectionView.collectionViewLayout.invalidateLayout() + skillsCollectionView.reloadData() + skillsCollectionView.layoutIfNeeded() + } + func dismiss() { presentingViewController?.dismissViewControllerAnimated(true, completion: nil) } diff --git a/Yep/ViewControllers/Register/RegisterVerifyMobileViewController.swift b/Yep/ViewControllers/Register/RegisterVerifyMobileViewController.swift index 6732b427..c6cb8ba8 100644 --- a/Yep/ViewControllers/Register/RegisterVerifyMobileViewController.swift +++ b/Yep/ViewControllers/Register/RegisterVerifyMobileViewController.swift @@ -84,7 +84,7 @@ class RegisterVerifyMobileViewController: UIViewController { func callMe() { nextButton.setTitle(NSLocalizedString("Calling", comment: ""), forState: .Normal) - resendVoiceVerifyCode(ofMobile: mobile, withAreaCode: areaCode, failureHandler: { (reason, errorMessage) -> () in + sendVerifyCodeOfMobile(mobile, withAreaCode: areaCode, useMethod: .Call, failureHandler: { (reason, errorMessage) in defaultFailureHandler(reason, errorMessage) if let errorMessage = errorMessage { diff --git a/Yep/ViewControllers/SearchUsers/SearchUsersViewController.swift b/Yep/ViewControllers/SearchUsers/SearchUsersViewController.swift index c13e69be..51caeda5 100644 --- a/Yep/ViewControllers/SearchUsers/SearchUsersViewController.swift +++ b/Yep/ViewControllers/SearchUsers/SearchUsersViewController.swift @@ -8,7 +8,7 @@ import UIKit -class SearchUsersViewController: UIViewController { +class SearchUsersViewController: BaseViewController { @IBOutlet weak var searchedUsersTableView: UITableView! diff --git a/Yep/ViewControllers/SearchedUsers/SearchedUsersViewController.swift b/Yep/ViewControllers/SearchedUsers/SearchedUsersViewController.swift new file mode 100644 index 00000000..554f3ebe --- /dev/null +++ b/Yep/ViewControllers/SearchedUsers/SearchedUsersViewController.swift @@ -0,0 +1,107 @@ +// +// SearchedUsersViewController.swift +// Yep +// +// Created by NIX on 15/5/22. +// Copyright (c) 2015年 Catch Inc. All rights reserved. +// + +import UIKit + +class SearchedUsersViewController: BaseViewController { + + var mobile = "18602354812" + + @IBOutlet weak var searchedUsersTableView: UITableView! + + var searchedUsers = [DiscoveredUser]() { + didSet { + updateSearchedUsersTableView() + } + } + + let cellIdentifier = "ContactsCell" + + override func viewDidLoad() { + super.viewDidLoad() + + title = NSLocalizedString("Searched Users", comment: "") + + searchedUsersTableView.registerNib(UINib(nibName: cellIdentifier, bundle: nil), forCellReuseIdentifier: cellIdentifier) + searchedUsersTableView.rowHeight = 80 + + + searchUsersByQ(mobile, failureHandler: { (reason, errorMessage) in + defaultFailureHandler(reason, errorMessage) + + }, completion: { users in + dispatch_async(dispatch_get_main_queue()) { + self.searchedUsers = users + } + }) + } + + // MARK: Actions + + func updateSearchedUsersTableView() { + searchedUsersTableView.reloadData() + } + + // MARK: Navigation + + override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { + if segue.identifier == "showProfile" { + if let indexPath = sender as? NSIndexPath { + let discoveredUser = searchedUsers[indexPath.row] + + let vc = segue.destinationViewController as! ProfileViewController + + if discoveredUser.id != YepUserDefaults.userID.value { + vc.profileUser = ProfileUser.DiscoveredUserType(discoveredUser) + } + + vc.setBackButtonWithTitle() + + vc.hidesBottomBarWhenPushed = true + } + } + } +} + +// MARK: UITableViewDataSource, UITableViewDelegate + +extension SearchedUsersViewController: UITableViewDataSource, UITableViewDelegate { + + func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return searchedUsers.count + } + + func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! ContactsCell + + let discoveredUser = searchedUsers[indexPath.row] + + let radius = min(CGRectGetWidth(cell.avatarImageView.bounds), CGRectGetHeight(cell.avatarImageView.bounds)) * 0.5 + + let avatarURLString = discoveredUser.avatarURLString + AvatarCache.sharedInstance.roundAvatarWithAvatarURLString(avatarURLString, withRadius: radius) { roundImage in + dispatch_async(dispatch_get_main_queue()) { + cell.avatarImageView.image = roundImage + } + } + + cell.joinedDateLabel.text = discoveredUser.introduction + let distance = discoveredUser.distance.format(".1") + cell.lastTimeSeenLabel.text = "\(distance)km | \(discoveredUser.lastSignInAt.timeAgo)" + + cell.nameLabel.text = discoveredUser.nickname + + return cell + } + + func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { + tableView.deselectRowAtIndexPath(indexPath, animated: true) + + performSegueWithIdentifier("showProfile", sender: indexPath) + } +} \ No newline at end of file diff --git a/Yep/ViewControllers/Settings/SettingsViewController.swift b/Yep/ViewControllers/Settings/SettingsViewController.swift index 38906a76..de63762b 100644 --- a/Yep/ViewControllers/Settings/SettingsViewController.swift +++ b/Yep/ViewControllers/Settings/SettingsViewController.swift @@ -8,7 +8,7 @@ import UIKit -class SettingsViewController: UIViewController { +class SettingsViewController: BaseViewController { @IBOutlet weak var settingsTableView: UITableView! @@ -32,6 +32,8 @@ class SettingsViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() + + animatedOnNavigationBar = false title = NSLocalizedString("Settings", comment: "") diff --git a/Yep/ViewControllers/SkillHome/SkillHomeViewController.swift b/Yep/ViewControllers/SkillHome/SkillHomeViewController.swift index 1cb7a98e..3db21fec 100644 --- a/Yep/ViewControllers/SkillHome/SkillHomeViewController.swift +++ b/Yep/ViewControllers/SkillHome/SkillHomeViewController.swift @@ -26,18 +26,18 @@ class SkillHomeViewController: CustomNavigationBarViewController { let cellIdentifier = "ContactsCell" - lazy var masterTableView: UITableView = { + lazy var masterTableView: YepChildScrollView = { - var tempTableView = UITableView(frame: CGRectZero) + var tempTableView = YepChildScrollView(frame: CGRectZero) return tempTableView; }() - lazy var learningtTableView: UITableView = { + lazy var learningtTableView: YepChildScrollView = { - var tempTableView = UITableView(frame: CGRectZero) + var tempTableView = YepChildScrollView(frame: CGRectZero) return tempTableView; @@ -50,6 +50,11 @@ class SkillHomeViewController: CustomNavigationBarViewController { } } + override func viewWillAppear(animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(false, animated: true) + } + var isFirstAppear = true var state: SkillHomeState = .Master { @@ -71,7 +76,7 @@ class SkillHomeViewController: CustomNavigationBarViewController { } } - @IBOutlet weak var skillHomeScrollView: UIScrollView! + @IBOutlet weak var skillHomeScrollView: YepScrollView! @IBOutlet weak var headerView: SkillHomeHeaderView! @@ -80,6 +85,7 @@ class SkillHomeViewController: CustomNavigationBarViewController { var discoveredMasterUsers = [DiscoveredUser]() var discoveredLearningUsers = [DiscoveredUser]() + override func viewDidLoad() { super.viewDidLoad() @@ -90,7 +96,6 @@ class SkillHomeViewController: CustomNavigationBarViewController { masterTableView.delegate = self masterTableView.tag = SkillHomeState.Master.hashValue - learningtTableView.registerNib(UINib(nibName: cellIdentifier, bundle: nil), forCellReuseIdentifier: cellIdentifier) learningtTableView.rowHeight = 80 learningtTableView.dataSource = self @@ -106,13 +111,30 @@ class SkillHomeViewController: CustomNavigationBarViewController { headerView.masterButton.addTarget(self, action: "changeToMaster", forControlEvents: UIControlEvents.TouchUpInside) headerView.learningButton.addTarget(self, action: "changeToLearning", forControlEvents: UIControlEvents.TouchUpInside) + automaticallyAdjustsScrollViewInsets = false + skillHomeScrollView.addSubview(masterTableView) skillHomeScrollView.addSubview(learningtTableView) skillHomeScrollView.pagingEnabled = true skillHomeScrollView.delegate = self + skillHomeScrollView.bounces = false + + if let gestures = navigationController?.view.gestureRecognizers { + for recognizer in gestures + { + if recognizer.isKindOfClass(UIScreenEdgePanGestureRecognizer) + { + skillHomeScrollView.panGestureRecognizer.requireGestureRecognizerToFail(recognizer as! UIScreenEdgePanGestureRecognizer) + println("Require UIScreenEdgePanGestureRecognizer to failed") + break + } + } + } + customTitleView() + // Do any additional setup after loading the view. } @@ -183,7 +205,9 @@ class SkillHomeViewController: CustomNavigationBarViewController { func scrollViewDidEndDecelerating(scrollView: UIScrollView) { - if scrollView.contentOffset.x >= scrollView.contentSize.width / 2 { + println("Did end decelerating \(skillHomeScrollView.contentOffset.x)") + + if skillHomeScrollView.contentOffset.x + 10 >= skillHomeScrollView.contentSize.width / 2.0 { state = .Learning @@ -238,9 +262,13 @@ class SkillHomeViewController: CustomNavigationBarViewController { let vc = segue.destinationViewController as! ProfileViewController - vc.profileUser = ProfileUser.DiscoveredUserType(discoveredUser) + if discoveredUser.id != YepUserDefaults.userID.value { + vc.profileUser = ProfileUser.DiscoveredUserType(discoveredUser) + } vc.hidesBottomBarWhenPushed = true + + vc.setBackButtonWithTitle() } } } @@ -277,8 +305,10 @@ extension SkillHomeViewController: UITableViewDelegate, UITableViewDataSource{ } } - cell.joinedDateLabel.text = discoveredUser.createdAt.timeAgo - cell.lastTimeSeenLabel.text = discoveredUser.lastSignInAt.timeAgo + cell.joinedDateLabel.text = discoveredUser.introduction + + let distance = discoveredUser.distance.format(".1") + cell.lastTimeSeenLabel.text = "\(distance) km | \(discoveredUser.lastSignInAt.timeAgo)" cell.nameLabel.text = discoveredUser.nickname diff --git a/Yep/ViewControllers/SocialWorks/SocialWorkDribbbleViewController.swift b/Yep/ViewControllers/SocialWorks/SocialWorkDribbbleViewController.swift index 6c4fec98..edac1fad 100644 --- a/Yep/ViewControllers/SocialWorks/SocialWorkDribbbleViewController.swift +++ b/Yep/ViewControllers/SocialWorks/SocialWorkDribbbleViewController.swift @@ -9,7 +9,7 @@ import UIKit import Kingfisher -class SocialWorkDribbbleViewController: UIViewController { +class SocialWorkDribbbleViewController: BaseViewController { var socialAccount: SocialAccount? var profileUser: ProfileUser? @@ -36,10 +36,13 @@ class SocialWorkDribbbleViewController: UIViewController { updateDribbbleCollectionView() } } + override func viewDidLoad() { super.viewDidLoad() + animatedOnNavigationBar = false + if let socialAccount = socialAccount { let accountImageView = UIImageView(image: UIImage(named: socialAccount.iconName)!) accountImageView.tintColor = socialAccount.tintColor @@ -71,9 +74,6 @@ class SocialWorkDribbbleViewController: UIViewController { case .UserType(let user): userID = user.userID } - - } else { - userID = YepUserDefaults.userID.value } if let userID = userID { diff --git a/Yep/ViewControllers/SocialWorks/SocialWorkGithubViewController.swift b/Yep/ViewControllers/SocialWorks/SocialWorkGithubViewController.swift index 2a325df9..c5bd5451 100644 --- a/Yep/ViewControllers/SocialWorks/SocialWorkGithubViewController.swift +++ b/Yep/ViewControllers/SocialWorks/SocialWorkGithubViewController.swift @@ -8,7 +8,7 @@ import UIKit -class SocialWorkGithubViewController: UIViewController { +class SocialWorkGithubViewController: BaseViewController { var socialAccount: SocialAccount? var profileUser: ProfileUser? @@ -65,7 +65,8 @@ class SocialWorkGithubViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = UIColor.whiteColor() + + animatedOnNavigationBar = false if let socialAccount = socialAccount { let accountImageView = UIImageView(image: UIImage(named: socialAccount.iconName)!) @@ -100,9 +101,6 @@ class SocialWorkGithubViewController: UIViewController { case .UserType(let user): userID = user.userID } - - } else { - userID = YepUserDefaults.userID.value } if let userID = userID { diff --git a/Yep/ViewControllers/SocialWorks/SocialWorkInstagramViewController.swift b/Yep/ViewControllers/SocialWorks/SocialWorkInstagramViewController.swift index 45044305..d9b4baab 100644 --- a/Yep/ViewControllers/SocialWorks/SocialWorkInstagramViewController.swift +++ b/Yep/ViewControllers/SocialWorks/SocialWorkInstagramViewController.swift @@ -8,7 +8,7 @@ import UIKit -class SocialWorkInstagramViewController: UIViewController { +class SocialWorkInstagramViewController: BaseViewController { var socialAccount: SocialAccount? var profileUser: ProfileUser? @@ -38,6 +38,8 @@ class SocialWorkInstagramViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() + + animatedOnNavigationBar = false if let socialAccount = socialAccount { let accountImageView = UIImageView(image: UIImage(named: socialAccount.iconName)!) @@ -69,9 +71,6 @@ class SocialWorkInstagramViewController: UIViewController { case .UserType(let user): userID = user.userID } - - } else { - userID = YepUserDefaults.userID.value } if let userID = userID { diff --git a/Yep/ViewControllers/TabBar/YepTabBarController.swift b/Yep/ViewControllers/TabBar/YepTabBarController.swift index 92ca74a8..9aa6895b 100644 --- a/Yep/ViewControllers/TabBar/YepTabBarController.swift +++ b/Yep/ViewControllers/TabBar/YepTabBarController.swift @@ -12,16 +12,16 @@ class YepTabBarController: UITabBarController { override func viewDidLoad() { super.viewDidLoad() - + self.view.backgroundColor = UIColor.whiteColor() // 将 UITabBarItem 的 image 下移一些,也不显示 title 了 - if let items = tabBar.items as? [UITabBarItem] { - for item in items { - item.imageInsets = UIEdgeInsetsMake(6, 0, -6, 0) - item.title = nil - } - } +// if let items = tabBar.items as? [UITabBarItem] { +// for item in items { +// item.imageInsets = UIEdgeInsetsMake(6, 0, -6, 0) +// item.title = nil +// } +// } } } diff --git a/Yep/Views/Cells/AddFriendMore/AddFriendMoreCell.swift b/Yep/Views/Cells/AddFriendMore/AddFriendMoreCell.swift new file mode 100644 index 00000000..540e54af --- /dev/null +++ b/Yep/Views/Cells/AddFriendMore/AddFriendMoreCell.swift @@ -0,0 +1,30 @@ +// +// AddFriendMoreCell.swift +// Yep +// +// Created by NIX on 15/5/19. +// Copyright (c) 2015年 Catch Inc. All rights reserved. +// + +import UIKit + +class AddFriendMoreCell: UITableViewCell { + + @IBOutlet weak var annotationLabel: UILabel! + + @IBOutlet weak var accessoryImageView: UIImageView! + + + override func awakeFromNib() { + super.awakeFromNib() + + accessoryImageView.tintColor = UIColor.lightGrayColor() + } + + override func setSelected(selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + } + +} diff --git a/Yep/Views/Cells/AddFriendMore/AddFriendMoreCell.xib b/Yep/Views/Cells/AddFriendMore/AddFriendMoreCell.xib new file mode 100644 index 00000000..008e7d78 --- /dev/null +++ b/Yep/Views/Cells/AddFriendMore/AddFriendMoreCell.xib @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Yep/Views/Cells/AddFriendSearch/AddFriendSearchCell.swift b/Yep/Views/Cells/AddFriendSearch/AddFriendSearchCell.swift new file mode 100644 index 00000000..b03ab27d --- /dev/null +++ b/Yep/Views/Cells/AddFriendSearch/AddFriendSearchCell.swift @@ -0,0 +1,26 @@ +// +// AddFriendSearchCell.swift +// Yep +// +// Created by NIX on 15/5/19. +// Copyright (c) 2015年 Catch Inc. All rights reserved. +// + +import UIKit + +class AddFriendSearchCell: UITableViewCell { + + @IBOutlet weak var searchTextField: UITextField! + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + + override func setSelected(selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + } + +} diff --git a/Yep/Views/Cells/AddFriendSearch/AddFriendSearchCell.xib b/Yep/Views/Cells/AddFriendSearch/AddFriendSearchCell.xib new file mode 100644 index 00000000..0f1441d3 --- /dev/null +++ b/Yep/Views/Cells/AddFriendSearch/AddFriendSearchCell.xib @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Yep/Views/Cells/ChatLeftAudio/ChatLeftAudioCell.swift b/Yep/Views/Cells/ChatLeftAudio/ChatLeftAudioCell.swift index 80bfc383..c88e11a9 100644 --- a/Yep/Views/Cells/ChatLeftAudio/ChatLeftAudioCell.swift +++ b/Yep/Views/Cells/ChatLeftAudio/ChatLeftAudioCell.swift @@ -10,7 +10,7 @@ import UIKit class ChatLeftAudioCell: UICollectionViewCell { - var message: Message! + var message: Message? var audioPlayedDuration: Double = 0 { willSet { @@ -43,6 +43,9 @@ class ChatLeftAudioCell: UICollectionViewCell { @IBOutlet weak var playButton: UIButton! + typealias AudioBubbleTapAction = (message: Message?) -> Void + var audioBubbleTapAction: AudioBubbleTapAction? + override func awakeFromNib() { super.awakeFromNib() @@ -56,12 +59,22 @@ class ChatLeftAudioCell: UICollectionViewCell { playButton.userInteractionEnabled = false playButton.tintColor = UIColor.darkGrayColor() + + bubbleImageView.userInteractionEnabled = true + let tap = UITapGestureRecognizer(target: self, action: "tapMediaView") + bubbleImageView.addGestureRecognizer(tap) } - func configureWithMessage(message: Message, audioPlayedDuration: Double) { + func tapMediaView() { + audioBubbleTapAction?(message: message) + } + + func configureWithMessage(message: Message, audioPlayedDuration: Double, audioBubbleTapAction: AudioBubbleTapAction?) { self.message = message + self.audioBubbleTapAction = audioBubbleTapAction + self.audioPlayedDuration = audioPlayedDuration if let sender = message.fromFriend { @@ -76,42 +89,46 @@ class ChatLeftAudioCell: UICollectionViewCell { } func updateAudioInfoViews() { - if !message.metaData.isEmpty { + + if let message = message { - if let data = message.metaData.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) { - if let metaDataDict = decodeJSON(data) { + if !message.metaData.isEmpty { - if let audioSamples = metaDataDict["audio_samples"] as? [CGFloat] { - sampleViewWidthConstraint.constant = CGFloat(audioSamples.count) * (YepConfig.audioSampleWidth() + YepConfig.audioSampleGap()) - YepConfig.audioSampleGap() // 最后最后一个 gap 不要 - - sampleViewWidthConstraint.constant = max(YepConfig.minMessageSampleViewWidth, sampleViewWidthConstraint.constant) - - sampleView.samples = audioSamples + if let data = message.metaData.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) { + if let metaDataDict = decodeJSON(data) { - if let audioDuration = metaDataDict["audio_duration"] as? Double { - audioDurationLabel.text = NSString(format: "%.1f\"", audioDuration) as String + if let audioSamples = metaDataDict["audio_samples"] as? [CGFloat] { + sampleViewWidthConstraint.constant = CGFloat(audioSamples.count) * (YepConfig.audioSampleWidth() + YepConfig.audioSampleGap()) - YepConfig.audioSampleGap() // 最后最后一个 gap 不要 - sampleView.progress = CGFloat(audioPlayedDuration / audioDuration) + sampleViewWidthConstraint.constant = max(YepConfig.minMessageSampleViewWidth, sampleViewWidthConstraint.constant) - } else { - sampleView.progress = 0 + sampleView.samples = audioSamples + + if let audioDuration = metaDataDict["audio_duration"] as? Double { + audioDurationLabel.text = NSString(format: "%.1f\"", audioDuration) as String + + sampleView.progress = CGFloat(audioPlayedDuration / audioDuration) + + } else { + sampleView.progress = 0 + } } } + + } else { + sampleViewWidthConstraint.constant = 15 * (YepConfig.audioSampleWidth() + YepConfig.audioSampleGap()) + audioDurationLabel.text = "" } - - } else { - sampleViewWidthConstraint.constant = 15 * (YepConfig.audioSampleWidth() + YepConfig.audioSampleGap()) - audioDurationLabel.text = "" } - } - if let audioPlayer = YepAudioService.sharedManager.audioPlayer { - if audioPlayer.playing { - if let playingMessage = YepAudioService.sharedManager.playingMessage { - if message == playingMessage { - playing = true - - return + if let audioPlayer = YepAudioService.sharedManager.audioPlayer { + if audioPlayer.playing { + if let playingMessage = YepAudioService.sharedManager.playingMessage { + if message.messageID == playingMessage.messageID { + playing = true + + return + } } } } diff --git a/Yep/Views/Cells/ChatLeftImage/ChatLeftImageCell.swift b/Yep/Views/Cells/ChatLeftImage/ChatLeftImageCell.swift index 45241d20..f8a398e5 100644 --- a/Yep/Views/Cells/ChatLeftImage/ChatLeftImageCell.swift +++ b/Yep/Views/Cells/ChatLeftImage/ChatLeftImageCell.swift @@ -16,16 +16,30 @@ class ChatLeftImageCell: UICollectionViewCell { @IBOutlet weak var messageImageView: UIImageView! @IBOutlet weak var messageImageViewWidthConstrint: NSLayoutConstraint! - + + typealias MediaTapAction = () -> Void + var mediaTapAction: MediaTapAction? + override func awakeFromNib() { super.awakeFromNib() avatarImageViewWidthConstraint.constant = YepConfig.chatCellAvatarSize() messageImageView.tintColor = UIColor.leftBubbleTintColor() + + messageImageView.userInteractionEnabled = true + let tap = UITapGestureRecognizer(target: self, action: "tapMediaView") + messageImageView.addGestureRecognizer(tap) } - func configureWithMessage(message: Message, messageImagePreferredWidth: CGFloat, messageImagePreferredHeight: CGFloat, messageImagePreferredAspectRatio: CGFloat) { + func tapMediaView() { + mediaTapAction?() + } + + func configureWithMessage(message: Message, messageImagePreferredWidth: CGFloat, messageImagePreferredHeight: CGFloat, messageImagePreferredAspectRatio: CGFloat, mediaTapAction: MediaTapAction?) { + + self.mediaTapAction = mediaTapAction + if let sender = message.fromFriend { AvatarCache.sharedInstance.roundAvatarOfUser(sender, withRadius: YepConfig.chatCellAvatarSize() * 0.5) { roundImage in dispatch_async(dispatch_get_main_queue()) { diff --git a/Yep/Views/Cells/ChatLeftVideo/ChatLeftVideoCell.swift b/Yep/Views/Cells/ChatLeftVideo/ChatLeftVideoCell.swift index 2ba9f0c2..17d35a0c 100644 --- a/Yep/Views/Cells/ChatLeftVideo/ChatLeftVideoCell.swift +++ b/Yep/Views/Cells/ChatLeftVideo/ChatLeftVideoCell.swift @@ -18,15 +18,29 @@ class ChatLeftVideoCell: UICollectionViewCell { @IBOutlet weak var playImageView: UIImageView! + typealias MediaTapAction = () -> Void + var mediaTapAction: MediaTapAction? + override func awakeFromNib() { super.awakeFromNib() avatarImageViewWidthConstraint.constant = YepConfig.chatCellAvatarSize() thumbnailImageView.tintColor = UIColor.leftBubbleTintColor() + + thumbnailImageView.userInteractionEnabled = true + let tap = UITapGestureRecognizer(target: self, action: "tapMediaView") + thumbnailImageView.addGestureRecognizer(tap) } - func configureWithMessage(message: Message, messageImagePreferredWidth: CGFloat, messageImagePreferredHeight: CGFloat, messageImagePreferredAspectRatio: CGFloat) { + func tapMediaView() { + mediaTapAction?() + } + + func configureWithMessage(message: Message, messageImagePreferredWidth: CGFloat, messageImagePreferredHeight: CGFloat, messageImagePreferredAspectRatio: CGFloat, mediaTapAction: MediaTapAction?) { + + self.mediaTapAction = mediaTapAction + if let sender = message.fromFriend { AvatarCache.sharedInstance.roundAvatarOfUser(sender, withRadius: YepConfig.chatCellAvatarSize() * 0.5) { roundImage in dispatch_async(dispatch_get_main_queue()) { diff --git a/Yep/Views/Cells/ChatRightAudio/ChatRightAudioCell.swift b/Yep/Views/Cells/ChatRightAudio/ChatRightAudioCell.swift index adf82d84..d2052750 100644 --- a/Yep/Views/Cells/ChatRightAudio/ChatRightAudioCell.swift +++ b/Yep/Views/Cells/ChatRightAudio/ChatRightAudioCell.swift @@ -10,7 +10,7 @@ import UIKit class ChatRightAudioCell: UICollectionViewCell { - var message: Message! + var message: Message? var audioPlayedDuration: Double = 0 { willSet { @@ -42,6 +42,9 @@ class ChatRightAudioCell: UICollectionViewCell { @IBOutlet weak var playButton: UIButton! + typealias AudioBubbleTapAction = (message: Message?) -> Void + var audioBubbleTapAction: AudioBubbleTapAction? + override func awakeFromNib() { super.awakeFromNib() @@ -54,12 +57,22 @@ class ChatRightAudioCell: UICollectionViewCell { audioDurationLabel.textColor = UIColor.whiteColor() playButton.userInteractionEnabled = false + + bubbleImageView.userInteractionEnabled = true + let tap = UITapGestureRecognizer(target: self, action: "tapMediaView") + bubbleImageView.addGestureRecognizer(tap) } - func configureWithMessage(message: Message, audioPlayedDuration: Double) { + func tapMediaView() { + audioBubbleTapAction?(message: message) + } + + func configureWithMessage(message: Message, audioPlayedDuration: Double, audioBubbleTapAction: AudioBubbleTapAction?) { self.message = message + self.audioBubbleTapAction = audioBubbleTapAction + self.audioPlayedDuration = audioPlayedDuration if let sender = message.fromFriend { @@ -74,47 +87,51 @@ class ChatRightAudioCell: UICollectionViewCell { } func updateAudioInfoViews() { - if !message.metaData.isEmpty { - if let data = message.metaData.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) { - if let metaDataDict = decodeJSON(data) { + if let message = message { - if let audioSamples = metaDataDict["audio_samples"] as? [CGFloat] { - sampleViewWidthConstraint.constant = CGFloat(audioSamples.count) * (YepConfig.audioSampleWidth() + YepConfig.audioSampleGap()) - YepConfig.audioSampleGap() // 最后最后一个 gap 不要 - - sampleViewWidthConstraint.constant = max(YepConfig.minMessageSampleViewWidth, sampleViewWidthConstraint.constant) - - sampleView.samples = audioSamples + if !message.metaData.isEmpty { - if let audioDuration = metaDataDict["audio_duration"] as? Double { - audioDurationLabel.text = NSString(format: "%.1f\"", audioDuration) as String + if let data = message.metaData.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) { + if let metaDataDict = decodeJSON(data) { - sampleView.progress = CGFloat(audioPlayedDuration / audioDuration) + if let audioSamples = metaDataDict["audio_samples"] as? [CGFloat] { + sampleViewWidthConstraint.constant = CGFloat(audioSamples.count) * (YepConfig.audioSampleWidth() + YepConfig.audioSampleGap()) - YepConfig.audioSampleGap() // 最后最后一个 gap 不要 - } else { - sampleView.progress = 0 + sampleViewWidthConstraint.constant = max(YepConfig.minMessageSampleViewWidth, sampleViewWidthConstraint.constant) + + sampleView.samples = audioSamples + + if let audioDuration = metaDataDict["audio_duration"] as? Double { + audioDurationLabel.text = NSString(format: "%.1f\"", audioDuration) as String + + sampleView.progress = CGFloat(audioPlayedDuration / audioDuration) + + } else { + sampleView.progress = 0 + } + } + } + + } else { + sampleViewWidthConstraint.constant = 15 * (YepConfig.audioSampleWidth() + YepConfig.audioSampleGap()) + audioDurationLabel.text = "" + } + } + + if let audioPlayer = YepAudioService.sharedManager.audioPlayer { + if audioPlayer.playing { + if let playingMessage = YepAudioService.sharedManager.playingMessage { + if message.messageID == playingMessage.messageID { + playing = true + + return } } } - - } else { - sampleViewWidthConstraint.constant = 15 * (YepConfig.audioSampleWidth() + YepConfig.audioSampleGap()) - audioDurationLabel.text = "" } } - - if let audioPlayer = YepAudioService.sharedManager.audioPlayer { - if audioPlayer.playing { - if let playingMessage = YepAudioService.sharedManager.playingMessage { - if message == playingMessage { - playing = true - - return - } - } - } - } - + playing = false } } diff --git a/Yep/Views/Cells/ChatRightImage/ChatRightImageCell.swift b/Yep/Views/Cells/ChatRightImage/ChatRightImageCell.swift index be948b95..a5da864f 100644 --- a/Yep/Views/Cells/ChatRightImage/ChatRightImageCell.swift +++ b/Yep/Views/Cells/ChatRightImage/ChatRightImageCell.swift @@ -16,6 +16,10 @@ class ChatRightImageCell: UICollectionViewCell { @IBOutlet weak var messageImageView: UIImageView! @IBOutlet weak var messageImageViewWidthConstrint: NSLayoutConstraint! + + typealias MediaTapAction = () -> Void + var mediaTapAction: MediaTapAction? + override func awakeFromNib() { super.awakeFromNib() @@ -23,9 +27,20 @@ class ChatRightImageCell: UICollectionViewCell { avatarImageViewWidthConstraint.constant = YepConfig.chatCellAvatarSize() messageImageView.tintColor = UIColor.rightBubbleTintColor() + + messageImageView.userInteractionEnabled = true + let tap = UITapGestureRecognizer(target: self, action: "tapMediaView") + messageImageView.addGestureRecognizer(tap) } - func configureWithMessage(message: Message, messageImagePreferredWidth: CGFloat, messageImagePreferredHeight: CGFloat, messageImagePreferredAspectRatio: CGFloat) { + func tapMediaView() { + mediaTapAction?() + } + + func configureWithMessage(message: Message, messageImagePreferredWidth: CGFloat, messageImagePreferredHeight: CGFloat, messageImagePreferredAspectRatio: CGFloat, mediaTapAction: MediaTapAction?) { + + self.mediaTapAction = mediaTapAction + if let sender = message.fromFriend { AvatarCache.sharedInstance.roundAvatarOfUser(sender, withRadius: YepConfig.chatCellAvatarSize() * 0.5) { roundImage in dispatch_async(dispatch_get_main_queue()) { diff --git a/Yep/Views/Cells/ChatRightVideo/ChatRightVideoCell.swift b/Yep/Views/Cells/ChatRightVideo/ChatRightVideoCell.swift index a71692a3..502dfa21 100644 --- a/Yep/Views/Cells/ChatRightVideo/ChatRightVideoCell.swift +++ b/Yep/Views/Cells/ChatRightVideo/ChatRightVideoCell.swift @@ -17,6 +17,9 @@ class ChatRightVideoCell: UICollectionViewCell { @IBOutlet weak var thumbnailImageViewWidthConstraint: NSLayoutConstraint! @IBOutlet weak var playImageView: UIImageView! + + typealias MediaTapAction = () -> Void + var mediaTapAction: MediaTapAction? override func awakeFromNib() { super.awakeFromNib() @@ -24,9 +27,20 @@ class ChatRightVideoCell: UICollectionViewCell { avatarImageViewWidthConstraint.constant = YepConfig.chatCellAvatarSize() thumbnailImageView.tintColor = UIColor.rightBubbleTintColor() + + thumbnailImageView.userInteractionEnabled = true + let tap = UITapGestureRecognizer(target: self, action: "tapMediaView") + thumbnailImageView.addGestureRecognizer(tap) } - func configureWithMessage(message: Message, messageImagePreferredWidth: CGFloat, messageImagePreferredHeight: CGFloat, messageImagePreferredAspectRatio: CGFloat) { + func tapMediaView() { + mediaTapAction?() + } + + func configureWithMessage(message: Message, messageImagePreferredWidth: CGFloat, messageImagePreferredHeight: CGFloat, messageImagePreferredAspectRatio: CGFloat, mediaTapAction: MediaTapAction?) { + + self.mediaTapAction = mediaTapAction + if let sender = message.fromFriend { AvatarCache.sharedInstance.roundAvatarOfUser(sender, withRadius: YepConfig.chatCellAvatarSize() * 0.5) { roundImage in dispatch_async(dispatch_get_main_queue()) { diff --git a/Yep/Views/Cells/ChatSectionDate/ChatSectionDateCell.xib b/Yep/Views/Cells/ChatSectionDate/ChatSectionDateCell.xib index 2296b743..be996533 100644 --- a/Yep/Views/Cells/ChatSectionDate/ChatSectionDateCell.xib +++ b/Yep/Views/Cells/ChatSectionDate/ChatSectionDateCell.xib @@ -1,7 +1,7 @@ - + - + diff --git a/Yep/Views/Cells/ChatState/ChatStateCell.swift b/Yep/Views/Cells/ChatState/ChatStateCell.swift new file mode 100644 index 00000000..86a45c9d --- /dev/null +++ b/Yep/Views/Cells/ChatState/ChatStateCell.swift @@ -0,0 +1,18 @@ +// +// ChatStateCell.swift +// Yep +// +// Created by kevinzhow on 15/5/30. +// Copyright (c) 2015年 Catch Inc. All rights reserved. +// + +import UIKit + +class ChatStateCell: UICollectionViewCell { + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + +} diff --git a/Yep/Views/Cells/ChatState/ChatStateCell.xib b/Yep/Views/Cells/ChatState/ChatStateCell.xib new file mode 100644 index 00000000..886de815 --- /dev/null +++ b/Yep/Views/Cells/ChatState/ChatStateCell.xib @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Yep/Views/Cells/Contacts/ContactsCell.xib b/Yep/Views/Cells/Contacts/ContactsCell.xib index 94f83fea..1da29839 100644 --- a/Yep/Views/Cells/Contacts/ContactsCell.xib +++ b/Yep/Views/Cells/Contacts/ContactsCell.xib @@ -1,7 +1,7 @@ - + - + @@ -16,35 +16,36 @@ - + - + - - + + diff --git a/Yep/Views/Cells/Conversation/ConversationCell.swift b/Yep/Views/Cells/Conversation/ConversationCell.swift index dbe4d6b9..82d83cbd 100644 --- a/Yep/Views/Cells/Conversation/ConversationCell.swift +++ b/Yep/Views/Cells/Conversation/ConversationCell.swift @@ -12,8 +12,22 @@ class ConversationCell: UITableViewCell { var conversation: Conversation! + var countOfUnreadMessages = 0 { + didSet { + let hidden = countOfUnreadMessages == 0 + + redDotImageView.hidden = hidden + unreadCountLabel.hidden = hidden + + unreadCountLabel.text = "\(countOfUnreadMessages)" + } + } @IBOutlet weak var avatarImageView: UIImageView! + + @IBOutlet weak var redDotImageView: UIImageView! + @IBOutlet weak var unreadCountLabel: UILabel! + @IBOutlet weak var nameLabel: UILabel! @IBOutlet weak var chatLabel: UILabel! @IBOutlet weak var timeAgoLabel: UILabel! @@ -34,6 +48,8 @@ class ConversationCell: UITableViewCell { func configureWithConversation(conversation: Conversation, avatarRadius radius: CGFloat) { self.conversation = conversation + + countOfUnreadMessages = countOfUnreadMessagesInConversation(conversation) if conversation.type == ConversationType.OneToOne.rawValue { diff --git a/Yep/Views/Cells/Conversation/ConversationCell.xib b/Yep/Views/Cells/Conversation/ConversationCell.xib index 2fefaccd..7b3d5ba2 100644 --- a/Yep/Views/Cells/Conversation/ConversationCell.xib +++ b/Yep/Views/Cells/Conversation/ConversationCell.xib @@ -1,7 +1,7 @@ - + - + @@ -16,50 +16,75 @@ - + - + + + + + + + + + + + + + + + + + + + + + diff --git a/Yep/Views/Cells/ProfileFooter/ProfileFooterCell.swift b/Yep/Views/Cells/ProfileFooter/ProfileFooterCell.swift index 33bac2fb..bcfb8ddc 100644 --- a/Yep/Views/Cells/ProfileFooter/ProfileFooterCell.swift +++ b/Yep/Views/Cells/ProfileFooter/ProfileFooterCell.swift @@ -22,6 +22,7 @@ class ProfileFooterCell: UICollectionViewCell { instroductionLabelRightConstraint.constant = YepConfig.Profile.rightEdgeInset introductionLabel.font = YepConfig.Profile.introductionLabelFont + introductionLabel.textColor = UIColor.yepGrayColor() } } diff --git a/Yep/Views/Cells/ProfileFooter/ProfileFooterCell.xib b/Yep/Views/Cells/ProfileFooter/ProfileFooterCell.xib index 388dd25e..413469b7 100644 --- a/Yep/Views/Cells/ProfileFooter/ProfileFooterCell.xib +++ b/Yep/Views/Cells/ProfileFooter/ProfileFooterCell.xib @@ -1,7 +1,7 @@ - + - + diff --git a/Yep/Views/Cells/ProfileHeader/ProfileHeaderCell.swift b/Yep/Views/Cells/ProfileHeader/ProfileHeaderCell.swift index 0227d010..f051984c 100644 --- a/Yep/Views/Cells/ProfileHeader/ProfileHeaderCell.swift +++ b/Yep/Views/Cells/ProfileHeader/ProfileHeaderCell.swift @@ -8,10 +8,12 @@ import UIKit import CoreLocation +import FXBlurView class ProfileHeaderCell: UICollectionViewCell { @IBOutlet weak var avatarImageView: UIImageView! + @IBOutlet weak var avatarBlurImageView: UIImageView! @IBOutlet weak var locationLabel: UILabel! deinit { @@ -22,17 +24,17 @@ class ProfileHeaderCell: UICollectionViewCell { super.awakeFromNib() } - func configureWithMyInfo() { - YepUserDefaults.avatarURLString.bindAndFireListener("ProfileHeaderCell.Avatar") { avatarURLString in - if let avatarURLString = avatarURLString { - self.updateAvatarWithAvatarURLString(avatarURLString) - } - } - - YepLocationService.sharedManager // TODO: 要迁走 - - NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateAddress", name: "YepLocationUpdated", object: nil) - } +// func configureWithMyInfo() { +// YepUserDefaults.avatarURLString.bindAndFireListener("ProfileHeaderCell.Avatar") { avatarURLString in +// if let avatarURLString = avatarURLString { +// self.updateAvatarWithAvatarURLString(avatarURLString) +// } +// } +// +// YepLocationService.sharedManager // TODO: 要迁走 +// +// NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateAddress", name: "YepLocationUpdated", object: nil) +// } func configureWithDiscoveredUser(discoveredUser: DiscoveredUser) { updateAvatarWithAvatarURLString(discoveredUser.avatarURLString) @@ -56,15 +58,44 @@ class ProfileHeaderCell: UICollectionViewCell { func configureWithUser(user: User) { updateAvatarWithAvatarURLString(user.avatarURLString) + if user.friendState == UserFriendState.Me.rawValue { + YepUserDefaults.avatarURLString.bindListener("ProfileHeaderCell.Avatar") { avatarURLString in + if let avatarURLString = avatarURLString { + self.updateAvatarWithAvatarURLString(avatarURLString) + } + } + + YepLocationService.sharedManager // TODO: 要迁走 + + NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateAddress", name: "YepLocationUpdated", object: nil) + } + // TODO: User Location } + + func blurImage(image: UIImage, completion: UIImage -> Void) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { + let blurredImage = image.blurredImageWithRadius(20, iterations: 20, tintColor: UIColor.blackColor()) + + completion(blurredImage) + } + } + func updateAvatarWithAvatarURLString(avatarURLString: String) { if avatarImageView.image == nil { avatarImageView.alpha = 0 + avatarBlurImageView.alpha = 0 } AvatarCache.sharedInstance.avatarFromURL(NSURL(string: avatarURLString)!) { image in + + self.blurImage(image) { blurredImage in + dispatch_async(dispatch_get_main_queue()) { + self.avatarBlurImageView.image = blurredImage + } + } + dispatch_async(dispatch_get_main_queue()) { self.avatarImageView.image = image diff --git a/Yep/Views/Cells/ProfileHeader/ProfileHeaderCell.xib b/Yep/Views/Cells/ProfileHeader/ProfileHeaderCell.xib index e6d021a3..919f143b 100644 --- a/Yep/Views/Cells/ProfileHeader/ProfileHeaderCell.xib +++ b/Yep/Views/Cells/ProfileHeader/ProfileHeaderCell.xib @@ -1,7 +1,7 @@ - + - + @@ -15,25 +15,34 @@ + + + + + + + + diff --git a/Yep/Views/Cells/ProfileSeparationLine/ProfileSeparationLineCell.swift b/Yep/Views/Cells/ProfileSeparationLine/ProfileSeparationLineCell.swift index 3a1c018b..73fb5051 100644 --- a/Yep/Views/Cells/ProfileSeparationLine/ProfileSeparationLineCell.swift +++ b/Yep/Views/Cells/ProfileSeparationLine/ProfileSeparationLineCell.swift @@ -13,30 +13,24 @@ class ProfileSeparationLineCell: UICollectionViewCell { var leftEdgeInset: CGFloat = YepConfig.Profile.leftEdgeInset var rightEdgeInset: CGFloat = YepConfig.Profile.rightEdgeInset var lineColor: UIColor = UIColor.lightGrayColor() + var lineWidth: CGFloat = 1.0 / UIScreen.mainScreen().scale - lazy var separationLineLayer: CAShapeLayer = { - let layer = CAShapeLayer() - layer.lineWidth = 1.0 / UIScreen.mainScreen().scale - layer.strokeColor = self.lineColor.CGColor - return layer - }() + // MARK: Draw + override func drawRect(rect: CGRect) { + super.drawRect(rect) - override func awakeFromNib() { - super.awakeFromNib() + lineColor.setStroke() - layer.addSublayer(separationLineLayer) + let context = UIGraphicsGetCurrentContext() + + CGContextSetLineWidth(context, lineWidth) + + let y = ceil(CGRectGetHeight(rect) * 0.5) + + CGContextMoveToPoint(context, leftEdgeInset, y) + CGContextAddLineToPoint(context, CGRectGetWidth(rect) - rightEdgeInset, y) + + CGContextStrokePath(context) } - - override func layoutSubviews() { - super.layoutSubviews() - - let path = UIBezierPath() - let y = ceil(CGRectGetHeight(bounds) * 0.5) - path.moveToPoint(CGPoint(x: leftEdgeInset, y: y)) - path.addLineToPoint(CGPoint(x: CGRectGetWidth(bounds) - rightEdgeInset, y: y)) - - separationLineLayer.path = path.CGPath - } - } diff --git a/Yep/Views/Cells/ProfileSocialAccount/ProfileSocialAccountCell.xib b/Yep/Views/Cells/ProfileSocialAccount/ProfileSocialAccountCell.xib index b804d444..f9f7ddb9 100644 --- a/Yep/Views/Cells/ProfileSocialAccount/ProfileSocialAccountCell.xib +++ b/Yep/Views/Cells/ProfileSocialAccount/ProfileSocialAccountCell.xib @@ -1,7 +1,7 @@ - + - + diff --git a/Yep/Views/Cells/ProfileSocialAccount/ProfileSocialAccountGithubCell.swift b/Yep/Views/Cells/ProfileSocialAccount/ProfileSocialAccountGithubCell.swift index 2d1e1c23..3d80f61d 100644 --- a/Yep/Views/Cells/ProfileSocialAccount/ProfileSocialAccountGithubCell.swift +++ b/Yep/Views/Cells/ProfileSocialAccount/ProfileSocialAccountGithubCell.swift @@ -57,7 +57,7 @@ class ProfileSocialAccountGithubCell: UICollectionViewCell { accessoryImageViewTrailingConstraint.constant = YepConfig.Profile.rightEdgeInset } - func configureWithProfileUser(profileUser: ProfileUser?, orSocialWorkProviderInfo socialWorkProviderInfo: ProfileViewController.SocialWorkProviderInfo, socialAccount: SocialAccount, githubWork: GithubWork?, completion: ((GithubWork) -> Void)?) { + func configureWithProfileUser(profileUser: ProfileUser?, socialAccount: SocialAccount, githubWork: GithubWork?, completion: ((GithubWork) -> Void)?) { iconImageView.image = UIImage(named: socialAccount.iconName) nameLabel.text = socialAccount.description @@ -68,48 +68,16 @@ class ProfileSocialAccountGithubCell: UICollectionViewCell { let providerName = socialAccount.description.lowercaseString var accountEnabled = false - + if let profileUser = profileUser { + accountEnabled = profileUser.enabledSocialAccount(socialAccount) - switch profileUser { - - case .DiscoveredUserType(let discoveredUser): - for provider in discoveredUser.socialAccountProviders { - if (provider.name == providerName) && provider.enabled { - iconImageView.tintColor = socialAccount.tintColor - nameLabel.textColor = socialAccount.tintColor - - accountEnabled = true - - break - } - } - - case .UserType(let user): - for provider in user.socialAccountProviders { - if (provider.name == providerName) && provider.enabled { - iconImageView.tintColor = socialAccount.tintColor - nameLabel.textColor = socialAccount.tintColor - - accountEnabled = true - - break - } - } - } - - } else { - if let enabled = socialWorkProviderInfo[providerName] { - if enabled { - iconImageView.tintColor = socialAccount.tintColor - nameLabel.textColor = socialAccount.tintColor - - accountEnabled = true - } + if accountEnabled { + iconImageView.tintColor = socialAccount.tintColor + nameLabel.textColor = socialAccount.tintColor } } - - + if !accountEnabled { reposImageView.hidden = true reposCountLabel.text = "" @@ -132,9 +100,6 @@ class ProfileSocialAccountGithubCell: UICollectionViewCell { case .UserType(let user): userID = user.userID } - - } else { - userID = YepUserDefaults.userID.value } if let userID = userID { diff --git a/Yep/Views/Cells/ProfileSocialAccount/ProfileSocialAccountGithubCell.xib b/Yep/Views/Cells/ProfileSocialAccount/ProfileSocialAccountGithubCell.xib index c9b87f63..7c0e8ef5 100644 --- a/Yep/Views/Cells/ProfileSocialAccount/ProfileSocialAccountGithubCell.xib +++ b/Yep/Views/Cells/ProfileSocialAccount/ProfileSocialAccountGithubCell.xib @@ -1,7 +1,7 @@ - + - + @@ -26,7 +26,7 @@ - + - + - + - + - + diff --git a/Yep/Views/Cells/SettingsMore/SettingsMoreCell.xib b/Yep/Views/Cells/SettingsMore/SettingsMoreCell.xib index 8576039a..0785f1b2 100644 --- a/Yep/Views/Cells/SettingsMore/SettingsMoreCell.xib +++ b/Yep/Views/Cells/SettingsMore/SettingsMoreCell.xib @@ -29,7 +29,7 @@ - + diff --git a/Yep/Views/Media/MediaControlView.swift b/Yep/Views/Media/MediaControlView.swift index 20af3b96..ab303bb5 100644 --- a/Yep/Views/Media/MediaControlView.swift +++ b/Yep/Views/Media/MediaControlView.swift @@ -111,6 +111,8 @@ class MediaControlView: UIView { let playButtonConstraintCenterX = NSLayoutConstraint(item: playButton, attribute: .CenterX, relatedBy: .Equal, toItem: self, attribute: .CenterX, multiplier: 1.0, constant: 0) NSLayoutConstraint.activateConstraints([playButtonConstraintCenterX]) + + backgroundColor = UIColor(white: 0.0, alpha: 0.3) } // MARK: Actions diff --git a/Yep/Views/Media/MediaView.swift b/Yep/Views/Media/MediaView.swift index c7b723c2..1a0f5dbf 100644 --- a/Yep/Views/Media/MediaView.swift +++ b/Yep/Views/Media/MediaView.swift @@ -11,6 +11,35 @@ import AVFoundation class MediaView: UIView { + var image: UIImage? { + didSet { + if let image = image { + imageView.image = image + + scrollView.frame = UIScreen.mainScreen().bounds + + let size = image.size + imageView.frame = CGRect(origin: CGPointZero, size: size) + + setZoomParametersForSize(scrollView.bounds.size, imageSize: size) + scrollView.zoomScale = scrollView.minimumZoomScale + + recenterImage() + } + } + } + + lazy var scrollView: UIScrollView = { + + let scrollView = UIScrollView() + scrollView.delegate = self + + scrollView.showsHorizontalScrollIndicator = false + scrollView.showsVerticalScrollIndicator = false + + return scrollView + }() + lazy var imageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .ScaleAspectFit @@ -34,7 +63,7 @@ class MediaView: UIView { println("videoPlayerLayer.frame: \(videoPlayerLayer.frame)") } - + override func layoutSubviews() { super.layoutSubviews() @@ -43,19 +72,81 @@ class MediaView: UIView { func makeUI() { - addSubview(imageView) + addSubview(scrollView) - imageView.setTranslatesAutoresizingMaskIntoConstraints(false) + scrollView.setTranslatesAutoresizingMaskIntoConstraints(false) let viewsDictionary = [ + "scrollView": scrollView, "imageView": imageView, ] - let constraintsV = NSLayoutConstraint.constraintsWithVisualFormat("V:|[imageView]|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDictionary) + let scrollViewConstraintsV = NSLayoutConstraint.constraintsWithVisualFormat("V:|[scrollView]|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDictionary) - let constraintsH = NSLayoutConstraint.constraintsWithVisualFormat("H:|[imageView]|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDictionary) + let scrollViewConstraintsH = NSLayoutConstraint.constraintsWithVisualFormat("H:|[scrollView]|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDictionary) - NSLayoutConstraint.activateConstraints(constraintsV) - NSLayoutConstraint.activateConstraints(constraintsH) + NSLayoutConstraint.activateConstraints(scrollViewConstraintsV) + NSLayoutConstraint.activateConstraints(scrollViewConstraintsH) + + + scrollView.addSubview(imageView) + + /* + imageView.setTranslatesAutoresizingMaskIntoConstraints(false) + + let imageViewLeadingConstraint = NSLayoutConstraint(item: imageView, attribute: .Leading, relatedBy: .Equal, toItem: self, attribute: .Leading, multiplier: 1.0, constant: 0) + + let imageViewTrailingConstraint = NSLayoutConstraint(item: imageView, attribute: .Trailing, relatedBy: .Equal, toItem: self, attribute: .Trailing, multiplier: 1.0, constant: 0) + + let imageViewTopConstraint = NSLayoutConstraint(item: imageView, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1.0, constant: 0) + + let imageViewBottomConstraint = NSLayoutConstraint(item: imageView, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1.0, constant: 0) + + NSLayoutConstraint.activateConstraints([ + imageViewLeadingConstraint, + imageViewTrailingConstraint, + imageViewTopConstraint, + imageViewBottomConstraint, + ]) + + let imageViewConstraintsV = NSLayoutConstraint.constraintsWithVisualFormat("V:|[imageView]|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDictionary) + + let imageViewConstraintsH = NSLayoutConstraint.constraintsWithVisualFormat("H:|[imageView]|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDictionary) + + NSLayoutConstraint.activateConstraints(imageViewConstraintsV) + NSLayoutConstraint.activateConstraints(imageViewConstraintsH) + */ + } + + func setZoomParametersForSize(scrollViewSize: CGSize, imageSize: CGSize) { + + let widthScale = scrollViewSize.width / imageSize.width + let heightScale = scrollViewSize.height / imageSize.height + let minScale = min(widthScale, heightScale) + + scrollView.minimumZoomScale = minScale + scrollView.maximumZoomScale = 3.0 + } + + func recenterImage() { + + let scrollViewSize = scrollView.bounds.size + let imageSize = imageView.frame.size + + let hSpace = imageSize.width < scrollViewSize.width ? (scrollViewSize.width - imageSize.width) * 0.5 : 0 + let vSpace = imageSize.height < scrollViewSize.height ? (scrollViewSize.height - imageSize.height) * 0.5 : 0 + + scrollView.contentInset = UIEdgeInsets(top: vSpace, left: hSpace, bottom: vSpace, right: hSpace) + } +} + +extension MediaView: UIScrollViewDelegate { + + func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? { + return imageView + } + + func scrollViewDidZoom(scrollView: UIScrollView) { + recenterImage() } } diff --git a/Yep/Views/MessageToolbar/MessageToolbar.swift b/Yep/Views/MessageToolbar/MessageToolbar.swift index 094271e7..9155061d 100644 --- a/Yep/Views/MessageToolbar/MessageToolbar.swift +++ b/Yep/Views/MessageToolbar/MessageToolbar.swift @@ -8,7 +8,7 @@ import UIKit -enum MessageToolbarState: Printable { +enum MessageToolbarState: Int, Printable { case Default case BeginTextInput case TextInputing @@ -38,7 +38,7 @@ class MessageToolbar: UIToolbar { let messageTextAttributes = [NSFontAttributeName: UIFont.systemFontOfSize(15)] - var stateTransitionAction: ((previousState: MessageToolbarState, currentState: MessageToolbarState) -> Void)? + var stateTransitionAction: ((messageToolbar: MessageToolbar, previousState: MessageToolbarState, currentState: MessageToolbarState) -> Void)? var previousState: MessageToolbarState = .Default var state: MessageToolbarState = .Default { @@ -47,7 +47,7 @@ class MessageToolbar: UIToolbar { previousState = state if let action = stateTransitionAction { - action(previousState: previousState, currentState: newValue) + action(messageToolbar: self, previousState: previousState, currentState: newValue) } switch newValue { diff --git a/Yep/Views/SayHi/SayHiView.swift b/Yep/Views/SayHi/SayHiView.swift new file mode 100644 index 00000000..8b31d0d4 --- /dev/null +++ b/Yep/Views/SayHi/SayHiView.swift @@ -0,0 +1,76 @@ +// +// SayHiView.swift +// Yep +// +// Created by NIX on 15/5/29. +// Copyright (c) 2015年 Catch Inc. All rights reserved. +// + +import UIKit + +@IBDesignable +class SayHiView: UIView { + + @IBInspectable var topLineColor: UIColor = UIColor.lightGrayColor() + @IBInspectable var topLineWidth: CGFloat = 1 / UIScreen.mainScreen().scale + + lazy var sayHiButton: UIButton = { + let button = UIButton() + button.titleLabel?.font = UIFont(name: "Helvetica-Regular", size: 14) + button.setTitle(NSLocalizedString("Say Hi", comment: ""), forState: .Normal) + button.backgroundColor = UIColor.yepTintColor() + button.setTitleColor(UIColor.whiteColor(), forState: .Normal) + button.layer.cornerRadius = 5 + button.addTarget(self, action: "trySayHi", forControlEvents: UIControlEvents.TouchUpInside) + return button + }() + + var sayHiAction: (() -> Void)? + + override func didMoveToSuperview() { + super.didMoveToSuperview() + + backgroundColor = UIColor.whiteColor() + + // Add sayHiButton + + self.addSubview(sayHiButton) + sayHiButton.setTranslatesAutoresizingMaskIntoConstraints(false) + + let sayHiButtonCenterXConstraint = NSLayoutConstraint(item: sayHiButton, attribute: .CenterX, relatedBy: .Equal, toItem: self, attribute: .CenterX, multiplier: 1.0, constant: 0) + + let sayHiButtonCenterYConstraint = NSLayoutConstraint(item: sayHiButton, attribute: .CenterY, relatedBy: .Equal, toItem: self, attribute: .CenterY, multiplier: 1.0, constant: 0) + + let sayHiButtonWidthConstraint = NSLayoutConstraint(item: sayHiButton, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 185) + + let sayHiButtonHeightConstraint = NSLayoutConstraint(item: sayHiButton, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 30) + + NSLayoutConstraint.activateConstraints([ + sayHiButtonCenterXConstraint, + sayHiButtonCenterYConstraint, + sayHiButtonWidthConstraint, + sayHiButtonHeightConstraint, + ]) + } + + // MARK: Actions + + func trySayHi() { + sayHiAction?() + } + + // MARK: Draw + + override func drawRect(rect: CGRect) { + super.drawRect(rect) + + topLineColor.setStroke() + + let context = UIGraphicsGetCurrentContext() + + CGContextSetLineWidth(context, topLineWidth) + CGContextMoveToPoint(context, 0, 0) + CGContextAddLineToPoint(context, CGRectGetWidth(rect), 0) + CGContextStrokePath(context) + } +} diff --git a/Yep/Views/ScrollView/YepChildScrollView.swift b/Yep/Views/ScrollView/YepChildScrollView.swift new file mode 100644 index 00000000..23fa077e --- /dev/null +++ b/Yep/Views/ScrollView/YepChildScrollView.swift @@ -0,0 +1,27 @@ +// +// YepChildScrollView.swift +// Yep +// +// Created by kevinzhow on 15/5/24. +// Copyright (c) 2015年 Catch Inc. All rights reserved. +// + +import UIKit + +class YepChildScrollView: UITableView { + + /* + // Only override drawRect: if you perform custom drawing. + // An empty implementation adversely affects performance during animation. + override func drawRect(rect: CGRect) { + // Drawing code + } + */ + + + func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { + + return false + + } +} diff --git a/Yep/Views/ScrollView/YepScrollView.swift b/Yep/Views/ScrollView/YepScrollView.swift new file mode 100644 index 00000000..ce516879 --- /dev/null +++ b/Yep/Views/ScrollView/YepScrollView.swift @@ -0,0 +1,34 @@ +// +// YepScrollView.swift +// Yep +// +// Created by kevinzhow on 15/5/24. +// Copyright (c) 2015年 Catch Inc. All rights reserved. +// + +import UIKit + +class YepScrollView: UIScrollView { + + /* + // Only override drawRect: if you perform custom drawing. + // An empty implementation adversely affects performance during animation. + override func drawRect(rect: CGRect) { + // Drawing code + } + */ + + func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { + + if gestureRecognizer.isKindOfClass(UIPanGestureRecognizer) && otherGestureRecognizer.isKindOfClass(UIScreenEdgePanGestureRecognizer) { + + return true + + } else { + return false + } + + } + + +}