diff --git a/example/ios/Podfile b/example/ios/Podfile index 8f3be61..ecb763f 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '9.0' +platform :ios, '11.0' source 'ssh://gitlab.finogeeks.club/finclip-ios/DevPods' source 'ssh://gitlab.finogeeks.club/finclip-ios/FinPods' diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index a038787..bcb7623 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -231,10 +231,12 @@ }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -262,6 +264,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -351,7 +354,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -430,7 +433,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -479,7 +482,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index 53f61d1..414701e 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -43,5 +43,7 @@ UIViewControllerBasedStatusBarAppearance + UIApplicationSupportsIndirectInputEvents + diff --git a/example/pubspec.lock b/example/pubspec.lock index ee91b9b..6f2e3f2 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,27 +5,31 @@ packages: dependency: transitive description: name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.flutter-io.cn" source: hosted - version: "2.9.0" + version: "2.11.0" boolean_selector: dependency: transitive description: name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.0" + version: "2.1.1" characters: dependency: transitive description: name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.flutter-io.cn" source: hosted - version: "1.2.1" + version: "1.3.0" clock: dependency: transitive description: name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" @@ -33,13 +37,15 @@ packages: dependency: transitive description: name: collection + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.flutter-io.cn" source: hosted - version: "1.16.0" + version: "1.17.1" cupertino_icons: dependency: "direct main" description: name: cupertino_icons + sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be url: "https://pub.flutter-io.cn" source: hosted version: "1.0.5" @@ -47,6 +53,7 @@ packages: dependency: transitive description: name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" url: "https://pub.flutter-io.cn" source: hosted version: "1.3.1" @@ -59,6 +66,7 @@ packages: dependency: "direct dev" description: name: flutter_lints + sha256: b543301ad291598523947dc534aaddc5aaad597b709d2426d3a0e0d44c5cb493 url: "https://pub.flutter-io.cn" source: hosted version: "1.0.4" @@ -66,6 +74,7 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle + sha256: c224ac897bed083dabf11f238dd11a239809b446740be0c2044608c50029ffdf url: "https://pub.flutter-io.cn" source: hosted version: "2.0.9" @@ -74,10 +83,19 @@ packages: description: flutter source: sdk version: "0.0.0" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.6.7" lints: dependency: transitive description: name: lints + sha256: a2c3d198cb5ea2e179926622d433331d8b58374ab8f29cdda6e863bd62fd369c url: "https://pub.flutter-io.cn" source: hosted version: "1.0.1" @@ -85,37 +103,41 @@ packages: dependency: transitive description: name: matcher + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.flutter-io.cn" source: hosted - version: "0.12.12" + version: "0.12.15" material_color_utilities: dependency: transitive description: name: material_color_utilities + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 url: "https://pub.flutter-io.cn" source: hosted - version: "0.1.5" + version: "0.2.0" meta: dependency: transitive description: name: meta + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.flutter-io.cn" source: hosted - version: "1.8.0" + version: "1.9.1" mop: dependency: "direct main" description: path: ".." relative: true source: path - version: "2.41.1" + version: "2.41.5" path: dependency: transitive description: name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.flutter-io.cn" source: hosted - version: "1.8.2" + version: "1.8.3" sky_engine: dependency: transitive description: flutter @@ -125,34 +147,39 @@ packages: dependency: transitive description: name: source_span + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 url: "https://pub.flutter-io.cn" source: hosted - version: "1.9.0" + version: "1.9.1" stack_trace: dependency: transitive description: name: stack_trace + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 url: "https://pub.flutter-io.cn" source: hosted - version: "1.10.0" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.1" + version: "1.2.0" term_glyph: dependency: transitive description: name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 url: "https://pub.flutter-io.cn" source: hosted version: "1.2.1" @@ -160,16 +187,18 @@ packages: dependency: transitive description: name: test_api + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.flutter-io.cn" source: hosted - version: "0.4.12" + version: "0.5.1" vector_math: dependency: transitive description: name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.2" + version: "2.1.4" sdks: - dart: ">=2.17.0-0 <3.0.0" + dart: ">=3.0.0-0 <4.0.0" flutter: ">=3.0.0" diff --git a/ios/Classes/FinAppletExt/Client/FATClient+ext.h b/ios/Classes/FinAppletExt/Client/FATClient+ext.h new file mode 100644 index 0000000..2425108 --- /dev/null +++ b/ios/Classes/FinAppletExt/Client/FATClient+ext.h @@ -0,0 +1,46 @@ +// +// FATClient+ext.h +// Pods +// +// Created by 王滔 on 2021/11/15. +// + +#ifndef FATClient_ext_h +#define FATClient_ext_h + +#import + +@interface FATClient (FATAppletExt) + +/// 获取小程序的权限 +/// @param authType 权限类型,0:相册 1:相机 2:麦克风 3:位置 +/// @param appletId 小程序id +/// @param complete 结果回调 status: 0 允许 1:用户拒绝 2: sdk拒绝 +- (void)fat_requestAppletAuthorize:(FATAuthorizationType)authType appletId:(NSString *)appletId complete:(void (^)(NSInteger status))complete; + +/// 内部sdk注入API方法,包括扩展sdk和其他地图等sdk 注入的API会加到内部白名单列表,保证小程序在设置了api白名单的情况下,也能正常响应 +/// @param extApiName API名称 +/// @param handler 回调 +- (BOOL)registerInnerExtensionApi:(NSString *)extApiName handler:(void (^)(FATAppletInfo *appletInfo, id param, FATExtensionApiCallback callback))handler; + +/** + 内部sdk注入API方法,包括扩展sdk和其他地图等sdk 注入的API会加到内部白名单列表,保证小程序在设置了api白名单的情况下,也能正常响应 + @param syncExtApiName 扩展的api名称 + @param handler 回调 + @return 返回注册结果 + */ +- (BOOL)registerInnerSyncExtensionApi:(NSString *)syncExtApiName handler:(NSDictionary *(^)(FATAppletInfo *appletInfo, id param))handler; + + +/** + 为HTML 注册要调用的原生 api(内部sdk注入的api) + @param webApiName 原生api名字 + @param handler 回调 + */ +- (BOOL)fat_registerInnerWebApi:(NSString *)webApiName handler:(void (^)(FATAppletInfo *appletInfo, id param, FATExtensionApiCallback callback))handler; + + + +@end + +#endif /* FATClient_ext_h */ diff --git a/ios/Classes/FinAppletExt/Client/FATExtClient.h b/ios/Classes/FinAppletExt/Client/FATExtClient.h new file mode 100644 index 0000000..891be19 --- /dev/null +++ b/ios/Classes/FinAppletExt/Client/FATExtClient.h @@ -0,0 +1,28 @@ +// +// FATExtClient.h +// FinAppletExtension +// +// Created by Haley on 2020/8/11. +// Copyright © 2020 finogeeks. All rights reserved. +// + +#import + +@interface FATExtClient : NSObject + ++ (instancetype)sharedClient; + +/// 版本号 ++ (NSString *)SDKVersion; + +- (void)fat_prepareExtensionApis; + +- (void)registerGoogleMapService:(NSString*)apiKey placesKey:(NSString*)placeKey; + +/// 获取webView +/// @param frame frame +/// @param URL 网页的URL +/// @param appletId 小程序ID +- (UIView *)webViewWithFrame:(CGRect)frame URL:(NSURL *)URL appletId:(NSString *)appletId; + +@end diff --git a/ios/Classes/FinAppletExt/Client/FATExtClient.m b/ios/Classes/FinAppletExt/Client/FATExtClient.m new file mode 100644 index 0000000..e3349a6 --- /dev/null +++ b/ios/Classes/FinAppletExt/Client/FATExtClient.m @@ -0,0 +1,92 @@ +// +// FATExtClient.m +// FinAppletExtension +// +// Created by Haley on 2020/8/11. +// Copyright © 2020 finogeeks. All rights reserved. +// + +#import "FATExtClient.h" +#import "FATExtBaseApi.h" +#import "FATWebView.h" +#import +#import "FATExtPrivateConstant.h" +#import "FATExtMapManager.h" +#import "FATMapViewDelegate.h" +#import "FATClient+ext.h" + +static FATExtClient *instance = nil; + +@implementation FATExtClient + ++ (instancetype)sharedClient { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[FATExtClient alloc] init]; + }); + return instance; +} + ++ (instancetype)allocWithZone:(struct _NSZone *)zone { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [super allocWithZone:zone]; + }); + return instance; +} + ++ (NSString *)SDKVersion +{ + return FATExtVersionString; +} + +/// 注册地图展示类 +/// @param mapClass 地图类,需要是UIView的子类,实现FATMapViewDelegate协议 +- (BOOL)fat_registerMapClass:(Class)mapClass { + if (![mapClass isSubclassOfClass:UIView.class]) { + return NO; + } + + if (![mapClass conformsToProtocol:@protocol(FATMapViewDelegate)]) { + return NO; + } + [FATExtMapManager shareInstance].mapClass = mapClass; + return YES; +} +- (void)registerGoogleMapService:(NSString*)apiKey placesKey:(NSString*)placeKey{ + //[GMSServices provideAPIKey: apiKey]; + [FATExtMapManager shareInstance].googleMapApiKey = apiKey; + if(placeKey == nil || [placeKey length] == 0){ + [FATExtMapManager shareInstance].placesApiKey = apiKey; + }else{ + [FATExtMapManager shareInstance].placesApiKey = placeKey; + } +} + +- (void)fat_prepareExtensionApis { + +} + +- (void)registerExtensionBLEApi { + //该空方法不能移除。如果集成了蓝牙拓展SDK,分类会覆盖此方法 +} + +- (UIView *)webViewWithFrame:(CGRect)frame URL:(NSURL *)URL appletId:(NSString *)appletId { + if (![FATClient sharedClient].inited) { + NSLog(@"appKey invalid"); + return nil; + } + + if (!URL || ![URL isKindOfClass:[NSURL class]]) { + NSLog(@"URL invalid"); + return nil; + } + + FATWebView *webView = [[FATWebView alloc] initWithFrame:frame URL:URL appletId:appletId]; + return webView; +} + + + + +@end diff --git a/ios/Classes/FinAppletExt/Common/Category/UIView+FATExtFrame.h b/ios/Classes/FinAppletExt/Common/Category/UIView+FATExtFrame.h new file mode 100644 index 0000000..d575eda --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Category/UIView+FATExtFrame.h @@ -0,0 +1,34 @@ +// +// UIView+FATExtFrame.h +// FinAppletBDMap +// +// Created by 王兆耀 on 2021/12/12. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIView (FATExtFrame) + +@property (nonatomic, assign) CGFloat x; +@property (nonatomic, assign) CGFloat y; +@property (nonatomic, assign) CGFloat centerX; +@property (nonatomic, assign) CGFloat centerY; +@property (nonatomic, assign) CGFloat width; +@property (nonatomic, assign) CGFloat height; +@property (nonatomic, assign) CGSize size; + +@property (nonatomic, assign) CGPoint origin; + +@property (nonatomic, assign) CGFloat top; +@property (nonatomic, assign) CGFloat bottom; +@property (nonatomic, assign) CGFloat left; +@property (nonatomic, assign) CGFloat right; + +//判断self和View是否重叠 +- (BOOL)intersectWithView:(UIView *)view; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/Common/Category/UIView+FATExtFrame.m b/ios/Classes/FinAppletExt/Common/Category/UIView+FATExtFrame.m new file mode 100644 index 0000000..8e0f2b1 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Category/UIView+FATExtFrame.m @@ -0,0 +1,131 @@ +// +// UIView+FATExtFrame.m +// FinAppletBDMap +// +// Created by 王兆耀 on 2021/12/12. +// + +#import "UIView+FATExtFrame.h" + +@implementation UIView (FATExtFrame) + +- (void)setX:(CGFloat)x { + CGRect frame = self.frame; + frame.origin.x = x; + self.frame = frame; +} + +- (CGFloat)x { + return self.frame.origin.x; +} + +- (void)setY:(CGFloat)y { + CGRect frame = self.frame; + frame.origin.y = y; + self.frame = frame; +} + +- (CGFloat)y { + return self.frame.origin.y; +} + +- (void)setCenterX:(CGFloat)centerX { + CGPoint center = self.center; + center.x = centerX; + self.center = center; +} + +- (CGFloat)centerX { + return self.center.x; +} + +- (void)setCenterY:(CGFloat)centerY { + CGPoint center = self.center; + center.y = centerY; + self.center = center; +} + +- (CGFloat)centerY { + return self.center.y; +} + +- (void)setWidth:(CGFloat)width { + CGRect frame = self.frame; + frame.size.width = width; + self.frame = frame; +} + +- (CGFloat)width { + return self.frame.size.width; +} + +- (void)setHeight:(CGFloat)height { + CGRect frame = self.frame; + frame.size.height = height; + self.frame = frame; +} + +- (CGFloat)height { + return self.frame.size.height; +} + +- (void)setSize:(CGSize)size { + CGRect frame = self.frame; + frame.size = size; + self.frame = frame; +} + +- (CGSize)size { + return self.frame.size; +} + +- (CGPoint)origin { + return self.frame.origin; +} + +- (void)setOrigin:(CGPoint)origin { + CGRect frame = self.frame; + frame.origin = origin; + self.frame = frame; +} + +- (void)setTop:(CGFloat)t { + self.frame = CGRectMake(self.left, t, self.width, self.height); +} +- (CGFloat)top { + return self.frame.origin.y; +} + +- (void)setBottom:(CGFloat)b { + self.frame = CGRectMake(self.left, b - self.height, self.width, self.height); +} + +- (CGFloat)bottom { + return self.frame.origin.y + self.frame.size.height; +} + +- (void)setLeft:(CGFloat)l { + self.frame = CGRectMake(l, self.top, self.width, self.height); +} + +- (CGFloat)left { + return self.frame.origin.x; +} + +- (void)setRight:(CGFloat)r { + self.frame = CGRectMake(r - self.width, self.top, self.width, self.height); +} + +- (CGFloat)right { + return self.frame.origin.x + self.frame.size.width; +} + +//判断self和View是否重叠 nil表示为window +- (BOOL)intersectWithView:(UIView *)view { + if (view == nil) view = [UIApplication sharedApplication].keyWindow; + CGRect rect1 = [self convertRect:self.bounds toView:nil]; + CGRect rect2 = [view convertRect:view.bounds toView:nil]; + return CGRectIntersectsRect(rect1, rect2); +} + +@end diff --git a/ios/Classes/FinAppletExt/Common/Constant/FATExtPrivateConstant.h b/ios/Classes/FinAppletExt/Common/Constant/FATExtPrivateConstant.h new file mode 100644 index 0000000..63ce064 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Constant/FATExtPrivateConstant.h @@ -0,0 +1,21 @@ +// +// FATExtConstant.h +// FinAppletExt +// +// Created by Haley on 2021/9/6. +// + +#ifndef FATExtPrivateConstant_h +#define FATExtPrivateConstant_h + +static NSString *kExtSendToCoreEventNotification = @"kExtSendToCoreEventNotification"; + +static NSString *FATExtVersionString = @"2.41.3"; + +typedef NS_ENUM(NSUInteger, FATExtEventType) { + FATExtEventTypeService, // 发送给service的事件 + FATExtEventTypePage, // 发送给page层的事件 + FATExtEventTypeView, // view 操作事件 +}; + +#endif /* FATExtPrivateConstant_h */ diff --git a/ios/Classes/FinAppletExt/Common/Manager/FATExtAVManager.h b/ios/Classes/FinAppletExt/Common/Manager/FATExtAVManager.h new file mode 100644 index 0000000..6348ed2 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Manager/FATExtAVManager.h @@ -0,0 +1,36 @@ +// +// FATExtAVManager.h +// FinAppletExt +// +// Created by Haley on 2020/8/14. +// Copyright © 2020 finogeeks. All rights reserved. +// + +#import + +typedef void (^FATExtAVSuccess)(NSString *filePath); +typedef void (^FATExtAVFail)(NSString *failMsg); + +@interface FATExtAVManager : NSObject + ++ (instancetype)sharedManager; + +/** + 开始录音 + + @param success 成功回调 + @param fail 失败回调 + */ +- (void)startRecordWithSuccess:(FATExtAVSuccess)success fail:(FATExtAVFail)fail; + +/** + 停止录音 + */ +- (void)stopRecord; + +/** + 检查录音状态 + */ +- (void)checkRecordState; + +@end diff --git a/ios/Classes/FinAppletExt/Common/Manager/FATExtAVManager.m b/ios/Classes/FinAppletExt/Common/Manager/FATExtAVManager.m new file mode 100644 index 0000000..626745c --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Manager/FATExtAVManager.m @@ -0,0 +1,195 @@ +// +// FATExtAVManager.m +// FinAppletExt +// +// Created by Haley on 2020/8/14. +// Copyright © 2020 finogeeks. All rights reserved. +// + +#import "FATExtAVManager.h" +#import "FATExtFileManager.h" + +#import +#import +#import + +@interface FATExtAVManager () + +@property (nonatomic, strong) AVAudioRecorder *recorder; + +@property (nonatomic, copy) FATExtAVSuccess recordSuccess; +@property (nonatomic, copy) FATExtAVFail recordFail; + +@end + +@implementation FATExtAVManager + ++ (instancetype)sharedManager { + static id _sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _sharedInstance = [[FATExtAVManager alloc] init]; + [_sharedInstance add_notifications]; + }); + + return _sharedInstance; +} + +- (void)add_notifications { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appletPageDisappear:) name:kFATPageDidDisappearNotification object:nil]; +} + +- (void)appletPageDisappear:(NSNotification *)notification +{ + if (self.recorder) { + [self.recorder pause]; + if (self.recordFail) { + self.recordFail(@"fail"); + } + [self.recorder deleteRecording]; + self.recorder = nil; + + // 胶囊按钮 + UIViewController *vc = [[UIApplication sharedApplication] fat_topViewController]; + UINavigationController *nav = (UINavigationController *)vc.navigationController; + if ([nav respondsToSelector:@selector(controlCapsuleStateButton:state:animate:)]) { + [nav controlCapsuleStateButton:YES state:FATCapsuleButtonStateMicroPhone animate:NO]; + } + } +} + +/** + 开始录音 + + @param success 成功回调 + @param fail 失败回调 + */ +- (void)startRecordWithSuccess:(FATExtAVSuccess)success fail:(FATExtAVFail)fail { + if ([self.recorder isRecording]) { + fail(@"正在录音中..."); + return; + } + + self.recordSuccess = success; + self.recordFail = fail; + + self.recorder = [self createAudioRecord]; + + [self.recorder prepareToRecord]; + AVAudioSession *audioSession = [AVAudioSession sharedInstance]; + [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:NULL]; + [audioSession setActive:YES error:NULL]; + [self.recorder recordForDuration:60]; + + // 胶囊按钮 + UIViewController *vc = [[UIApplication sharedApplication] fat_topViewController]; + UINavigationController *nav = (UINavigationController *)vc.navigationController; + if ([nav respondsToSelector:@selector(controlCapsuleStateButton:state:animate:)]) { + [nav controlCapsuleStateButton:NO state:FATCapsuleButtonStateMicroPhone animate:YES]; + } +} + +/** + 停止录音 + */ +- (void)stopRecord { + if (self.recorder) { + [self.recorder stop]; + self.recorder = nil; + + // 胶囊按钮 + UIViewController *vc = [[UIApplication sharedApplication] fat_topViewController]; + UINavigationController *nav = (UINavigationController *)vc.navigationController; + if ([nav respondsToSelector:@selector(controlCapsuleStateButton:state:animate:)]) { + [nav controlCapsuleStateButton:YES state:FATCapsuleButtonStateMicroPhone animate:NO]; + } + } +} + +- (void)checkRecordState { + if ([self.recorder isRecording]) { + // 胶囊按钮 + UIViewController *vc = [[UIApplication sharedApplication] fat_topViewController]; + UINavigationController *nav = (UINavigationController *)vc.navigationController; + if ([nav respondsToSelector:@selector(controlCapsuleStateButton:state:animate:)]) { + [nav controlCapsuleStateButton:NO state:FATCapsuleButtonStateMicroPhone animate:YES]; + } + } +} + +#pragma mark - private method + +- (AVAudioRecorder *)createAudioRecord { + // 使用此配置 录制1分钟大小200KB左右 + NSDictionary *settings = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:kAudioFormatMPEG4AAC], AVFormatIDKey, + [NSNumber numberWithFloat:16000.0], AVSampleRateKey, + [NSNumber numberWithInt:1], AVNumberOfChannelsKey, + nil]; + + // 使用当前时间戳的md5作为文件名 + NSString *currentDt = [NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]]; + NSData *data = [currentDt dataUsingEncoding:NSUTF8StringEncoding]; + NSString *nameMD5 = [self fat_md5WithBytes:(char *)[data bytes] length:data.length]; + NSString *fileName = [NSString stringWithFormat:@"tmp_%@.m4a", nameMD5]; + NSString *filePath = [[self tmpDir] stringByAppendingPathComponent:fileName]; + AVAudioRecorder *recorder = [[AVAudioRecorder alloc] initWithURL:[NSURL fileURLWithPath:filePath] settings:settings error:nil]; + recorder.delegate = self; + + return recorder; +} + +- (NSString *)tmpDir { + FATAppletInfo *appInfo = [[FATClient sharedClient] currentApplet]; + NSString *cacheDir = [FATExtFileManager appTempDirPath:appInfo.appId]; + NSFileManager *fileManager = [NSFileManager defaultManager]; + BOOL flag = YES; + if (![fileManager fileExistsAtPath:cacheDir isDirectory:&flag]) { + [fileManager createDirectoryAtPath:cacheDir withIntermediateDirectories:YES attributes:nil error:nil]; + } + + return cacheDir; +} + +- (NSString *)fat_md5WithBytes:(char *)bytes length:(NSUInteger)length { + unsigned char result[16]; + CC_MD5(bytes, (CC_LONG)length, result); + return [NSString stringWithFormat: + @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", + result[0], result[1], result[2], result[3], + result[4], result[5], result[6], result[7], + result[8], result[9], result[10], result[11], + result[12], result[13], result[14], result[15]]; +} + +#pragma mark - AVAudioRecord Delegate +- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag { + NSString *filePath = recorder.url.lastPathComponent; + if (flag) { + if (self.recordSuccess) { + self.recordSuccess([@"finfile://" stringByAppendingString:filePath]); + } + } else { + [recorder deleteRecording]; + if (self.recordFail) { + self.recordFail(@"fail"); + } + } + + AVAudioSession *audioSession = [AVAudioSession sharedInstance]; + [audioSession setCategory:AVAudioSessionCategoryPlayback error:NULL]; + [audioSession setActive:YES error:NULL]; + + self.recorder = nil; + self.recordSuccess = nil; + self.recordFail = nil; + + // 胶囊按钮 + UIViewController *vc = [[UIApplication sharedApplication] fat_topViewController]; + UINavigationController *nav = (UINavigationController *)vc.navigationController; + if ([nav respondsToSelector:@selector(controlCapsuleStateButton:state:animate:)]) { + [nav controlCapsuleStateButton:YES state:FATCapsuleButtonStateMicroPhone animate:NO]; + } +} + +@end diff --git a/ios/Classes/FinAppletExt/Common/Manager/FATExtFileManager.h b/ios/Classes/FinAppletExt/Common/Manager/FATExtFileManager.h new file mode 100644 index 0000000..29fd410 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Manager/FATExtFileManager.h @@ -0,0 +1,30 @@ +// +// FATExtFileManager.h +// FinAppletExt +// +// Created by Haley on 2020/8/17. +// Copyright © 2020 finogeeks. All rights reserved. +// + +#import + +@interface FATExtFileManager : NSObject + +/** + 工程根目录路径 + + @return 工程根目录路径 + */ ++ (NSString *)projectRootAppsDirPath; + +/** + 获取当前小程序根目录 + */ ++ (NSString *)appRootDirPath:(NSString *)appId; + +/** + 获取当前小程序临时存储目录 + */ ++ (NSString *)appTempDirPath:(NSString *)appId; + +@end diff --git a/ios/Classes/FinAppletExt/Common/Manager/FATExtFileManager.m b/ios/Classes/FinAppletExt/Common/Manager/FATExtFileManager.m new file mode 100644 index 0000000..05cc9dc --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Manager/FATExtFileManager.m @@ -0,0 +1,60 @@ +// +// FATExtFileManager.m +// FinAppletExt +// +// Created by Haley on 2020/8/17. +// Copyright © 2020 finogeeks. All rights reserved. +// + +#import "FATExtFileManager.h" +#import "FATExtUtil.h" + +#import + +static NSString *FATEXT_PROJECT_ROOT = @"FinChatRoot"; +static NSString *FATEXT_PROJECT_ROOT_App = @"app"; +static NSString *FATEXT_PROJECT_ROOT_Framework = @"framework"; + +@implementation FATExtFileManager + ++ (NSString *)projectRootDirPath { + NSString *rootPath; + if ([FATExtUtil currentProductIdentificationIsEmpty]) { + rootPath = [NSTemporaryDirectory() stringByAppendingPathComponent:FATEXT_PROJECT_ROOT]; + } else { + rootPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[FATExtUtil currentProductIdentification]]; + } + return rootPath; +} + ++ (NSString *)projectRootAppsDirPath { + NSString *rootPath = [[FATExtFileManager projectRootDirPath] stringByAppendingFormat:@"/%@", FATEXT_PROJECT_ROOT_App]; + + return rootPath; +} + +/** + 获取当前小程序根路径 + + @return 获取当前小程序根路径 + */ ++ (NSString *)appRootDirPath:(NSString *)appId { + NSString *rootPath = [FATExtFileManager projectRootAppsDirPath]; + NSString *appDirPath = [rootPath stringByAppendingPathComponent:appId]; + return appDirPath; +} + +/** + 小程序临时存储目录 + + @return NSString * + */ ++ (NSString *)appTempDirPath:(NSString *)appId { + NSString *currtUserId = [FATExtUtil currentUserId]; + NSString *tempFileCachePath = [[FATExtFileManager appRootDirPath:appId] stringByAppendingPathComponent:[currtUserId fat_md5String]]; + tempFileCachePath = [tempFileCachePath stringByAppendingPathComponent:@"Temp"]; + + return tempFileCachePath; +} + +@end diff --git a/ios/Classes/FinAppletExt/Common/Manager/FATExtLocationManager.h b/ios/Classes/FinAppletExt/Common/Manager/FATExtLocationManager.h new file mode 100644 index 0000000..a4d5ee4 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Manager/FATExtLocationManager.h @@ -0,0 +1,17 @@ +// +// FATExtLocationManager.h +// FinAppletExt +// +// Created by beetle_92 on 2022/8/19. +// Copyright © 2022 finogeeks. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface FATExtLocationManager : CLLocationManager + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/Common/Manager/FATExtLocationManager.m b/ios/Classes/FinAppletExt/Common/Manager/FATExtLocationManager.m new file mode 100644 index 0000000..7805a3b --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Manager/FATExtLocationManager.m @@ -0,0 +1,47 @@ +// +// FATExtLocationManager.m +// FinAppletExt +// +// Created by beetle_92 on 2022/8/19. +// Copyright © 2022 finogeeks. All rights reserved. +// + +#import "FATExtLocationManager.h" +#import "FATExtAVManager.h" +#import "FATExtRecordManager.h" +#import "FATExt_LocationUpdateManager.h" + +#import + +@implementation FATExtLocationManager + +- (void)startUpdatingLocation { + [super startUpdatingLocation]; + if ([self.delegate isKindOfClass:NSClassFromString(@"FATMapView")]) { + return; + } + // 胶囊按钮 + UIViewController *vc = [[UIApplication sharedApplication] fat_topViewController]; + UINavigationController *nav = (UINavigationController *)vc.navigationController; + if ([nav respondsToSelector:@selector(controlCapsuleStateButton:state:animate:)]) { + [nav controlCapsuleStateButton:NO state:FATCapsuleButtonStateLocation animate:YES]; + } +} + +- (void)stopUpdatingLocation { + [super stopUpdatingLocation]; + // 胶囊按钮 + UIViewController *vc = [[UIApplication sharedApplication] fat_topViewController]; + UINavigationController *nav = (UINavigationController *)vc.navigationController; + if ([nav respondsToSelector:@selector(controlCapsuleStateButton:state:animate:)]) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [nav controlCapsuleStateButton:YES state:FATCapsuleButtonStateLocation animate:NO]; + [[FATExtAVManager sharedManager] checkRecordState]; + [[FATExtRecordManager shareManager] checkRecordState]; + [[FATExt_LocationUpdateManager sharedManager] checkLocationState]; + + }); + } +} + +@end diff --git a/ios/Classes/FinAppletExt/Common/Manager/FATExtMapManager.h b/ios/Classes/FinAppletExt/Common/Manager/FATExtMapManager.h new file mode 100644 index 0000000..dbbf900 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Manager/FATExtMapManager.h @@ -0,0 +1,25 @@ +// +// FATExtMapManager.h +// FinAppletExt +// +// Created by 王兆耀 on 2021/11/18. +// + +#import +#import "FATExtPrivateConstant.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FATExtMapManager : NSObject +@property (nonatomic, copy) NSString *pageId; +@property (nonatomic, strong) Class mapClass; +@property (nonatomic, strong) NSString *googleMapApiKey; +@property (nonatomic, strong) NSString *placesApiKey; ++ (instancetype)shareInstance; + +@property (nonatomic, strong) NSMutableDictionary *dataDic; + + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/Common/Manager/FATExtMapManager.m b/ios/Classes/FinAppletExt/Common/Manager/FATExtMapManager.m new file mode 100644 index 0000000..aabef68 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Manager/FATExtMapManager.m @@ -0,0 +1,44 @@ +// +// FATExtMapManager.m +// FinAppletExt +// +// Created by 王兆耀 on 2021/11/18. +// + +#import "FATExtMapManager.h" +#import +#import "FATMapViewDelegate.h" +#import "FATMapView.h" + +static FATExtMapManager *instance = nil; + +@implementation FATExtMapManager + ++ (instancetype)shareInstance { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[FATExtMapManager alloc] init]; + }); + + return instance; +} + ++ (instancetype)allocWithZone:(struct _NSZone *)zone { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [super allocWithZone:zone]; + }); + + return instance; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _dataDic = [[NSMutableDictionary alloc] init]; + self.mapClass = FATMapView.class; + } + return self; +} + +@end diff --git a/ios/Classes/FinAppletExt/Common/Manager/FATExtRecordManager.h b/ios/Classes/FinAppletExt/Common/Manager/FATExtRecordManager.h new file mode 100644 index 0000000..8f6c6ba --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Manager/FATExtRecordManager.h @@ -0,0 +1,30 @@ +// +// FATRecordManager.h +// FinAppletExt +// +// Created by Haley on 2021/1/21. +// Copyright © 2021 finogeeks. All rights reserved. +// + +#import + +@interface FATExtRecordManager : NSObject + ++ (instancetype)shareManager; + +#pragma mark - new api +- (BOOL)startRecordWithData:(NSDictionary *)data appletId:(NSString *)appletId eventBlock:(void (^)(NSInteger eventType,NSString *eventName, NSDictionary *paramDic,NSDictionary *extDic))eventBlock; + +- (BOOL)pauseRecordWithData:(NSDictionary *)data appletId:(NSString *)appletId; + +- (BOOL)resumeRecordWithData:(NSDictionary *)data appletId:(NSString *)appletId; + +- (BOOL)stopRecordWithData:(NSDictionary *)data appletId:(NSString *)appletId; + +- (BOOL)checkRecordWithMethod:(NSString *)method data:(NSDictionary *)data appletId:(NSString *)appletId; + +- (void)sendRecordFrameBufferWithData:(NSDictionary *)data appletId:(NSString *)appletId; + +- (void)checkRecordState; + +@end diff --git a/ios/Classes/FinAppletExt/Common/Manager/FATExtRecordManager.m b/ios/Classes/FinAppletExt/Common/Manager/FATExtRecordManager.m new file mode 100644 index 0000000..57dbd39 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Manager/FATExtRecordManager.m @@ -0,0 +1,362 @@ +// +// FATRecordManager.m +// FinAppletExt +// +// Created by Haley on 2021/1/21. +// Copyright © 2021 finogeeks. All rights reserved. +// + +#import "FATExtRecordManager.h" +#import "FATExtRecorder.h" +#import "FATExtUtil.h" + +#import + +static FATExtRecordManager *instance = nil; + +@interface FATExtRecordManager () + +@property (nonatomic, strong) NSMutableDictionary *recordDictionary; + +@property (nonatomic, strong) NSMutableDictionary *recorderDict; + +@end + +@implementation FATExtRecordManager + ++ (instancetype)shareManager { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[FATExtRecordManager alloc] init]; + instance.recordDictionary = [[NSMutableDictionary alloc] init]; + instance.recorderDict = [[NSMutableDictionary alloc] init]; + }); + return instance; +} + ++ (instancetype)allocWithZone:(struct _NSZone *)zone { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [super allocWithZone:zone]; + }); + return instance; +} + +- (instancetype)init { + self = [super init]; + if (self) { + [self p_addNotifications]; + } + return self; +} + +#pragma mark - private methods +- (void)p_addNotifications { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; +} + +- (void)_clearAudioSession { + AVAudioSession *audioSession = [AVAudioSession sharedInstance]; + [audioSession setCategory:AVAudioSessionCategoryPlayback error:NULL]; + [audioSession setActive:YES error:NULL]; +} + +#pragma mark - notification handler +- (void)applicationDidEnterBackground:(NSNotification *)notice { + [self.recorderDict enumerateKeysAndObjectsUsingBlock:^(NSString *key, FATExtRecorder *recorder, BOOL * _Nonnull stop) { + if (!recorder.isPausing) { + [self pauseRecordWithData:nil appletId:key]; + } + }]; +} + +#pragma mark - new api +- (BOOL)startRecordWithData:(NSDictionary *)data appletId:(NSString *)appletId eventBlock:(void (^)(NSInteger eventType,NSString *eventName, NSDictionary *paramDic,NSDictionary *extDic))eventBlock { + FATExtRecorder *record = [[FATExtRecorder alloc] init]; + record.delegate = self; + record.eventCallBack = eventBlock; + BOOL started = [record startRecordWithDict:data appId:appletId]; + if (started) { + [self.recorderDict setObject:record forKey:appletId]; + NSDictionary *params = @{@"method" : @"onStart"}; + + if (eventBlock) { + eventBlock(0,@"onRecorderManager",params,nil); + } + // 胶囊按钮 + UIViewController *vc = [[UIApplication sharedApplication] fat_topViewController]; + UINavigationController *nav = (UINavigationController *)vc.navigationController; + if ([nav respondsToSelector:@selector(controlCapsuleStateButton:state:animate:)]) { + [nav controlCapsuleStateButton:NO state:FATCapsuleButtonStateMicroPhone animate:YES]; + } + return YES; + } + + return NO; +} + +- (BOOL)pauseRecordWithData:(NSDictionary *)data appletId:(NSString *)appletId { + FATExtRecorder *record = [self.recorderDict objectForKey:appletId]; + if (!record) { + return NO; + } + BOOL result = [record pauseRecord]; + if (result) { + if (record.eventCallBack) { + NSDictionary *params = @{@"method" : @"onPause"}; + record.eventCallBack(0, @"onRecorderManager", params,nil); + } + // 胶囊按钮 + UIViewController *vc = [[UIApplication sharedApplication] fat_topViewController]; + UINavigationController *nav = (UINavigationController *)vc.navigationController; + if ([nav respondsToSelector:@selector(controlCapsuleStateButton:state:animate:)]) { + [nav controlCapsuleStateButton:YES state:FATCapsuleButtonStateMicroPhone animate:NO]; + } + return YES; + } + + return NO; +} + +- (BOOL)resumeRecordWithData:(NSDictionary *)data appletId:(NSString *)appletId { + FATExtRecorder *record = [self.recorderDict objectForKey:appletId]; + BOOL result = [record resumeRecord]; + if (result) { + if (record.eventCallBack) { + NSDictionary *params = @{@"method" : @"onResume"}; + record.eventCallBack(0, @"onRecorderManager", params,nil); + } + // 胶囊按钮 + UIViewController *vc = [[UIApplication sharedApplication] fat_topViewController]; + UINavigationController *nav = (UINavigationController *)vc.navigationController; + if ([nav respondsToSelector:@selector(controlCapsuleStateButton:state:animate:)]) { + [nav controlCapsuleStateButton:NO state:FATCapsuleButtonStateMicroPhone animate:YES]; + } + return YES; + } + + return NO; +} + +- (BOOL)stopRecordWithData:(NSDictionary *)data appletId:(NSString *)appletId { + FATExtRecorder *record = [self.recorderDict objectForKey:appletId]; + [record stopRecord]; + // 胶囊按钮 + UIViewController *vc = [[UIApplication sharedApplication] fat_topViewController]; + UINavigationController *nav = (UINavigationController *)vc.navigationController; + if ([nav respondsToSelector:@selector(controlCapsuleStateButton:state:animate:)]) { + [nav controlCapsuleStateButton:YES state:FATCapsuleButtonStateMicroPhone animate:NO]; + } + return YES; +} + +- (BOOL)checkRecordWithMethod:(NSString *)method data:(NSDictionary *)data appletId:(NSString *)appletId { + FATExtRecorder *recorder = [self.recorderDict objectForKey:appletId]; + if ([method isEqualToString:@"start"]) { // 录制中 或 暂停 时,不能开始 + if (recorder.isRecording || recorder.isPausing) { + NSDictionary *params = @{ + @"method" : @"onError", + @"data" : @{@"errMsg" : @"is recording or paused"}, + }; + if (recorder.eventCallBack) { + recorder.eventCallBack(0, @"onRecorderManager", params,nil); + } + return NO; + } + } else if ([method isEqualToString:@"pause"]) { // 非录制中状态,不能暂停 + if (![recorder isRecording]) { + NSDictionary *params = @{ + @"method" : @"onError", + @"data" : @{@"errMsg" : @"not recording"}, + }; + if (recorder.eventCallBack) { + recorder.eventCallBack(0, @"onRecorderManager", params,nil); + } + return NO; + } + } else if ([method isEqualToString:@"resume"]) { // 非暂停状态,不能继续录制 + if (!recorder.isPausing) { + NSDictionary *params = @{ + @"method" : @"onError", + @"data" : @{@"errMsg" : @"not paused"}, + }; + if (recorder.eventCallBack) { + recorder.eventCallBack(0, @"onRecorderManager", params,nil); + } + return NO; + } + } else if ([method isEqualToString:@"stop"]) { // 非开始状态,不能停止 + if (!recorder.isStarted) { + NSDictionary *params = @{ + @"method" : @"onError", + @"data" : @{@"errMsg" : @"recorder not start"}, + }; + if (recorder.eventCallBack) { + recorder.eventCallBack(0, @"onRecorderManager", params,nil); + } + return NO; + } + } + return YES; +} + +- (void)sendRecordFrameBufferWithData:(NSDictionary *)data appletId:(NSString *)appletId { + FATExtRecorder *recorder = [self.recorderDict objectForKey:appletId]; + recorder.frameState = FATFrameStatePrepareToSend; + if (recorder.frameInfoArray.count == 0) { + recorder.waitToSendBuffer = YES; + return; + } + + [self sendFrameDataWithRecorder:recorder withFrameBufferData:nil]; +} + +- (void)sendFrameDataWithRecorder:(FATExtRecorder *)recorder withFrameBufferData:(NSData *)data { + // 0.判断是否为分包小程序 + + recorder.waitToSendBuffer = NO; + recorder.frameState = FATFrameStateAlreadyWillSend; + // 1.从文件中取出buffer挂载至jscore上 + NSDictionary *dict = [recorder.frameInfoArray firstObject]; + if (!dict) { + return; + } + +// NSNumber *frameIndex = dict[@"frameIndex"]; + NSString *frameBufferKey = dict[@"frameBufferKey"]; + NSString *frameBufferPath = dict[@"frameBufferPath"]; + NSNumber *isLastFrame = dict[@"isLastFrame"]; + + NSData *frameData; + if (data) { + frameData = data; + } else { + frameData = [NSData dataWithContentsOfFile:frameBufferPath options:0 error:nil]; + } + Byte *bytes = (Byte *)frameData.bytes; + NSMutableArray *arrayM = [NSMutableArray array]; + for (int i = 0; i < frameData.length; i++) { + int number = (int)bytes[i]; + [arrayM addObject:@(number)]; + } + + NSDictionary *params = @{ + @"method" : @"onFrameRecorded", + @"data" : @{ + @"isLastFrame": isLastFrame, + @"buffer_id": frameBufferKey + } + }; + NSDictionary *extDic = @{@"jsContextKey":frameBufferKey,@"jsContextValue":arrayM}; + [recorder.frameInfoArray removeObject:dict]; + if (recorder.eventCallBack) { + recorder.eventCallBack(0, @"onRecorderManager", params,extDic); + } +// [[FATExtCoreEventManager shareInstance] sendToServiceWithAppId:[[FATClient sharedClient] currentApplet].appId eventName:@"onRecorderManager" paramDict:params]; + // 2.将帧信息从数组中删除 + recorder.frameState = FATFrameStateAlreadySent; +} + +- (void)checkRecordState { + FATAppletInfo *appInfo = [[FATClient sharedClient] currentApplet]; + if (!appInfo.appId) { + return; + } + FATExtRecorder *recorder = [self.recorderDict objectForKey:appInfo.appId]; + if ([recorder isRecording]) { + // 胶囊按钮 + UIViewController *vc = [[UIApplication sharedApplication] fat_topViewController]; + UINavigationController *nav = (UINavigationController *)vc.navigationController; + if ([nav respondsToSelector:@selector(controlCapsuleStateButton:state:animate:)]) { + [nav controlCapsuleStateButton:NO state:FATCapsuleButtonStateMicroPhone animate:YES]; + } + } +} + + +#pragma mark - FATExtRecorderDelegate +- (void)extRecorder:(FATExtRecorder *)recorder + onFrameData:(NSData *)frameData + frameIndex:(NSInteger)frameIndex + isLastFrame:(BOOL)isLastFrame { + NSString *frameBufferKey = [recorder.recordFilePath lastPathComponent]; + frameBufferKey = [frameBufferKey stringByDeletingPathExtension]; + frameBufferKey = [frameBufferKey stringByAppendingFormat:@"_%ld", (long)frameIndex]; + + NSString *frameFileName = [frameBufferKey stringByAppendingPathExtension:recorder.recordFilePath.pathExtension]; + NSString *frameBufferPath = [[recorder.recordFilePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:frameFileName]; + + NSDictionary *infoDict = @{@"frameIndex": @(frameIndex), + @"isLastFrame": @(isLastFrame), + @"frameBufferKey": frameBufferKey, + @"frameBufferPath": frameBufferPath + }; + + if ([NSThread isMainThread]) { + [recorder.frameInfoArray addObject:infoDict]; + if (recorder.frameState == FATFrameStatePrepareToSend || recorder.waitToSendBuffer) { + // 取出录音数据发送 + [self sendFrameDataWithRecorder:recorder withFrameBufferData:frameData]; + } + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + [recorder.frameInfoArray addObject:infoDict]; + if (recorder.frameState == FATFrameStatePrepareToSend || recorder.waitToSendBuffer) { + // 取出录音数据发送 + [self sendFrameDataWithRecorder:recorder withFrameBufferData:frameData]; + } + }); + } +} + +- (void)extRecorder:(FATExtRecorder *)recorder onError:(NSError *)error { + NSString *msg = error.localizedDescription ? : @"fail"; + NSDictionary *params = @{ + @"method" : @"onError", + @"data" : @{@"errMsg" : msg}, + }; + if (recorder.eventCallBack) { + recorder.eventCallBack(0, @"onRecorderManager", params,nil); + } +} + +- (void)extRecorderDidCompletion:(FATExtRecorder *)recorder { + NSString *filePath = recorder.recordFilePath; + NSURL *fileURL = [NSURL fileURLWithPath:filePath]; + float duration = [FATExtUtil durtaionWithFileURL:fileURL]; + long long fileSize = [FATExtUtil fileSizeWithFileURL:fileURL]; + NSString *tempFilePath = [@"finfile://" stringByAppendingString:filePath.lastPathComponent]; + + NSMutableDictionary *result = [NSMutableDictionary dictionary]; + [result setValue:tempFilePath forKey:@"tempFilePath"]; + [result setValue:@(duration) forKey:@"duration"]; + [result setValue:@(fileSize) forKey:@"fileSize"]; + + NSDictionary *params = @{ + @"method" : @"onStop", + @"data" : result, + }; + [self.recorderDict removeObjectForKey:recorder.recorderId]; + [self _clearAudioSession]; + if (recorder.eventCallBack) { + recorder.eventCallBack(0, @"onRecorderManager", params,nil); + } +} + +- (void)extRecorderBeginInterruption:(FATExtRecorder *)recorder { + NSDictionary *params = @{@"method" : @"onInterruptionBegin"}; + if (recorder.eventCallBack) { + recorder.eventCallBack(0, @"onRecorderManager", params,nil); + } + // 暂停录制 + [self pauseRecordWithData:nil appletId:recorder.recorderId]; +} + +- (void)extRecorderEndInterruption:(FATExtRecorder *)recorder withOptions:(NSUInteger)flags { + NSDictionary *params = @{@"method" : @"onInterruptionEnd"}; + if (recorder.eventCallBack) { + recorder.eventCallBack(0, @"onRecorderManager", params,nil); + } +} + +@end diff --git a/ios/Classes/FinAppletExt/Common/Manager/FATMapViewDelegate.h b/ios/Classes/FinAppletExt/Common/Manager/FATMapViewDelegate.h new file mode 100644 index 0000000..1f8fa61 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Manager/FATMapViewDelegate.h @@ -0,0 +1,84 @@ +// +// FATMapViewDelegate.h +// FinAppletExt +// +// Created by 王滔 on 2021/11/24. +// Copyright © 2021 finogeeks. All rights reserved. +// + +#ifndef FATMapViewDelegate_h +#define FATMapViewDelegate_h + +#import + +@protocol FATMapViewDelegate + +@required + +/// 发送Subscribe事件给page层 +/// eventName 事件名 +/// resultDic 事件的参数 +@property (nonatomic, copy) void(^eventCallBack)(NSString *eventName, NSDictionary *paramDic); + +- (instancetype)initWithParam:(NSDictionary *)param mapPageId:(NSString *)pageId; + +- (void)updateWithParam:(NSDictionary *)param; + +/// 获取中心点的经纬度 +- (NSDictionary *)fat_getCenter; + +/// 计算缩放级别 +- (double)fat_getScale; + +/// 移动到指定位置 +/// @param data 要移动的经纬度 +- (NSString *)fat_moveToLocation:(NSDictionary *)data; + +/// 缩放地图,展示所有的经纬度 +/// @param data 对应的数据 +- (void)fat_includePoints:(NSDictionary *)data; + +/// 获取左下,右上角的经纬度信息 +- (NSDictionary *)fat_mapgetRegion; + +/// 获取屏幕上的点对应的经纬度,坐标原点为地图左上角。 +- (NSDictionary *)fat_fromScreenLocation; + +/// 获取经纬度对应的屏幕坐标,坐标原点为地图左上角 +/// @param data 包含经纬度信息 +- (CGPoint)fat_toScreenLocation:(NSDictionary *)data; + +///// 打开地图app进行导航 +- (void)fat_openMapApp:(NSDictionary *)data; + +/// 在地图上添加大头针 +- (void)fat_addMarkers:(NSDictionary *)data; + +/// 在地图上移除大头针 +- (void)fat_removeMarkers:(NSDictionary *)data; + +/// 在地图上平移大头针 +- (BOOL)fat_translateMarker:(NSDictionary *)data; + +/// 沿指定路径移动 marker +- (BOOL)fat_moveAlong:(NSDictionary *)data; + +@optional + +/// 设置地图中心点偏移,向后向下为增长,屏幕比例范围(0.25~0.75),默认偏移为[0.5, 0.5] +- (void)mapSetCenterOffset:(NSDictionary *)data; + +- (void)fat_setLocMarkerIcon:(NSDictionary *)data; + +/// 获取当前地图的旋转角,地图sdk未实现返回nil +- (NSDictionary *)fat_getRotate; + +/// 获取当前地图的倾斜角 +- (NSDictionary *)fat_getskew; + +/// 用于customCallout的数据更新 +- (void)updateNativeMapMarkers:(NSDictionary *)param; + +@end + +#endif /* FATMapViewDelegate_h */ diff --git a/ios/Classes/FinAppletExt/Common/Util/FATExtHelper.h b/ios/Classes/FinAppletExt/Common/Util/FATExtHelper.h new file mode 100644 index 0000000..46e778b --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/FATExtHelper.h @@ -0,0 +1,15 @@ +// +// FATExtHelper.h +// FinAppletExt +// +// Created by Haley on 2020/8/19. +// Copyright © 2020 finogeeks. All rights reserved. +// + +#import + +@interface FATExtHelper : NSObject + ++ (UIImage *)fat_ext_imageFromBundleWithName:(NSString *)imageName; + +@end diff --git a/ios/Classes/FinAppletExt/Common/Util/FATExtHelper.m b/ios/Classes/FinAppletExt/Common/Util/FATExtHelper.m new file mode 100644 index 0000000..49a6414 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/FATExtHelper.m @@ -0,0 +1,21 @@ +// +// FATExtHelper.m +// FinAppletExt +// +// Created by Haley on 2020/8/19. +// Copyright © 2020 finogeeks. All rights reserved. +// + +#import "FATExtHelper.h" + +@implementation FATExtHelper + ++ (UIImage *)fat_ext_imageFromBundleWithName:(NSString *)imageName { + NSString *bundleResourcePath = [NSBundle bundleForClass:[FATExtHelper class]].resourcePath; + NSString *assetPath = [bundleResourcePath stringByAppendingPathComponent:@"FinAppletExt.bundle"]; + NSBundle *assetBundle = [NSBundle bundleWithPath:assetPath]; + NSString *path = [[assetBundle bundlePath] stringByAppendingPathComponent:[NSString stringWithFormat:@"/%@", imageName]]; + return [UIImage imageWithContentsOfFile:path]; +} + +@end diff --git a/ios/Classes/FinAppletExt/Common/Util/FATExtUtil.h b/ios/Classes/FinAppletExt/Common/Util/FATExtUtil.h new file mode 100644 index 0000000..441dd60 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/FATExtUtil.h @@ -0,0 +1,53 @@ +// +// FATExtUtil.h +// FinAppletExt +// +// Created by Haley on 2021/1/25. +// Copyright © 2021 finogeeks. All rights reserved. +// + +#import +#import +#import + +@interface FATExtUtil : NSObject + ++ (NSString *)tmpDirWithAppletId:(NSString *)appletId; + ++ (NSString *)fat_md5WithBytes:(char *)bytes length:(NSUInteger)length; + ++ (NSString *)jsonStringFromDict:(NSDictionary *)dict; + ++ (NSString *)jsonStringFromArray:(NSArray *)array; + ++ (NSString *)signFromDict:(NSDictionary *)dict; + ++ (NSString *)realPathForFINFile:(NSString *)finfile appId:(NSString *)appId; + +/// 获取音频文件时长 +/// @param fileURL 文件url(必须为AVURLAsset可解码的文件格式,如 .caf .aac .wav .mp3 等) ++ (float)durtaionWithFileURL:(NSURL *)fileURL; + +/// 获取音频文件大小 +/// @param fileURL 文件url ++ (long long)fileSizeWithFileURL:(NSURL *)fileURL; + +/// 获取userId ++ (NSString *)currentUserId; + +/** + 返回是否设置了产品标识。 + */ ++ (BOOL)currentProductIdentificationIsEmpty; + +/** + 返回设置了的产品标识。 + */ ++ (NSString *)currentProductIdentification; + ++ (NSString *)getAppName; + ++ (void)getNearbyPlacesByCategory:(NSString *)category coordinates:(CLLocationCoordinate2D)coordinates radius:(NSInteger)radius token:(NSString *)token completion:(void (^)(NSDictionary *))completion; + ++ (NSArray *)convertPlaceDictToArray:(NSDictionary*)dict; +@end diff --git a/ios/Classes/FinAppletExt/Common/Util/FATExtUtil.m b/ios/Classes/FinAppletExt/Common/Util/FATExtUtil.m new file mode 100644 index 0000000..505935f --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/FATExtUtil.m @@ -0,0 +1,263 @@ +// +// FATExtUtil.m +// FinAppletExt +// +// Created by Haley on 2021/1/25. +// Copyright © 2021 finogeeks. All rights reserved. +// + +#import "FATExtUtil.h" +#import "FATExtFileManager.h" +#import "FATExtMapManager.h" +#import "fincore.h" +#import "FATMapPlace.h" + +#import +#import + +#define FAT_EXT_FILE_SCHEMA @"finfile://" + +@implementation FATExtUtil + ++ (NSString *)tmpDirWithAppletId:(NSString *)appletId { + if (!appletId) { + return nil; + } + + NSString *cacheDir = [FATExtFileManager appTempDirPath:appletId]; + NSFileManager *fileManager = [NSFileManager defaultManager]; + BOOL flag = YES; + if (![fileManager fileExistsAtPath:cacheDir isDirectory:&flag]) { + [fileManager createDirectoryAtPath:cacheDir withIntermediateDirectories:YES attributes:nil error:nil]; + } + + return cacheDir; +} + ++ (NSString *)fat_md5WithBytes:(char *)bytes length:(NSUInteger)length { + unsigned char result[16]; + CC_MD5(bytes, (CC_LONG)length, result); + return [NSString stringWithFormat: + @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", + result[0], result[1], result[2], result[3], + result[4], result[5], result[6], result[7], + result[8], result[9], result[10], result[11], + result[12], result[13], result[14], result[15]]; +} + ++ (NSString *)jsonStringFromDict:(NSDictionary *)dict { + if (!dict || ![dict isKindOfClass:[NSDictionary class]]) { + return nil; + } + + NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:nil]; + if (!data) { + return nil; + } + + NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + return jsonString; +} + ++ (NSString *)jsonStringFromArray:(NSArray *)array { + if (!array || ![array isKindOfClass:[NSArray class]]) { + return nil; + } + + NSData *data = [NSJSONSerialization dataWithJSONObject:array options:NSJSONWritingPrettyPrinted error:nil]; + if (!data) { + return nil; + } + + NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + return jsonString; +} + ++ (NSString *)signFromDict:(NSDictionary *)dict { + if (!dict || ![dict isKindOfClass:[NSDictionary class]]) { + return nil; + } + NSArray *keys = [dict allKeys]; + NSArray *sortedKeys = [keys sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { + return [obj1 compare:obj2 options:NSNumericSearch]; //正序 + }]; + + NSString *plainText = @""; + for (NSString *key in sortedKeys) { + NSString *and = [plainText isEqualToString:@""] ? @"" : @"&"; + NSString *value = [dict valueForKey:key]; + // if ([value isKindOfClass:[NSDictionary class]]) { + // NSDictionary *dictValue = (NSDictionary *)value; + // value = [FATExtUtil jsonStringFromDict:dictValue]; + // } else if ([key isKindOfClass:[NSArray class]]) { + // NSArray *arrayValue = (NSArray *)value; + // value = [FATExtUtil jsonStringFromArray:arrayValue]; + // } + if ([key isEqualToString:@"sign"]) { + continue; + } + if ([value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]]) { + NSString *append = [NSString stringWithFormat:@"%@%@=%@", and, key, value]; + plainText = [plainText stringByAppendingString:append]; + } + } +// NSLog(@"扩展:%@", plainText); + NSString *digest = [[[SDKCoreClient sharedInstance].finoLicenseService fin_messageDigest:plainText] uppercaseString]; + NSData *data = [digest dataUsingEncoding:NSUTF8StringEncoding]; + NSData *encodeData = [[SDKCoreClient sharedInstance].finoLicenseService fin_encodeSMContent:data]; + NSString *sign = [[NSString alloc] initWithData:encodeData encoding:NSUTF8StringEncoding]; +// NSLog(@"扩展sign:%@", sign); + + return sign; +} + +/// 获取音频文件时长 +/// @param fileURL 文件url(必须为AVURLAsset可解码的文件格式,如 .caf .aac .wav .mp3 等) ++ (float)durtaionWithFileURL:(NSURL *)fileURL { + NSDictionary *options = @{AVURLAssetPreferPreciseDurationAndTimingKey : @(YES)}; + AVURLAsset *recordAsset = [AVURLAsset URLAssetWithURL:fileURL options:options]; + // 录音的时长,单位ms + CMTime durationTime = recordAsset.duration; + durationTime.value = durationTime.value; + float seconds = CMTimeGetSeconds(durationTime); + float duration = seconds * 1000; + return duration; +} + +/// 获取音频文件大小 +/// @param fileURL 文件url ++ (long long)fileSizeWithFileURL:(NSURL *)fileURL { + // 录音文件的大小,单位Byte + NSFileManager *manager = [NSFileManager defaultManager]; + long long fileSize = [[manager attributesOfItemAtPath:fileURL.path error:nil] fileSize]; + return fileSize; +} + ++ (NSString *)currentUserId { + NSString *currentUserId = [FATClient sharedClient].config.currentUserId; + NSString *productIdentification = [FATClient sharedClient].config.productIdentification; + if (!currentUserId || currentUserId.length == 0) { + if ([NSString fat_isEmptyWithString:productIdentification]) { + currentUserId = @"finclip_default"; + } else { + currentUserId = productIdentification; + } + } + return currentUserId; +} + ++ (BOOL)currentProductIdentificationIsEmpty { + NSString *productIdentification = [FATClient sharedClient].config.productIdentification; + return [NSString fat_isEmptyWithString:productIdentification]; +} + ++ (NSString *)currentProductIdentification { + NSString *productIdentification = [FATClient sharedClient].config.productIdentification; + return productIdentification; +} + ++ (NSString *)getAppName { + NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary]; + NSString *appName = [infoDict valueForKey:@"CFBundleDisplayName"]; + if (!appName) appName = [infoDict valueForKey:@"CFBundleName"]; + if (!appName) appName = [infoDict valueForKey:@"CFBundleExecutable"]; + return appName; +} + ++ (void)getNearbyPlacesByCategory:(NSString *)category coordinates:(CLLocationCoordinate2D)coordinates radius:(NSInteger)radius token:(NSString *)token + completion:(void (^)(NSDictionary *))completion { + NSURL *url = [NSURL URLWithString:[self searchApiHost]]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + request.HTTPMethod = @"GET"; + + if (token && [token length] > 0) { + NSDictionary *parameters = @{ + @"key" : [FATExtMapManager shareInstance].googleMapApiKey, + @"pagetoken" : token + }; + NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; + NSMutableArray *queryItems = [NSMutableArray array]; + for (NSString *key in parameters) { + NSString *value = [NSString stringWithFormat:@"%@", parameters[key]]; + [queryItems addObject:[NSURLQueryItem queryItemWithName:key value:value]]; + } + urlComponents.queryItems = queryItems; + request.URL = urlComponents.URL; + } else { + NSDictionary *parameters = @{ + @"key" : [FATExtMapManager shareInstance].placesApiKey, + @"radius" : @(radius), + @"location" : [NSString stringWithFormat:@"%f,%f", coordinates.latitude, coordinates.longitude], + @"type" : [category lowercaseString] + }; + NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; + NSMutableArray *queryItems = [NSMutableArray array]; + for (NSString *key in parameters) { + NSString *value = [NSString stringWithFormat:@"%@", parameters[key]]; + [queryItems addObject:[NSURLQueryItem queryItemWithName:key value:value]]; + } + urlComponents.queryItems = queryItems; + request.URL = urlComponents.URL; + } + + NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + if (error) { + NSLog(@"Error: %@", error); + completion(nil); + return; + } + + if (data) { + NSError *jsonError; + NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; + if (jsonError) { + NSLog(@"Error: %@", jsonError); + completion(nil); + } else { + completion(json); + } + } else { + completion(nil); + } + }]; + + [task resume]; +} + ++ (NSArray *)convertPlaceDictToArray:(NSDictionary*)dict{ + NSMutableArray *placeArrayM = [NSMutableArray array]; + for (NSDictionary *dictItem in [dict objectForKey:@"results"]) { + FATMapPlace *place = [[FATMapPlace alloc] init]; + place.name = dictItem[@"name"]; + place.address = dictItem[@"vicinity"]; + FATMapPlace *mark = [[FATMapPlace alloc] init]; + NSDictionary *dict = dictItem[@"geometry"]; + mark.name = dictItem[@"name"]; + mark.address = dictItem[@"vicinity"]; + double lat = [dict[@"location"][@"lat"] doubleValue]; + double lng = [dict[@"location"][@"lng"] doubleValue]; + place.location = [[CLLocation alloc] initWithLatitude:lat longitude:lng] ; + [placeArrayM addObject:place]; + } + return placeArrayM; +} + ++ (NSArray *)getCategories { + NSArray *list = @[@"Places",@"Bakery", @"Doctor", @"School", @"Taxi_stand", @"Hair_care", @"Restaurant", @"Pharmacy", @"Atm", @"Gym", @"Store", @"Spa"]; + return list; +} + ++ (NSString *)searchApiHost { + return @"https://maps.googleapis.com/maps/api/place/nearbysearch/json"; +} + ++ (NSString *)googlePhotosHost { + return @"https://maps.googleapis.com/maps/api/place/photo"; +} + ++ (NSString *)googlePlaceDetailsHost { + return @"https://maps.googleapis.com/maps/api/place/details/json"; +} + + +@end diff --git a/ios/Classes/FinAppletExt/Common/Util/Location/FATLocationManager.h b/ios/Classes/FinAppletExt/Common/Util/Location/FATLocationManager.h new file mode 100644 index 0000000..74618d9 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/Location/FATLocationManager.h @@ -0,0 +1,22 @@ +// +// FATLocationManager.h +// FinApplet +// +// Created by Haley on 2020/4/7. +// Copyright © 2020 finogeeks. All rights reserved. +// + +#import +#import + +@interface FATLocationManager : NSObject + +@property (nonatomic, strong) CLLocation *location; + +@property (nonatomic, strong) CLPlacemark *placemark; + ++ (instancetype)manager; + +- (void)updateLocation; + +@end diff --git a/ios/Classes/FinAppletExt/Common/Util/Location/FATLocationManager.m b/ios/Classes/FinAppletExt/Common/Util/Location/FATLocationManager.m new file mode 100644 index 0000000..1014af5 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/Location/FATLocationManager.m @@ -0,0 +1,99 @@ +// +// FATLocationManager.m +// FinApplet +// +// Created by Haley on 2020/4/7. +// Copyright © 2020 finogeeks. All rights reserved. +// + +#import "FATLocationManager.h" +#import "FATWGS84ConvertToGCJ02.h" +#import "FATExtLocationManager.h" + +static FATLocationManager *instance = nil; + +@interface FATLocationManager () + +@property (nonatomic, strong) FATExtLocationManager *locationManager; + +@end + +@implementation FATLocationManager + ++ (instancetype)manager { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[FATLocationManager alloc] init]; + }); + return instance; +} + ++ (instancetype)allocWithZone:(struct _NSZone *)zone { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [super allocWithZone:zone]; + }); + return instance; +} + +- (void)updateLocation { + if (![FATExtLocationManager locationServicesEnabled]) { + return; + } + + CLAuthorizationStatus status = [FATExtLocationManager authorizationStatus]; + if (status == kCLAuthorizationStatusAuthorizedWhenInUse || + status == kCLAuthorizationStatusAuthorizedAlways || + status == kCLAuthorizationStatusNotDetermined) { + //定位功能可用 + FATExtLocationManager *locationManager = [[FATExtLocationManager alloc] init]; + self.locationManager = locationManager; + locationManager.delegate = self; + locationManager.desiredAccuracy = kCLLocationAccuracyBest; + [locationManager requestWhenInUseAuthorization]; + [locationManager startUpdatingLocation]; + + } else if (status == kCLAuthorizationStatusDenied) { + } +} + +- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { + CLLocation *newLocation = [locations firstObject]; + //判断是不是属于国内范围 + if (![FATWGS84ConvertToGCJ02ForAMapView isLocationOutOfChina:[newLocation coordinate]]) { + //转换后的coord + CLLocationCoordinate2D coord = [FATWGS84ConvertToGCJ02ForAMapView transformFromWGSToGCJ:[newLocation coordinate]]; + newLocation = [[CLLocation alloc] initWithLatitude:coord.latitude longitude:coord.longitude]; + } + self.location = newLocation; + + [self.locationManager stopUpdatingLocation]; + + CLGeocoder *geocoder = [[CLGeocoder alloc] init]; + [geocoder reverseGeocodeLocation:newLocation completionHandler:^(NSArray *_Nullable placemarks, NSError *_Nullable error) { + if (error) { + return; + } + + if (placemarks.count > 0) { + CLPlacemark *placemark = [placemarks objectAtIndex:0]; + self.placemark = placemark; + // //获取省份 + // NSString *province = placemark.administrativeArea; + // // 位置名 + // NSLog(@"name,%@", placemark.name); + // // 街道 + // NSLog(@"thoroughfare,%@", placemark.thoroughfare); + // // 子街道 + // NSLog(@"subThoroughfare,%@", placemark.subThoroughfare); + // // 市 + // NSLog(@"locality,%@", placemark.locality); + // // 区 + // NSLog(@"subLocality,%@", placemark.subLocality); + // // 国家 + // NSLog(@"country,%@", placemark.country); + } + }]; +} + +@end diff --git a/ios/Classes/FinAppletExt/Common/Util/Location/FATWGS84ConvertToGCJ02.h b/ios/Classes/FinAppletExt/Common/Util/Location/FATWGS84ConvertToGCJ02.h new file mode 100644 index 0000000..7393dd8 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/Location/FATWGS84ConvertToGCJ02.h @@ -0,0 +1,19 @@ +// +// FATWGS84ConvertToGCJ02.h +// FinApplet +// +// Created by 杨涛 on 2018/8/9. +// Copyright © 2018年 finogeeks. All rights reserved. +// + +#import +#import + +@interface FATWGS84ConvertToGCJ02ForAMapView : NSObject +//判断是否已经超出中国范围 ++ (BOOL)isLocationOutOfChina:(CLLocationCoordinate2D)location; +//转GCJ-02 ++ (CLLocationCoordinate2D)transformFromWGSToGCJ:(CLLocationCoordinate2D)wgsLoc; +// gcj02转wgs82 ++ (CLLocationCoordinate2D)transformFromGCJToWGS:(CLLocationCoordinate2D)wgsLoc; +@end diff --git a/ios/Classes/FinAppletExt/Common/Util/Location/FATWGS84ConvertToGCJ02.m b/ios/Classes/FinAppletExt/Common/Util/Location/FATWGS84ConvertToGCJ02.m new file mode 100644 index 0000000..8f55b66 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/Location/FATWGS84ConvertToGCJ02.m @@ -0,0 +1,135 @@ +// +// FATWGS84ConvertToGCJ02ForAMapView.m +// FinApplet +// +// Created by 杨涛 on 2018/8/9. +// Copyright © 2018年 finogeeks. All rights reserved. +// + +#import "FATWGS84ConvertToGCJ02.h" +#define RANGE_LON_MAX 137.8347 +#define RANGE_LON_MIN 72.004 +#define RANGE_LAT_MAX 55.8271 +#define RANGE_LAT_MIN 0.8293 +#define LAT_OFFSET_0(x,y) -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(fabs(x)) +#define LAT_OFFSET_1 (20.0 * sin(6.0 * x * M_PI) + 20.0 * sin(2.0 * x * M_PI)) * 2.0 / 3.0 +#define LAT_OFFSET_2 (20.0 * sin(y * M_PI) + 40.0 * sin(y / 3.0 * M_PI)) * 2.0 / 3.0 +#define LAT_OFFSET_3 (160.0 * sin(y / 12.0 * M_PI) + 320 * sin(y * M_PI / 30.0)) * 2.0 / 3.0 + +#define LON_OFFSET_0(x,y) 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(fabs(x)) +#define LON_OFFSET_1 (20.0 * sin(6.0 * x * M_PI) + 20.0 * sin(2.0 * x * M_PI)) * 2.0 / 3.0 +#define LON_OFFSET_2 (20.0 * sin(x * M_PI) + 40.0 * sin(x / 3.0 * M_PI)) * 2.0 / 3.0 +#define LON_OFFSET_3 (150.0 * sin(x / 12.0 * M_PI) + 300.0 * sin(x / 30.0 * M_PI)) * 2.0 / 3.0 +#define jzA 6378245.0 +#define jzEE 0.00669342162296594323 + +static const double fat_a = 6378245.0; +static const double fat_ee = 0.00669342162296594323; + +@implementation FATWGS84ConvertToGCJ02ForAMapView ++ (CLLocationCoordinate2D)transformFromWGSToGCJ:(CLLocationCoordinate2D)wgsLoc { + CLLocationCoordinate2D adjustLoc; + if ([self isLocationOutOfChina:wgsLoc]) { + adjustLoc = wgsLoc; + } else { + double adjustLat = [self transformLatWithX:wgsLoc.longitude - 105.0 withY:wgsLoc.latitude - 35.0]; + double adjustLon = [self transformLonWithX:wgsLoc.longitude - 105.0 withY:wgsLoc.latitude - 35.0]; + double radLat = wgsLoc.latitude / 180.0 * M_PI; + double magic = sin(radLat); + magic = 1 - fat_ee * magic * magic; + double sqrtMagic = sqrt(magic); + adjustLat = (adjustLat * 180.0) / ((fat_a * (1 - fat_ee)) / (magic * sqrtMagic) * M_PI); + adjustLon = (adjustLon * 180.0) / (fat_a / sqrtMagic * cos(radLat) * M_PI); + adjustLoc.latitude = wgsLoc.latitude + adjustLat - 0.00039900; // 减去这个数字 完全是凑数,准确性有待验证 + adjustLoc.longitude = wgsLoc.longitude + adjustLon; + } + return adjustLoc; +} + ++ (CLLocationCoordinate2D)transformFromGCJToWGS:(CLLocationCoordinate2D)wgsLoc { + return [self gcj02Decrypt:wgsLoc.latitude gjLon:wgsLoc.longitude]; +} + ++ (CLLocationCoordinate2D)gcj02Decrypt:(double)gjLat gjLon:(double)gjLon { + CLLocationCoordinate2D gPt = [self gcj02Encrypt:gjLat bdLon:gjLon]; + double dLon = gPt.longitude - gjLon; + double dLat = gPt.latitude - gjLat; + CLLocationCoordinate2D pt; + pt.latitude = gjLat - dLat; + pt.longitude = gjLon - dLon; + return pt; +} + ++ (CLLocationCoordinate2D)gcj02Encrypt:(double)ggLat bdLon:(double)ggLon { + CLLocationCoordinate2D resPoint; + double mgLat; + double mgLon; + if ([self outOfChina:ggLat bdLon:ggLon]) { + resPoint.latitude = ggLat; + resPoint.longitude = ggLon; + return resPoint; + } + double dLat = [self transformLat:(ggLon - 105.0)bdLon:(ggLat - 35.0)]; + double dLon = [self transformLon:(ggLon - 105.0) bdLon:(ggLat - 35.0)]; + double radLat = ggLat / 180.0 * M_PI; + double magic = sin(radLat); + magic = 1 - jzEE * magic * magic; + double sqrtMagic = sqrt(magic); + dLat = (dLat * 180.0) / ((jzA * (1 - jzEE)) / (magic * sqrtMagic) * M_PI); + dLon = (dLon * 180.0) / (jzA / sqrtMagic * cos(radLat) * M_PI); + mgLat = ggLat + dLat; + mgLon = ggLon + dLon; + + resPoint.latitude = mgLat; + resPoint.longitude = mgLon; + return resPoint; +} + ++ (BOOL)outOfChina:(double)lat bdLon:(double)lon { + if (lon < RANGE_LON_MIN || lon > RANGE_LON_MAX) + return true; + if (lat < RANGE_LAT_MIN || lat > RANGE_LAT_MAX) + return true; + return false; +} + ++ (double)transformLat:(double)x bdLon:(double)y { + double ret = LAT_OFFSET_0(x, y); + ret += LAT_OFFSET_1; + ret += LAT_OFFSET_2; + ret += LAT_OFFSET_3; + return ret; +} + ++ (double)transformLon:(double)x bdLon:(double)y { + double ret = LON_OFFSET_0(x, y); + ret += LON_OFFSET_1; + ret += LON_OFFSET_2; + ret += LON_OFFSET_3; + return ret; +} + +//判断是不是在中国 ++ (BOOL)isLocationOutOfChina:(CLLocationCoordinate2D)location { + if (location.longitude < 72.004 || location.longitude > 137.8347 || location.latitude < 0.8293 || location.latitude > 55.8271) + return YES; + return NO; +} + ++ (double)transformLatWithX:(double)x withY:(double)y { + double lat = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(fabs(x)); + lat += (20.0 * sin(6.0 * x * M_PI) + 20.0 * sin(2.0 * x * M_PI)) * 2.0 / 3.0; + lat += (20.0 * sin(y * M_PI) + 40.0 * sin(y / 3.0 * M_PI)) * 2.0 / 3.0; + lat += (160.0 * sin(y / 12.0 * M_PI) + 320 * sin(y * M_PI / 30.0)) * 2.0 / 3.0; + return lat; +} + ++ (double)transformLonWithX:(double)x withY:(double)y { + double lon = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(fabs(x)); + lon += (20.0 * sin(6.0 * x * M_PI) + 20.0 * sin(2.0 * x * M_PI)) * 2.0 / 3.0; + lon += (20.0 * sin(x * M_PI) + 40.0 * sin(x / 3.0 * M_PI)) * 2.0 / 3.0; + lon += (150.0 * sin(x / 12.0 * M_PI) + 300.0 * sin(x / 30.0 * M_PI)) * 2.0 / 3.0; + return lon; +} + +@end diff --git a/ios/Classes/FinAppletExt/Common/Util/Location/UIView+FATExtSafaFrame.h b/ios/Classes/FinAppletExt/Common/Util/Location/UIView+FATExtSafaFrame.h new file mode 100644 index 0000000..6134cd5 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/Location/UIView+FATExtSafaFrame.h @@ -0,0 +1,17 @@ +// +// UIView+FATExtSafaFrame.h +// FinAppletExt +// +// Created by 王兆耀 on 2023/5/25. +// Copyright © 2023 finogeeks. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIView (FATExtSafaFrame) + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/Common/Util/Location/UIView+FATExtSafaFrame.m b/ios/Classes/FinAppletExt/Common/Util/Location/UIView+FATExtSafaFrame.m new file mode 100644 index 0000000..5909e1a --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/Location/UIView+FATExtSafaFrame.m @@ -0,0 +1,70 @@ +// +// UIView+FATExtSafaFrame.m +// FinAppletExt +// +// Created by 王兆耀 on 2023/5/25. +// Copyright © 2023 finogeeks. All rights reserved. +// + +#import "UIView+FATExtSafaFrame.h" +#import + +@implementation UIView (FATExtSafaFrame) + ++ (void)load{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Method method1 = class_getInstanceMethod(self.class, @selector(setFrame:)); + Method method2 = class_getInstanceMethod(self.class, @selector(fatSafe_setFrame:)); + method_exchangeImplementations(method1, method2); + Method method3 = class_getInstanceMethod(self.class, @selector(setCenter:)); + Method method4 = class_getInstanceMethod(self.class, @selector(fatSafe_setCenter:)); + method_exchangeImplementations(method3, method4); + }); +} + +- (void)fatSafe_setFrame:(CGRect)frame{ + + CGRect unitFrame = frame; + + if(isnan(unitFrame.origin.x)){ + return; + } + if(isnan(unitFrame.origin.y)){ + return; + } + if(isnan(unitFrame.size.width)){ + return; + } + if(isnan(unitFrame.size.height)){ + return; + } + + @try { + [self fatSafe_setFrame:unitFrame]; + } @catch (NSException *exception) { + + } @finally { + } +} + +- (void)fatSafe_setCenter:(CGPoint)center{ + + CGPoint unitCenter = center; + + if(isnan(unitCenter.x)){ + return; + } + if(isnan(unitCenter.y)){ + return; + } + + @try { + [self fatSafe_setCenter:unitCenter]; + } @catch (NSException *exception) { + + } @finally { + } +} + +@end diff --git a/ios/Classes/FinAppletExt/Common/Util/Map/FATAnnotation.h b/ios/Classes/FinAppletExt/Common/Util/Map/FATAnnotation.h new file mode 100644 index 0000000..ac2f1b9 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/Map/FATAnnotation.h @@ -0,0 +1,21 @@ +// +// FATAnnotation.h +// AppletDemo +// +// Created by Haley on 2020/4/17. +// Copyright © 2020 weidian. All rights reserved. +// + +#import +#import + +@interface FATAnnotation : NSObject + +@property (nonatomic, assign) CLLocationCoordinate2D coordinate; + +@property (nonatomic, copy) NSString *title; +@property (nonatomic, copy) NSString *address; + +- (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate; + +@end diff --git a/ios/Classes/FinAppletExt/Common/Util/Map/FATAnnotation.m b/ios/Classes/FinAppletExt/Common/Util/Map/FATAnnotation.m new file mode 100644 index 0000000..81ea8ac --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/Map/FATAnnotation.m @@ -0,0 +1,20 @@ +// +// FATAnnotation.m +// AppletDemo +// +// Created by Haley on 2020/4/17. +// Copyright © 2020 weidian. All rights reserved. +// + +#import "FATAnnotation.h" + +@implementation FATAnnotation + +- (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate { + if (self = [super init]) { + self.coordinate = coordinate; + } + return self; +} + +@end diff --git a/ios/Classes/FinAppletExt/Common/Util/Map/FATExtChoosePoiViewController.h b/ios/Classes/FinAppletExt/Common/Util/Map/FATExtChoosePoiViewController.h new file mode 100644 index 0000000..80c7f0c --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/Map/FATExtChoosePoiViewController.h @@ -0,0 +1,22 @@ +// +// FATExtChoosePoiViewController.h +// FinAppletExt +// +// Created by 王兆耀 on 2021/12/8. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^SureBlock)(NSDictionary *locationInfo); + +@interface FATExtChoosePoiViewController : FATUIViewController + +@property (nonatomic, copy) dispatch_block_t cancelBlock; +@property (nonatomic, copy) SureBlock sureBlock; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/Common/Util/Map/FATExtChoosePoiViewController.m b/ios/Classes/FinAppletExt/Common/Util/Map/FATExtChoosePoiViewController.m new file mode 100644 index 0000000..3e20486 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/Map/FATExtChoosePoiViewController.m @@ -0,0 +1,389 @@ +// +// FATExtChoosePoiViewController.m +// FinAppletExt +// +// Created by 王兆耀 on 2021/12/8. +// + +#import "FATExtChoosePoiViewController.h" +#import "FATLocationResultViewController.h" +#import "FATAnnotation.h" +#import "FATExtHelper.h" +#import "FATExtLocationManager.h" +#import "FATExtMapManager.h" +#import "FATExtUtil.h" + +#import + +static NSString *kAnnotationId = @"FATAnnotationViewId"; +static NSString *kUserAnnotationId = @"FATUserAnnotationViewId"; + +@interface FATExtChoosePoiViewController () + +@property (nonatomic, strong) UIImageView *centerLocationView; +@property (nonatomic, strong) MKUserLocation *userLocation; + +@property (nonatomic, strong) UITableView *tableView; + +@property (nonatomic, strong) UIActivityIndicatorView *indicatorView; + +@property (nonatomic, strong) UISearchController *searchController; +@property (nonatomic, strong) FATLocationResultViewController *locationResultVC; +@property (nonatomic, strong) FATExtLocationManager *locationManager; +@property (nonatomic, strong) NSMutableArray *dataArray; +@property (nonatomic, copy) NSString *cityName; +@end + +@implementation FATExtChoosePoiViewController + +- (NSMutableArray *)dataArray { + if (!_dataArray) { + _dataArray = [[NSMutableArray alloc] init]; + } + return _dataArray; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view. + + self.edgesForExtendedLayout = UIRectEdgeNone; + + [self p_initNavigationBar]; + + [self p_initSubViews]; + + [self startLocation]; +} + +- (void)viewWillLayoutSubviews { + [super viewWillLayoutSubviews]; + + CGFloat width = self.view.bounds.size.width; + CGFloat height = self.view.bounds.size.height; + CGFloat searchBarH = self.searchController.searchBar.bounds.size.height; + + CGFloat totalH = height - searchBarH; + + self.centerLocationView.center = CGPointMake(width * 0.5, totalH * 0.5 * 0.5); + + self.tableView.frame = CGRectMake(0, 0, width, height); + + self.indicatorView.center = CGPointMake(width * 0.5, height * 0.5); +} + +- (void)dealloc { + _searchController = nil; + _locationResultVC = nil; +} + +#pragma mark - private method +- (void)p_initNavigationBar { + self.title = [[FATClient sharedClient] fat_localizedStringForKey:@"Location"]; + + NSString *cancel = [[FATClient sharedClient] fat_localizedStringForKey:@"Cancel"]; + NSString *ok = [[FATClient sharedClient] fat_localizedStringForKey:@"OK"]; + UIButton *cancelButton = [[UIButton alloc] init]; + [cancelButton setTitle:cancel forState:UIControlStateNormal]; + [cancelButton setTitleColor:[UIColor systemBlueColor] forState:UIControlStateNormal]; + [cancelButton addTarget:self action:@selector(cancelItemClick) forControlEvents:UIControlEventTouchUpInside]; + + UIButton *okButton = [[UIButton alloc] init]; + [okButton setTitle:ok forState:UIControlStateNormal]; + [okButton setTitleColor:[UIColor systemBlueColor] forState:UIControlStateNormal]; + [okButton addTarget:self action:@selector(sureItemClick) forControlEvents:UIControlEventTouchUpInside]; + + self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:cancelButton]; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:okButton]; +} + +- (void)p_initSubViews { + [self p_initSearchBar]; + + self.centerLocationView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)]; + self.centerLocationView.image = [FATExtHelper fat_ext_imageFromBundleWithName:@"fav_fileicon_loc90"]; + + [self.view addSubview:self.tableView]; + + [self.view addSubview:self.indicatorView]; + [self.indicatorView startAnimating]; +} + +//开始定位 +- (void)startLocation { + // CLog(@"--------开始定位"); + self.locationManager = [[FATExtLocationManager alloc] init]; + self.locationManager.delegate = self; + // 控制定位精度,越高耗电量越 + self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer; + + [self.locationManager requestWhenInUseAuthorization]; + self.locationManager.distanceFilter = 10.0f; + [self.locationManager startUpdatingLocation]; +} + +- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { + if ([error code] == kCLErrorDenied) { + // NSLog(@"访问被拒绝"); + } + if ([error code] == kCLErrorLocationUnknown) { + // NSLog(@"无法获取位置信息"); + } +} +//定位代理经纬度回调 +- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { + CLLocation *newLocation = locations[0]; + + CLLocationCoordinate2D centerCoordinate = newLocation.coordinate; + CLGeocoder *geocoder = [[CLGeocoder alloc] init]; + CLLocation *location = [[CLLocation alloc] initWithLatitude:centerCoordinate.latitude longitude:centerCoordinate.longitude]; + [geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *array, NSError *error) { + CLPlacemark *placemark = nil; + if (!error) { + placemark = [array firstObject]; + } + // 获取当前城市名 + NSString *city = placemark.locality; + if (!city) { + city = placemark.administrativeArea; + } + self.cityName = city; + MKLocalSearchRequest *request = [[MKLocalSearchRequest alloc] init]; + MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(centerCoordinate, 1000, 1000); + request.region = region; + request.naturalLanguageQuery = [[FATClient sharedClient] fat_localizedStringForKey:@"Office Building"]; + + MKLocalSearch *localSearch = [[MKLocalSearch alloc] initWithRequest:request]; + if([FATExtMapManager shareInstance].googleMapApiKey.length > 1){ + [FATExtUtil getNearbyPlacesByCategory:request.naturalLanguageQuery coordinates: region.center radius:1000 token:@"" + completion:^(NSDictionary * _Nonnull dict) { + + self.dataArray = [[NSMutableArray alloc] initWithArray: [FATExtUtil convertPlaceDictToArray:dict]]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [self.indicatorView stopAnimating]; + [self addSpecialCity]; + [self.tableView reloadData]; + }); + }]; + }else { + [localSearch startWithCompletionHandler:^(MKLocalSearchResponse *_Nullable response, NSError *_Nullable error) { + //NSMutableArray *placeArrayM = [NSMutableArray array]; + if (placemark) { + FATMapPlace *place = [[FATMapPlace alloc] init]; + place.name = placemark.name; + place.address = placemark.thoroughfare; + place.location = placemark.location; + place.selected = YES; + [self.dataArray addObject:place]; + } + + for (MKMapItem *item in response.mapItems) { + if (!item.isCurrentLocation) { + FATMapPlace *place = [[FATMapPlace alloc] init]; + place.name = item.placemark.name; + place.address = item.placemark.thoroughfare; + place.location = item.placemark.location; + [self.dataArray addObject:place]; + } + } + [self.indicatorView stopAnimating]; + [self addSpecialCity]; + [self.tableView reloadData]; + }]; + } + }]; + [manager stopUpdatingLocation]; +} + +- (void)p_initSearchBar { + _locationResultVC = [FATLocationResultViewController new]; + _locationResultVC.delegate = self; + _locationResultVC.searchBarHeight = self.searchController.searchBar.bounds.size.height; + _searchController = [[UISearchController alloc] initWithSearchResultsController:_locationResultVC]; + _searchController.searchResultsUpdater = _locationResultVC; + + NSString *placeholder = [[FATClient sharedClient] fat_localizedStringForKey:@"Search nearby"]; + _searchController.searchBar.placeholder = placeholder; + _searchController.searchBar.backgroundColor = [UIColor colorWithRed:239 / 255.0 green:239 / 255.0 blue:244 / 255.0 alpha:1]; + UITextField *searchField; + if (@available(iOS 13.0, *)) { + searchField = _searchController.searchBar.searchTextField; + } else { + searchField = [_searchController.searchBar valueForKey:@"searchField"]; + } + searchField.layer.borderColor = [UIColor colorWithRed:218 / 255.0 green:219 / 255.0 blue:233 / 255.0 alpha:1].CGColor; + self.tableView.tableHeaderView = _searchController.searchBar; +} + +#pragma mark - click events +- (void)cancelItemClick { + [self.navigationController dismissViewControllerAnimated:YES completion:nil]; + if (self.cancelBlock) { + self.cancelBlock(); + } +} + +- (void)sureItemClick { + [self.navigationController dismissViewControllerAnimated:YES completion:nil]; + FATMapPlace *selectPlace = nil; + for (FATMapPlace *place in self.dataArray) { + if (place.selected) { + selectPlace = place; + break; + } + } + NSString *type; + CLLocationCoordinate2D coordinate = selectPlace.location.coordinate; + NSMutableDictionary *locationInfo = [[NSMutableDictionary alloc] initWithDictionary:@{@"latitude" : [NSString stringWithFormat:@"%@", @(coordinate.latitude)], + @"longitude" : [NSString stringWithFormat:@"%@", @(coordinate.longitude)]}]; + if ([selectPlace.name isEqualToString:[[FATClient sharedClient] fat_localizedStringForKey:@"Don't show location"]]) { + type = @"0"; + } else if (!selectPlace.address) { + type = @"1"; + [locationInfo setValue: selectPlace.name ?: @"" forKey:@"city"]; + } else { + type = @"2"; + [locationInfo setValue: selectPlace.address ?: @"" forKey:@"name"]; + [locationInfo setValue: selectPlace.name ?: @"" forKey:@"address"]; + } + [locationInfo setValue:type forKey:@"type"]; + if (self.sureBlock) { + self.sureBlock(locationInfo); + } +} + +- (UITableView *)tableView { + if (!_tableView) { + _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; + _tableView.dataSource = self; + _tableView.delegate = self; + _tableView.backgroundColor = [UIColor whiteColor]; + _tableView.tableFooterView = [UIView new]; + } + return _tableView; +} + +- (UIActivityIndicatorView *)indicatorView { + if (!_indicatorView) { + _indicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + _indicatorView.hidesWhenStopped = YES; + } + + return _indicatorView; +} + +#pragma mark - FATLocationResultDelegate +- (void)selectedLocationWithLocation:(FATMapPlace *)place { + [self dismissViewControllerAnimated:NO completion:nil]; + [self.navigationController dismissViewControllerAnimated:YES completion:nil]; + NSString *type; + CLLocationCoordinate2D coordinate = place.location.coordinate; + NSMutableDictionary *locationInfo = [[NSMutableDictionary alloc] initWithDictionary:@{@"latitude" : [NSString stringWithFormat:@"%@", @(coordinate.latitude)], + @"longitude" : [NSString stringWithFormat:@"%@", @(coordinate.longitude)]}]; + if ([place.name isEqualToString:[[FATClient sharedClient] fat_localizedStringForKey:@"Don't show location"]]) { + type = @"0"; + } else if (!place.address) { + type = @"1"; + [locationInfo setValue: place.name ?: @"" forKey:@"city"]; + } else { + type = @"2"; + [locationInfo setValue: place.address ?: @"" forKey:@"name"]; + [locationInfo setValue: place.name ?: @"" forKey:@"address"]; + } + [locationInfo setValue:type forKey:@"type"]; + if (self.sureBlock) { + self.sureBlock(locationInfo); + } +} + +#pragma mark - MKMapViewDelegate +- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation { + if (!self.userLocation) { + _userLocation = userLocation; + CLLocationCoordinate2D center = userLocation.location.coordinate; + if (center.latitude > 0 && center.longitude > 0) { + MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(center, 1000, 1000); + mapView.centerCoordinate = center; + mapView.region = region; + + self.locationResultVC.region = region; + } + } +} +// 单独加上所在的城市和不显示位置两条数据 +- (void)addSpecialCity { + // 避免重复添加。 + NSMutableArray *deleteArray = [[NSMutableArray alloc] init]; + for (FATMapPlace *place in self.dataArray) { + if ([place.name isEqualToString:[[FATClient sharedClient] fat_localizedStringForKey:@"Don't show location"]]) { + [deleteArray insertObject:place atIndex:0]; + } + if (place.name && !place.address) { + [deleteArray insertObject:place atIndex:0]; + } + } + if (deleteArray.count != 0) { + [self.dataArray removeObjectsInArray:deleteArray]; + } + + FATMapPlace *place = [[FATMapPlace alloc] init]; + place.name = self.cityName; + place.location = place.location; + place.selected = NO; + [self.dataArray insertObject:place atIndex:0]; + + FATMapPlace *NoPlace = [[FATMapPlace alloc] init]; + NoPlace.name = [[FATClient sharedClient] fat_localizedStringForKey:@"Don't show location"]; + NoPlace.selected = NO; + [self.dataArray insertObject:NoPlace atIndex:0]; +} + +#pragma mark - UITableViewDataSource +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.dataArray.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + static NSString *identifer = @"identifer"; + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer]; + if (cell == nil) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifer]; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + cell.detailTextLabel.textColor = [UIColor grayColor]; + } + + FATMapPlace *place = self.dataArray[indexPath.row]; + if ([place.name isEqualToString:[[FATClient sharedClient] fat_localizedStringForKey:@"Don't show location"]]) { + cell.textLabel.textColor = [UIColor fat_colorWithRGBHexString:@"0066ff" alpha:1.0]; + } else { + if (@available(iOS 13.0, *)) { + cell.textLabel.textColor = UIColor.labelColor; + } else { + cell.textLabel.textColor = UIColor.blackColor; + } + } + cell.textLabel.text = place.name; + cell.detailTextLabel.text = place.address; + if (place.selected) { + cell.accessoryType = UITableViewCellAccessoryCheckmark; + } else { + cell.accessoryType = UITableViewCellAccessoryNone; + } + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + for (FATMapPlace *place in self.dataArray) { + place.selected = NO; + } + FATMapPlace *place = self.dataArray[indexPath.row]; + place.selected = YES; + [self.tableView reloadData]; + + MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(place.location.coordinate, 1000, 1000); + self.locationResultVC.region = region; +} + +@end diff --git a/ios/Classes/FinAppletExt/Common/Util/Map/FATExtSliderView.h b/ios/Classes/FinAppletExt/Common/Util/Map/FATExtSliderView.h new file mode 100644 index 0000000..ca4ff9b --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/Map/FATExtSliderView.h @@ -0,0 +1,32 @@ +// +// FATExtSliderView.h +// FinAppletGDMap +// +// Created by 王兆耀 on 2021/12/13. +// + +#import +#import +#import "FATMapPlace.h" + +#define fatKScreenWidth ([UIScreen mainScreen].bounds.size.width) +#define fatKScreenHeight ([UIScreen mainScreen].bounds.size.height) + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^SelectItemBlock)(FATMapPlace *locationInfo); +typedef void (^TopDistance)(float height, BOOL isTopOrBottom); + +@interface FATExtSliderView : UIView + +@property (nonatomic, copy) SelectItemBlock selectItemBlock; +@property (nonatomic, assign) float topH; //上滑后距离顶部的距离 +@property (nonatomic, copy) TopDistance topDistance; +@property (nonatomic, strong) UITableView *tableView; +@property (nonatomic, strong) NSMutableArray *poiInfoListArray; + +- (void)updateSearchFrameWithColcationCoordinate:(CLLocationCoordinate2D)coord; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/Common/Util/Map/FATExtSliderView.m b/ios/Classes/FinAppletExt/Common/Util/Map/FATExtSliderView.m new file mode 100644 index 0000000..d7e5bec --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/Map/FATExtSliderView.m @@ -0,0 +1,365 @@ +// +// FATExtSliderView.m +// FinAppletGDMap +// +// Created by 王兆耀 on 2021/12/13. +// + +#import "FATExtSliderView.h" +#import "UIView+FATExtFrame.h" +#import +#import "UIView+FATExtSafaFrame.h" +#import "FATExtUtil.h" +#import "FATExtMapManager.h" + +@interface FATExtSliderView () + +@property (nonatomic, assign) float bottomH; //下滑后距离顶部的距离 +@property (nonatomic, assign) float stop_y; //tableView滑动停止的位置 +@property (nonatomic, strong) UISearchController *searchController; +@property (nonatomic, strong) NSMutableArray *tempPoiInfoListArray; +@property (nonatomic, assign) NSInteger selectNumber; +@property (nonatomic, strong) UISearchController *search; +@property (nonatomic, assign) CLLocationCoordinate2D locationCoordinate; +@property (nonatomic, assign) NSInteger pageIndex; +@property (nonatomic, strong) NSString *cityString; +@property (nonatomic, assign) MKCoordinateRegion region; +@end + +@implementation FATExtSliderView + +- (NSMutableArray *)poiInfoListArray { + if (!_poiInfoListArray) { + _poiInfoListArray = [[NSMutableArray alloc] init]; + } + return _poiInfoListArray; +} + +- (NSMutableArray *)tempPoiInfoListArray { + if (!_tempPoiInfoListArray) { + _tempPoiInfoListArray = [[NSMutableArray alloc] init]; + } + return _tempPoiInfoListArray; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self setUI]; + } + return self; +} + +- (void)setUI { + self.backgroundColor = [UIColor systemGrayColor]; + self.bottomH = self.top; + self.pageIndex = 0; + self.selectNumber = 0; + UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panAction:)]; + panGestureRecognizer.delegate = self; + [self addGestureRecognizer:panGestureRecognizer]; + [self creatUI]; +} + +- (void)creatUI { + self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, fatKScreenWidth, self.frame.size.height) style:UITableViewStylePlain]; + self.tableView.backgroundColor = [UIColor whiteColor]; + self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; + self.tableView.showsVerticalScrollIndicator = NO; + self.tableView.delegate = self; + self.tableView.dataSource = self; + self.tableView.bounces = NO; + if (@available(iOS 11.0, *)) { + self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } else { + // Fallback on earlier versions + } + [self addSubview:self.tableView]; + [self.tableView registerNib:[UINib nibWithNibName:@"SlideTableViewCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"SlideTableViewCell"]; + self.tableView.tableHeaderView = self.searchController.searchBar; + self.search.delegate = self; +} + +- (void)getData { + CLLocationCoordinate2D centerCoordinate = self.locationCoordinate; + CLGeocoder *geocoder = [[CLGeocoder alloc] init]; + CLLocation *location = [[CLLocation alloc] initWithLatitude:centerCoordinate.latitude longitude:centerCoordinate.longitude]; + + [geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *array, NSError *error) { + CLPlacemark *placemark = nil; + if (!error) { + placemark = [array firstObject]; + } + + MKCoordinateSpan span = MKCoordinateSpanMake(centerCoordinate.latitude, centerCoordinate.longitude); + MKCoordinateRegion newRegion = MKCoordinateRegionMake(centerCoordinate, span); + + MKLocalSearchRequest *request = [[MKLocalSearchRequest alloc] init]; + request.region = self.region; + request.naturalLanguageQuery = @"Place"; + CLLocationCoordinate2D destCenter = self.region.center; + if(destCenter.latitude == 0){ + destCenter = centerCoordinate; + } + + if([FATExtMapManager shareInstance].googleMapApiKey.length > 1){ + [FATExtUtil getNearbyPlacesByCategory:@"All" coordinates:destCenter radius:1000 token:@"" + completion:^(NSDictionary * _Nonnull dict) { + NSMutableArray *placeArrayM = [NSMutableArray array]; + if (placemark) { + FATMapPlace *place = [[FATMapPlace alloc] init]; + place.name = placemark.name; + place.address = placemark.thoroughfare; + place.location = placemark.location; + place.selected = YES; + [placeArrayM addObject:place]; + } + [placeArrayM addObjectsFromArray:[FATExtUtil convertPlaceDictToArray:dict]]; + self.poiInfoListArray = [[NSMutableArray alloc] initWithArray: placeArrayM]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [self.tableView reloadData]; + }); + }]; + }else{ + MKLocalSearch *localSearch = [[MKLocalSearch alloc] initWithRequest:request]; + [localSearch startWithCompletionHandler:^(MKLocalSearchResponse *_Nullable response, NSError *_Nullable error) { + NSMutableArray *placeArrayM = [NSMutableArray array]; + for (MKMapItem *item in response.mapItems) { + if (!item.isCurrentLocation) { + FATMapPlace *place = [[FATMapPlace alloc] init]; + place.name = item.placemark.name; + place.address = item.placemark.thoroughfare; + place.location = item.placemark.location; + [placeArrayM addObject:place]; + } + } + self.poiInfoListArray = [[NSMutableArray alloc] initWithArray:placeArrayM]; + [self.tableView reloadData]; + }]; + } + }]; +} + +- (void)updateSearchFrameWithColcationCoordinate:(CLLocationCoordinate2D)coord { +// struct CLLocationCoordinate2D my2D = {40.0, 115.0};//self.locationCoordinate + MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(self.locationCoordinate, 1000, 1000); + self.region = region; + self.locationCoordinate = coord; + [self.poiInfoListArray removeAllObjects]; + [self.tableView reloadData]; + [self getData]; +} + +- (void)p_searchLocationsWithSearchText:(NSString *)searchText { + MKLocalSearchRequest *request = [[MKLocalSearchRequest alloc] init]; + request.region = self.region; + request.naturalLanguageQuery = searchText; + CLLocationCoordinate2D destCenter = self.region.center; + if(destCenter.latitude == 0){ + destCenter = self.locationCoordinate; + } + + if([FATExtMapManager shareInstance].googleMapApiKey.length > 1){ + [FATExtUtil getNearbyPlacesByCategory:searchText coordinates:destCenter radius:1000 token:@"" + completion:^(NSDictionary * _Nonnull dict) { + + self.poiInfoListArray = [[NSMutableArray alloc] initWithArray: [FATExtUtil convertPlaceDictToArray:dict]]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [self.tableView reloadData]; + }); + }]; + }else { MKLocalSearch *localSearch = [[MKLocalSearch alloc] initWithRequest:request]; + [localSearch startWithCompletionHandler:^(MKLocalSearchResponse *_Nullable response, NSError *_Nullable error) { + NSMutableArray *placeArrayM = [NSMutableArray array]; + for (MKMapItem *item in response.mapItems) { + if (!item.isCurrentLocation) { + FATMapPlace *place = [[FATMapPlace alloc] init]; + place.name = item.placemark.name; + place.address = item.placemark.thoroughfare; + place.location = item.placemark.location; + [placeArrayM addObject:place]; + } + } + self.poiInfoListArray = [[NSMutableArray alloc] initWithArray:placeArrayM]; + [self.tableView reloadData]; + }]; + } +} + +#pragma mark - 滑动 +- (void)scrollViewDidScroll:(UIScrollView *)scrollView { + + CGFloat currentPostion = scrollView.contentOffset.y; + self.stop_y = currentPostion; + + if (self.top > self.topH) { + [scrollView setContentOffset:CGPointMake(0, 0)]; + } +} + +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { + return YES; +} + +- (void)panAction:(UIPanGestureRecognizer *)pan { + // 获取视图偏移量 + CGPoint point = [pan translationInView:self]; + // stop_y是tableview的偏移量,当tableview的偏移量大于0时则不去处理视图滑动的事件 + if (self.stop_y > 0) { + // 将视频偏移量重置为0 + [pan setTranslation:CGPointMake(0, 0) inView:self]; + return; + } + + // self.top是视图距离顶部的距离 + self.top += point.y; + if (self.top < self.topH) { + self.top = self.topH; + } + + // self.bottomH是视图在底部时距离顶部的距离 + if (self.top > self.bottomH) { + self.top = self.bottomH; + } + + // 在滑动手势结束时判断滑动视图距离顶部的距离是否超过了屏幕的一半,如果超过了一半就往下滑到底部 + // 如果小于一半就往上滑到顶部 + if (pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateCancelled) { + // 滑动速度 + CGPoint velocity = [pan velocityInView:self]; + CGFloat speed = 350; + if (velocity.y < -speed) { + [self goTop]; + [pan setTranslation:CGPointMake(0, 0) inView:self]; + return; + } else if (velocity.y > speed) { + [self goBack]; + [pan setTranslation:CGPointMake(0, 0) inView:self]; + return; + } + + if (self.top > fatKScreenHeight / 2) { + [self goBack]; + } else { + [self goTop]; + } + } + + [pan setTranslation:CGPointMake(0, 0) inView:self]; + if (self.top != self.bottomH) { + if (!isnan(point.y)) { + if (self.topDistance) { + self.topDistance(point.y, false); + } + } + } +} + +- (void)goTop { + if (self.top != self.bottomH) { + if (self.topDistance) { + if (!isnan(self.topH)) { + self.topDistance(self.topH, true); + } + } + } + [UIView animateWithDuration:0.5 animations:^{ + self.top = self.topH; + } completion:^(BOOL finished){ + + }]; +} + +- (void)goBack { + if (self.topDistance) { + if (!isnan(self.bottomH)) { + self.topDistance(self.bottomH, true); + } + } + [UIView animateWithDuration:0.5 animations:^{ + self.top = self.bottomH; + } completion:^(BOOL finished){ + // self.tableView.userInteractionEnabled = NO; + }]; +} + +#pragma mark - UITableViewDelegate +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.poiInfoListArray.count; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + return 50; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + static NSString *identifer = @"identifer"; + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer]; + if (cell == nil) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifer]; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + cell.detailTextLabel.textColor = [UIColor grayColor]; + } + + cell.textLabel.text = self.poiInfoListArray[indexPath.row].name; + cell.detailTextLabel.text = self.poiInfoListArray[indexPath.row].address; + if (indexPath.row == self.selectNumber) { + cell.accessoryType = UITableViewCellAccessoryCheckmark; + } else { + cell.accessoryType = UITableViewCellAccessoryNone; + } + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:NO]; + self.selectNumber = indexPath.row; + [tableView reloadData]; + FATMapPlace *poiInfo = self.poiInfoListArray[indexPath.row]; + if (self.selectItemBlock) { + self.selectItemBlock(poiInfo); + } +} + +#pragma mark - UISearchResultsUpdating + +- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar { + [self goTop]; + self.tempPoiInfoListArray = [[NSMutableArray alloc] initWithArray:self.poiInfoListArray]; + [self.poiInfoListArray removeAllObjects]; + [self.tableView reloadData]; +} + +- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar { + self.selectNumber = -1; + NSString *searchString = self.searchController.searchBar.text; + [self p_searchLocationsWithSearchText:searchString]; + self.searchController.active = NO; + [self goBack]; + self.searchController.searchBar.text = searchString; +} + +- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar { + [self goBack]; + self.poiInfoListArray = [[NSMutableArray alloc] initWithArray:self.tempPoiInfoListArray]; + [self.tableView reloadData]; +} + +- (UISearchController *)searchController { + if (!_searchController) { + _searchController = [[UISearchController alloc] initWithSearchResultsController:nil]; + _searchController.searchBar.delegate = self; + _searchController.dimsBackgroundDuringPresentation = NO; + _searchController.hidesNavigationBarDuringPresentation = NO; + _searchController.searchBar.autocapitalizationType = UITextAutocapitalizationTypeNone; + _searchController.searchBar.frame = CGRectMake(0, 0, fatKScreenWidth, 44); + NSString *placeholder = [[FATClient sharedClient] fat_localizedStringForKey:@"Search for location"]; + _searchController.searchBar.placeholder = placeholder; + } + return _searchController; +} + +@end diff --git a/ios/Classes/FinAppletExt/Common/Util/Map/FATLocationResultViewController.h b/ios/Classes/FinAppletExt/Common/Util/Map/FATLocationResultViewController.h new file mode 100644 index 0000000..7a6e414 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/Map/FATLocationResultViewController.h @@ -0,0 +1,27 @@ +// +// FATLocationResultViewController.h +// AppletDemo +// +// Created by Haley on 2020/4/17. +// Copyright © 2020 weidian. All rights reserved. +// + +#import +#import +#import + +#import "FATMapPlace.h" + +@protocol FATLocationResultDelegate + +- (void)selectedLocationWithLocation:(FATMapPlace *)place; + +@end + +@interface FATLocationResultViewController : FATUIViewController + +@property (nonatomic, assign) MKCoordinateRegion region; +@property (nonatomic, weak) id delegate; +@property (nonatomic, assign) NSInteger searchBarHeight; + +@end diff --git a/ios/Classes/FinAppletExt/Common/Util/Map/FATLocationResultViewController.m b/ios/Classes/FinAppletExt/Common/Util/Map/FATLocationResultViewController.m new file mode 100644 index 0000000..5c3cdc5 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/Map/FATLocationResultViewController.m @@ -0,0 +1,124 @@ +// +// FATLocationResultViewController.m +// AppletDemo +// +// Created by Haley on 2020/4/17. +// Copyright © 2020 weidian. All rights reserved. +// + +#import "FATLocationResultViewController.h" +#import "FATExtMapManager.h" +#import "FATExtUtil.h" + + +@interface FATLocationResultViewController () + +@property (nonatomic, strong) UITableView *tableView; + +@property (nonatomic, copy) NSArray *places; + +@property (nonatomic, strong) FATMapPlace *selectedPlace; + +@end + +@implementation FATLocationResultViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view. + + [self p_initSubViews]; +} + +#pragma mark - private method +- (void)p_initSubViews { + self.edgesForExtendedLayout = UIRectEdgeNone; + CGFloat width = self.view.bounds.size.width; + CGFloat height = self.view.bounds.size.height; + self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, self.searchBarHeight, width, height - self.searchBarHeight - 60) style:UITableViewStylePlain]; + self.tableView.dataSource = self; + self.tableView.delegate = self; + self.tableView.rowHeight = 60; + [self.view addSubview:self.tableView]; +} + +- (void)p_searchLocationsWithSearchText:(NSString *)searchText { + MKLocalSearchRequest *request = [[MKLocalSearchRequest alloc] init]; + request.region = self.region; + request.naturalLanguageQuery = searchText; + + MKLocalSearch *localSearch = [[MKLocalSearch alloc] initWithRequest:request]; + if([FATExtMapManager shareInstance].googleMapApiKey.length > 1){ + [FATExtUtil getNearbyPlacesByCategory:searchText coordinates:self.region.center radius:1000 token:@"" + completion:^(NSDictionary * _Nonnull dict) { + self.places = [[FATExtUtil convertPlaceDictToArray:dict] copy]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self.tableView reloadData]; + }); + }]; + }else{[localSearch startWithCompletionHandler:^(MKLocalSearchResponse *_Nullable response, NSError *_Nullable error) { + NSMutableArray *placeArrayM = [NSMutableArray array]; + for (MKMapItem *item in response.mapItems) { + if (!item.isCurrentLocation) { + FATMapPlace *place = [[FATMapPlace alloc] init]; + place.name = item.placemark.name; + place.address = item.placemark.thoroughfare; + place.location = item.placemark.location; + [placeArrayM addObject:place]; + } + } + self.places = [placeArrayM copy]; + [self.tableView reloadData]; + }]; + } +} + +#pragma mark - UISearchResultsUpdating +- (void)updateSearchResultsForSearchController:(UISearchController *)searchController { + NSString *searchString = searchController.searchBar.text; + [self p_searchLocationsWithSearchText:searchString]; +} + + +#pragma mark - UITableViewDataSource +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.places.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + static NSString *identifer = @"placeCell"; + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifer]; + if (cell == nil) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifer]; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + cell.detailTextLabel.textColor = [UIColor grayColor]; + } + + FATMapPlace *place = self.places[indexPath.row]; + cell.textLabel.text = place.name; + cell.detailTextLabel.text = place.address; + if (place.selected) { + cell.accessoryType = UITableViewCellAccessoryCheckmark; + } else { + cell.accessoryType = UITableViewCellAccessoryNone; + } + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + for (FATMapPlace *place in self.places) { + place.selected = NO; + } + FATMapPlace *place = self.places[indexPath.row]; + place.selected = YES; + [self.tableView reloadData]; + + if (self.delegate && [self.delegate respondsToSelector:@selector(selectedLocationWithLocation:)]) { + [self.delegate selectedLocationWithLocation:place]; + } + + [self dismissViewControllerAnimated:YES completion:nil]; +} + +@end diff --git a/ios/Classes/FinAppletExt/Common/Util/Map/FATMapPlace.h b/ios/Classes/FinAppletExt/Common/Util/Map/FATMapPlace.h new file mode 100644 index 0000000..a0c7e67 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/Map/FATMapPlace.h @@ -0,0 +1,23 @@ +// +// FATMapPlace.h +// AppletDemo +// +// Created by Haley on 2020/4/17. +// Copyright © 2020 weidian. All rights reserved. +// + +#import +#import + +@interface FATMapPlace : NSObject + +@property (nonatomic, copy) NSString *name; + +@property (nonatomic, copy) NSString *address; + +//@property (nonatomic, strong) CLPlacemark *placemark; +@property (nonatomic, strong) CLLocation *location; + +@property (nonatomic, assign) BOOL selected; + +@end diff --git a/ios/Classes/FinAppletExt/Common/Util/Map/FATMapPlace.m b/ios/Classes/FinAppletExt/Common/Util/Map/FATMapPlace.m new file mode 100644 index 0000000..9d6a5e5 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/Map/FATMapPlace.m @@ -0,0 +1,13 @@ +// +// FATMapPlace.m +// AppletDemo +// +// Created by Haley on 2020/4/17. +// Copyright © 2020 weidian. All rights reserved. +// + +#import "FATMapPlace.h" + +@implementation FATMapPlace + +@end diff --git a/ios/Classes/FinAppletExt/Common/Util/Map/FATMapViewController.h b/ios/Classes/FinAppletExt/Common/Util/Map/FATMapViewController.h new file mode 100644 index 0000000..6d183ea --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/Map/FATMapViewController.h @@ -0,0 +1,24 @@ +// +// FATMapViewController.h +// AppletDemo +// +// Created by Haley on 2020/4/16. +// Copyright © 2020 weidian. All rights reserved. +// + +#import +#import + +typedef void (^SureBlock)(NSDictionary *locationInfo); + +@interface FATMapViewController : FATUIViewController + +@property (nonatomic, copy) dispatch_block_t cancelBlock; +@property (nonatomic, copy) SureBlock sureBlock; + +/// 目标地纬度 +@property (nonatomic, strong) NSString *latitude; +/// 目标地经度 +@property (nonatomic, strong) NSString *longitude; + +@end diff --git a/ios/Classes/FinAppletExt/Common/Util/Map/FATMapViewController.m b/ios/Classes/FinAppletExt/Common/Util/Map/FATMapViewController.m new file mode 100644 index 0000000..f36d987 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/Map/FATMapViewController.m @@ -0,0 +1,350 @@ +// +// FATMapViewController.m +// AppletDemo +// +// Created by Haley on 2020/4/16. +// Copyright © 2020 weidian. All rights reserved. +// + +#import "FATMapViewController.h" +#import "FATLocationResultViewController.h" +#import "FATAnnotation.h" +#import "FATExtHelper.h" +#import "FATExtSliderView.h" +#import "FATWGS84ConvertToGCJ02.h" +#import "FATExtLocationManager.h" +#import +#import "UIView+FATExtSafaFrame.h" + +#define fatKScreenWidth ([UIScreen mainScreen].bounds.size.width) +#define fatKScreenHeight ([UIScreen mainScreen].bounds.size.height) + +static NSString *kAnnotationId = @"FATAnnotationViewId"; +static NSString *kUserAnnotationId = @"FATUserAnnotationViewId"; + + +@interface FATMapViewController () + +@property (nonatomic, strong) MKMapView *mapView; +@property (nonatomic, strong) MKUserLocation *userLocation; +@property (nonatomic, strong) UIButton *returnButton; +@property (nonatomic, strong) UIButton *determineButton; +@property (nonatomic, strong) UIButton *positionButton; +@property (nonatomic, strong) FATExtSliderView *slideView; +@property (nonatomic, strong) FATMapPlace *poiInfo; +@property (nonatomic, strong) MKPointAnnotation *centerAnnotation; +@property (nonatomic, strong) FATExtLocationManager *locationManager; + +@end + +@implementation FATMapViewController + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + [self.navigationController setNavigationBarHidden:YES animated:animated]; +} +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; +// [self.navigationController setNavigationBarHidden:NO animated:animated]; +} + +- (void)viewDidDisappear:(BOOL)animated { + [super viewDidDisappear: animated]; + _mapView.showsUserLocation = NO; + _mapView.userTrackingMode = MKUserTrackingModeNone; + [_mapView.layer removeAllAnimations]; + [_mapView removeAnnotations:self.mapView.annotations]; + [_mapView removeOverlays:self.mapView.overlays]; + [_mapView removeFromSuperview]; + _mapView.delegate = nil; + _mapView = nil; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view. + + self.edgesForExtendedLayout = UIRectEdgeNone; + + [self p_initSubViews]; + + [self settingMapCenter]; +} + +- (void)dealloc { + _mapView.showsUserLocation = NO; + _mapView.userTrackingMode = MKUserTrackingModeNone; + [_mapView.layer removeAllAnimations]; + [_mapView removeAnnotations:self.mapView.annotations]; + [_mapView removeOverlays:self.mapView.overlays]; + [_mapView removeFromSuperview]; + _mapView.delegate = nil; + _mapView = nil; +} + +- (void)p_initSubViews { + // 判断是否是暗黑模式 + BOOL isDark = false; + if (@available(iOS 12.0, *)) { + isDark = (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark); + } + self.view.backgroundColor = isDark ? UIColor.blackColor : UIColor.whiteColor; + [self.view addSubview:self.mapView]; + self.mapView.frame = CGRectMake(0, 0, fatKScreenWidth, fatKScreenHeight - 200); + + self.centerAnnotation = [[MKPointAnnotation alloc] init]; + self.centerAnnotation.coordinate = self.mapView.centerCoordinate; + [self.mapView addAnnotation:self.centerAnnotation]; + + CGFloat top = [UIView fat_statusHeight]; + self.returnButton = [[UIButton alloc] initWithFrame:CGRectMake(16, top, 57, 32)]; + [self.returnButton setImage:[FATExtHelper fat_ext_imageFromBundleWithName:@"map_back_n"] forState:UIControlStateNormal]; + [self.returnButton addTarget:self action:@selector(cancelItemClick) forControlEvents:UIControlEventTouchUpInside]; + [self.mapView addSubview:self.returnButton]; + + self.determineButton = [[UIButton alloc] initWithFrame:CGRectMake(self.view.frame.size.width - 16 - 57, top, 57, 32)]; + NSString *ok = [[FATClient sharedClient] fat_localizedStringForKey:@"OK"]; + [self.determineButton setTitle:ok forState:UIControlStateNormal]; + [self.determineButton setTitleColor:[UIColor colorWithRed:255 / 255.0 green:255 / 255.0 blue:255 / 255.0 alpha:1 / 1.0] forState:UIControlStateNormal]; + [self.determineButton setBackgroundColor:[UIColor colorWithRed:64 / 255.0 green:158 / 255.0 blue:255 / 255.0 alpha:1 / 1.0]]; + [self.determineButton addTarget:self action:@selector(sureItemClick) forControlEvents:UIControlEventTouchUpInside]; + self.determineButton.titleLabel.font = [UIFont systemFontOfSize:17]; + [self.mapView addSubview:self.determineButton]; + + self.positionButton = [[UIButton alloc] initWithFrame:CGRectMake(self.view.frame.size.width - 73, fatKScreenHeight - 260, 45, 45)]; + + if (isDark) { + [self.positionButton setImage:[FATExtHelper fat_ext_imageFromBundleWithName:@"map_location_dn"] forState:UIControlStateNormal]; + [self.positionButton setImage:[FATExtHelper fat_ext_imageFromBundleWithName:@"map_location_dp"] forState:UIControlStateHighlighted]; + self.mapView.mapType = MKMapTypeStandard; + } else { + [self.positionButton setImage:[FATExtHelper fat_ext_imageFromBundleWithName:@"map_location_ln"] forState:UIControlStateNormal]; + [self.positionButton setImage:[FATExtHelper fat_ext_imageFromBundleWithName:@"map_location_lp"] forState:UIControlStateHighlighted]; + self.mapView.mapType = MKMapTypeStandard; + } + [self.positionButton addTarget:self action:@selector(locationOnClick) forControlEvents:UIControlEventTouchUpInside]; + [self.mapView addSubview:self.positionButton]; + __weak typeof(self) weakSelf = self; + self.slideView = [[FATExtSliderView alloc] initWithFrame:CGRectMake(0, fatKScreenHeight - 200, fatKScreenWidth, fatKScreenHeight - 250)]; + self.slideView.backgroundColor = isDark ? UIColor.blackColor : UIColor.whiteColor; + self.slideView.tableView.backgroundColor = isDark ? UIColor.blackColor : UIColor.whiteColor; + self.slideView.topH = 300; + self.slideView.selectItemBlock = ^(FATMapPlace *locationInfo) { + CLLocationCoordinate2D centerCoord = {locationInfo.location.coordinate.latitude, locationInfo.location.coordinate.longitude}; + weakSelf.poiInfo = locationInfo; + [UIView animateWithDuration:1 animations:^{ + weakSelf.centerAnnotation.coordinate = centerCoord; + MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(centerCoord, 1000, 1000); + weakSelf.mapView.centerCoordinate = centerCoord; + weakSelf.mapView.region = region; + }]; + }; + + if ([self.latitude isEqualToString:@"nil"] || [self.longitude isEqualToString:@"nil"] || (!self.latitude && !self.longitude)) { + [self startStandardUpdates]; + } else { + CLLocationCoordinate2D centerCoord = {[self.latitude doubleValue], [self.longitude doubleValue]}; + self.mapView.centerCoordinate = centerCoord; + [self.slideView updateSearchFrameWithColcationCoordinate:self.mapView.centerCoordinate]; + } + __block float heights = fatKScreenHeight - 200; + __block float positionButtonY = fatKScreenHeight - 260; + self.slideView.topDistance = ^(float height, BOOL isTopOrBottom) { + if (!isTopOrBottom) { + heights += height; + positionButtonY += height; + } else { + heights = height; + positionButtonY = height; + } + dispatch_async(dispatch_get_main_queue(), ^{ + weakSelf.mapView.frame = CGRectMake(0, 0, fatKScreenWidth, heights); + weakSelf.positionButton.frame = CGRectMake(self.view.frame.size.width - 73, positionButtonY > fatKScreenHeight - 260 ? fatKScreenHeight - 260 : positionButtonY, 45, 45); + if (heights + weakSelf.slideView.frame.size.height < fatKScreenHeight) { + weakSelf.mapView.frame = CGRectMake(0, 0, fatKScreenWidth, fatKScreenHeight - weakSelf.slideView.frame.size.height); + weakSelf.positionButton.frame = CGRectMake(self.view.frame.size.width - 73, fatKScreenHeight - 260, 45, 45); + } + }); + }; + [self.view addSubview:self.slideView]; +} + +- (void)locationOnClick { + [self startStandardUpdates]; +} + +- (void)startStandardUpdates { +// if (![FATExtLocationManager locationServicesEnabled]) { +// return; +// } + + CLAuthorizationStatus status = [FATExtLocationManager authorizationStatus]; + if (status == kCLAuthorizationStatusAuthorizedWhenInUse || + status == kCLAuthorizationStatusAuthorizedAlways || + status == kCLAuthorizationStatusNotDetermined) { + self.locationManager.delegate = self; + self.locationManager.desiredAccuracy = kCLLocationAccuracyBest; + [self.locationManager requestWhenInUseAuthorization]; + [self.locationManager startUpdatingLocation]; + } +} + +- (void)settingMapCenter { + if ([NSString fat_isEmptyWithString:self.latitude] || [NSString fat_isEmptyWithString:self.longitude]) { + return; + } + + double LongitudeDelta = [self fat_getLongitudeDelta:14.00]; +// double LatitudeDelta = LongitudeDelta * 2; + CLLocationCoordinate2D centerCoord = {[self judgeLatition:[self.latitude doubleValue]], [self judgeLongitude:[self.longitude doubleValue]]}; + MKCoordinateRegion region = MKCoordinateRegionMake(centerCoord, MKCoordinateSpanMake(LongitudeDelta, LongitudeDelta)); + + [self.mapView setRegion:region animated:YES]; +} + +#pragma mark - click events +- (void)cancelItemClick { + [self.navigationController dismissViewControllerAnimated:YES completion:nil]; + if (self.cancelBlock) { + self.cancelBlock(); + } +} + +- (void)sureItemClick { + [self.navigationController dismissViewControllerAnimated:YES completion:nil]; + + if (self.poiInfo == nil && self.slideView.poiInfoListArray.count > 0) { + self.poiInfo = self.slideView.poiInfoListArray.firstObject; + } + + CLLocationCoordinate2D coordinate = self.poiInfo.location.coordinate; + NSDictionary *locationInfo = @{@"name" : self.poiInfo.name ?: @"", + @"address" : self.poiInfo.address ?: @"", + @"latitude" : @(coordinate.latitude), + @"longitude" : @(coordinate.longitude)}; + if (self.sureBlock) { + self.sureBlock(locationInfo); + } +} + +#pragma mark - FATLocationResultDelegate +- (void)selectedLocationWithLocation:(FATMapPlace *)place { + MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(place.location.coordinate, 1000, 1000); + self.mapView.centerCoordinate = place.location.coordinate; + self.mapView.region = region; +} + +#pragma mark - MKMapViewDelegate +- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation { + if (!self.userLocation) { + _userLocation = userLocation; + CLLocationCoordinate2D center = [FATWGS84ConvertToGCJ02ForAMapView transformFromWGSToGCJ:userLocation.location.coordinate]; + if (center.latitude > 0 && center.longitude > 0) { + MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(center, 1000, 1000); + if ([NSString fat_isEmptyWithString:self.latitude] || [NSString fat_isEmptyWithString:self.longitude]) { + mapView.centerCoordinate = center; + mapView.region = region; + [self.mapView setRegion:region animated:YES]; + self.centerAnnotation.coordinate = center; + } + } + [self.slideView updateSearchFrameWithColcationCoordinate:center]; + } +} + +/** + * 更新到位置之后调用 + * + * @param manager 位置管理者 + * @param locations 位置数组 + */ +- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { + CLLocation *location = [locations firstObject]; + //位置更新后的经纬度 + CLLocationCoordinate2D theCoordinate = location.coordinate; + //设置地图显示的中心及范围 + MKCoordinateRegion theRegion; + theRegion.center = theCoordinate; + CLLocationCoordinate2D coord = [FATWGS84ConvertToGCJ02ForAMapView transformFromWGSToGCJ:theCoordinate]; + MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(coord, 1000, 1000); + [self.mapView setRegion:region animated:YES]; + self.centerAnnotation.coordinate = coord; + [self.locationManager stopUpdatingLocation]; +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + CLLocationCoordinate2D centerCoordinate = self.mapView.centerCoordinate; +// CLLocationCoordinate2D centerCoordinate = self.centerAnnotation.coordinate; + [self.slideView updateSearchFrameWithColcationCoordinate:centerCoordinate]; +} + +- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated { + CLLocationCoordinate2D centerCoordinate = mapView.centerCoordinate; + [UIView animateWithDuration:1 animations:^{ + self.centerAnnotation.coordinate = centerCoordinate; + }]; +} + +- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id)annotation { + if ([annotation isKindOfClass:[MKUserLocation class]]) { + return nil; + } + + static NSString *ID = @"anno"; + MKPinAnnotationView *annoView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:ID]; + if (annoView == nil) { + annoView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:ID]; + // 显示气泡 + annoView.canShowCallout = YES; + // 设置绿色 + } + + return annoView; +} + +- (double)fat_getLongitudeDelta:(double)scale { + double longitudeDelta = (360 * self.mapView.frame.size.width / 256.0 / pow(2, scale)); + return longitudeDelta; +} + +// 校验经度是否合规 +- (double)judgeLatition:(double)latitude { + if (latitude >= 90) { + latitude = 85.00; + } + if (latitude <= -90) { + latitude = -85.00; + } + return latitude; +} +// 校验纬度是否合规 +- (double)judgeLongitude:(double)longitude { + if (longitude >= 180) { + longitude = 180.00; + } + if (longitude <= -180) { + longitude = -180.00; + } + return longitude; +} + +- (FATExtLocationManager *)locationManager { + if (_locationManager == nil) { + _locationManager = [[FATExtLocationManager alloc] init]; + _locationManager.delegate = self; + } + return _locationManager; +} + +- (MKMapView *)mapView { + if (!_mapView) { + _mapView = [[MKMapView alloc] init]; + _mapView.delegate = self; + _mapView.mapType = MKMapTypeStandard; + _mapView.showsUserLocation = YES; + _mapView.userTrackingMode = MKUserTrackingModeNone; + } + return _mapView; +} + +@end diff --git a/ios/Classes/FinAppletExt/Common/Util/Map/FATOpenLocationViewController.h b/ios/Classes/FinAppletExt/Common/Util/Map/FATOpenLocationViewController.h new file mode 100644 index 0000000..327e3a4 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/Map/FATOpenLocationViewController.h @@ -0,0 +1,33 @@ +// +// FATOpenLocationViewController.h +// FinAppletExt +// +// Created by 王兆耀 on 2021/12/9. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^SureBlock)(NSDictionary *locationInfo); + +@interface FATOpenLocationViewController : FATUIViewController + +@property (nonatomic, copy) dispatch_block_t cancelBlock; +@property (nonatomic, copy) SureBlock sureBlock; + +/// 目标地纬度 +@property (nonatomic, strong) NSString *latitude; +/// 目标地经度 +@property (nonatomic, strong) NSString *longitude; + +@property (nonatomic, strong) NSString *scale; + +@property (nonatomic, strong) NSString *name; + +@property (nonatomic, strong) NSString *address; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/Common/Util/Map/FATOpenLocationViewController.m b/ios/Classes/FinAppletExt/Common/Util/Map/FATOpenLocationViewController.m new file mode 100644 index 0000000..aa7c101 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/Map/FATOpenLocationViewController.m @@ -0,0 +1,339 @@ +// +// FATOpenLocationViewController.m +// FinAppletExt +// +// Created by 王兆耀 on 2021/12/9. +// + +#import "FATOpenLocationViewController.h" +#import "FATExtHelper.h" +#import "MKMarkerView.h" +#import "FATExtLocationManager.h" +#import "FATWGS84ConvertToGCJ02.h" +#import "FATExtUtil.h" + +#import + +@interface FATOpenLocationViewController () + +@property (nonatomic, strong) MKMapView *mapView; + +@property (nonatomic, strong) UIButton *locationButton; + +@property (nonatomic, strong) UIButton *returnButton; + +@property (nonatomic, strong) FATExtLocationManager *locationManager; + +@property (nonatomic, assign) double Delta; + +@end + +@implementation FATOpenLocationViewController + +- (MKMapView *)mapView { + if (!_mapView) { + _mapView = [[MKMapView alloc] init]; + _mapView.delegate = self; + _mapView.mapType = MKMapTypeStandard; + _mapView.showsUserLocation = YES; + _mapView.userTrackingMode = MKUserTrackingModeNone; + } + return _mapView; +} + +- (FATExtLocationManager *)locationManager { + if (_locationManager == nil) { + _locationManager = [[FATExtLocationManager alloc] init]; + _locationManager.delegate = self; + } + return _locationManager; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + [self.navigationController setNavigationBarHidden:YES animated:animated]; +} +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view. + [self fatCreatUI]; + + UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 20, self.view.frame.size.height)]; + view.backgroundColor = UIColor.clearColor; + [self.view addSubview:view]; + + UIScreenEdgePanGestureRecognizer *edgeGes = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(edgePan:)]; + edgeGes.edges = UIRectEdgeLeft; + [view addGestureRecognizer:edgeGes]; +} + +- (void)dealloc { +// _mapView.showsUserLocation = NO; +// _mapView.userTrackingMode = MKUserTrackingModeNone; + [_mapView.layer removeAllAnimations]; + [_mapView removeAnnotations:self.mapView.annotations]; + [_mapView removeOverlays:self.mapView.overlays]; + [_mapView removeFromSuperview]; + _mapView.delegate = nil; + _mapView = nil; +} + +- (void)edgePan:(UIPanGestureRecognizer *)recognizer { + [self dismissViewControllerAnimated:YES completion:^{ + + }]; +} + +- (void)fatCreatUI { + CGFloat width = self.view.bounds.size.width; + CGFloat height = self.view.bounds.size.height; + CGFloat bottomViewHeight = 100; + + self.mapView.frame = CGRectMake(0, 0, width, height); + [self.view addSubview:self.mapView]; + + CGFloat top = [UIView fat_statusHeight]; + self.returnButton = [[UIButton alloc] initWithFrame:CGRectMake(16, top, 50, 50)]; + [self.returnButton setImage:[FATExtHelper fat_ext_imageFromBundleWithName:@"map_back_n"] forState:UIControlStateNormal]; + [self.returnButton addTarget:self action:@selector(returnOnClick) forControlEvents:UIControlEventTouchUpInside]; + [self.mapView addSubview:self.returnButton]; + + self.locationButton = [[UIButton alloc] initWithFrame:CGRectMake(width - 48 - 16.5, height - bottomViewHeight - 24.5 - 48, 48, 48)]; + // 判断是否是暗黑模式 + BOOL isDark = false; + if (@available(iOS 13.0, *)) { + isDark = (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark); + } + if (isDark) { + [self.locationButton setImage:[FATExtHelper fat_ext_imageFromBundleWithName:@"map_location_dn"] forState:UIControlStateNormal]; + [self.locationButton setImage:[FATExtHelper fat_ext_imageFromBundleWithName:@"map_location_dp"] forState:UIControlStateHighlighted]; + } else { + [self.locationButton setImage:[FATExtHelper fat_ext_imageFromBundleWithName:@"map_location_ln"] forState:UIControlStateNormal]; + [self.locationButton setImage:[FATExtHelper fat_ext_imageFromBundleWithName:@"map_location_lp"] forState:UIControlStateHighlighted]; + } + [self.locationButton addTarget:self action:@selector(locationOnClick) forControlEvents:UIControlEventTouchUpInside]; + [self.mapView addSubview:self.locationButton]; + + UIView *bgView = [[UIView alloc] initWithFrame:CGRectMake(0, height - bottomViewHeight, width, bottomViewHeight)]; + if (@available(iOS 13.0, *)) { + bgView.backgroundColor = UIColor.systemGray6Color; + } else { + // Fallback on earlier versions + } + [self.mapView addSubview:bgView]; + UILabel *nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 25.5, width - 70, 31)]; + nameLabel.font = [UIFont systemFontOfSize:22]; + nameLabel.text = self.name; + nameLabel.textColor = isDark ? [UIColor colorWithRed:208 / 255.0 green:208 / 255.0 blue:208 / 255.0 alpha:1 / 1.0] : [UIColor colorWithRed:34 / 255.0 green:34 / 255.0 blue:34 / 255.0 alpha:1 / 1.0]; + [bgView addSubview:nameLabel]; + UILabel *addressLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 65.5, width - 70, 16.5)]; + addressLabel.font = [UIFont systemFontOfSize:12]; + addressLabel.text = self.address; + addressLabel.textColor = UIColor.lightGrayColor; + [bgView addSubview:addressLabel]; + + UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(bgView.frame.size.width - 70, bottomViewHeight / 2 - 25, 50, 50)]; + [button setImage:[FATExtHelper fat_ext_imageFromBundleWithName:@"map_navigation_n"] forState:UIControlStateNormal]; + [button setImage:[FATExtHelper fat_ext_imageFromBundleWithName:@"map_navigation_p"] forState:UIControlStateHighlighted]; + button.layer.cornerRadius = 25; + [button addTarget:self action:@selector(navigationOnClick) forControlEvents:UIControlEventTouchUpInside]; + [bgView addSubview:button]; + + double delta = [self.scale doubleValue]; + if (delta < 5) { + delta = 5.00; + } + if (delta > 18) { + delta = 18.00; + } + double LongitudeDelta = [self fat_getLongitudeDelta:delta]; + self.Delta = LongitudeDelta; + CLLocationCoordinate2D centerCoord = {[self judgeLatition:[self.latitude doubleValue]], [self judgeLongitude:[self.longitude doubleValue]]}; + [self.mapView setRegion:MKCoordinateRegionMake(centerCoord, MKCoordinateSpanMake(LongitudeDelta, LongitudeDelta)) animated:YES]; + // 添加一个大头针 + MKMarker *marker = [[MKMarker alloc] init]; + marker.coordinate = centerCoord; + [self.mapView addAnnotation:marker]; +} + +- (void)locationOnClick { + [self startStandardUpdates]; +} + +- (void)returnOnClick { + [self dismissViewControllerAnimated:YES completion:^{ + + }]; +} + +- (void)startStandardUpdates { + if (![FATExtLocationManager locationServicesEnabled]) { + return; + } + + CLAuthorizationStatus status = [FATExtLocationManager authorizationStatus]; + if (status == kCLAuthorizationStatusAuthorizedWhenInUse || + status == kCLAuthorizationStatusAuthorizedAlways || + status == kCLAuthorizationStatusNotDetermined) { + //定位功能可用 + + self.locationManager.delegate = self; + self.locationManager.desiredAccuracy = kCLLocationAccuracyBest; + [self.locationManager requestWhenInUseAuthorization]; + [self.locationManager startUpdatingLocation]; + + } else if (status == kCLAuthorizationStatusDenied) { + } +} + +- (double)fat_getLongitudeDelta:(double)scale { + double longitudeDelta = (360 * self.mapView.frame.size.width / 256.0 / pow(2, scale)); + return longitudeDelta; +} + +- (void)navigationOnClick { + [self fat_openMapApp:@{@"latitude" : self.latitude, @"longitude" : self.longitude, @"destination" : self.name}]; +} + +/** + * 更新到位置之后调用 + * + * @param manager 位置管理者 + * @param locations 位置数组 + */ +- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { + CLLocation *location = [locations firstObject]; + //位置更新后的经纬度 + CLLocationCoordinate2D coord = [FATWGS84ConvertToGCJ02ForAMapView transformFromWGSToGCJ:location.coordinate]; + CLLocation *newLocations = [[CLLocation alloc] initWithLatitude:coord.latitude longitude:coord.longitude]; + + [self.mapView setRegion:MKCoordinateRegionMake(newLocations.coordinate, MKCoordinateSpanMake(self.Delta, self.Delta)) animated:YES]; + [self.locationManager stopUpdatingLocation]; +} + +- (nullable MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id)annotation { + // If the annotation is the user location, just return nil.(如果是显示用户位置的Annotation,则使用默认的蓝色圆点) + if ([annotation isKindOfClass:[MKUserLocation class]]) { + return nil; + } + if ([annotation isKindOfClass:MKMarker.class]) { + MKMarker *marker = (MKMarker *)annotation; + MKAnnotationView *markerView = [mapView dequeueReusableAnnotationViewWithIdentifier:@"markerView"]; + markerView.canShowCallout = YES; + markerView.annotation = marker; + markerView.image = marker.image; + return markerView; + } + return nil; +} + +- (void)fat_openMapApp:(NSDictionary *)data { + /* + 常见app的url Scheme + 1.苹果自带地图(不需要检测,所以不需要URL Scheme) + 2.百度地图 :baidumap + 3.高德地图 :iosamap + 4.谷歌地图 :comgooglemaps + IOS9之后,苹果进一步完善了安全机制,必须在plist里面设置url scheme白名单,不然无法打开对应的应用. + 添加一个字段:LSApplicationQueriesSchemes,类型为数组,然后在这个数组里面再添加我们所需要的地图 URL Scheme : + */ + + // 1.先检测有没有对应的app,有的话再加入 + NSString *appName = [FATExtUtil getAppName]; + NSString *title = [NSString stringWithFormat:@"%@%@", [[FATClient sharedClient] fat_localizedStringForKey:@"Navigate to"], data[@"destination"]]; + CLLocationCoordinate2D coordinate = {[data[@"latitude"] doubleValue], [data[@"longitude"] doubleValue]}; + // 打开地图进行导航 + // 1.创建UIAlertController + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title + message:nil + preferredStyle:UIAlertControllerStyleActionSheet]; + UIAlertAction *appleAction = [UIAlertAction actionWithTitle:[[FATClient sharedClient] fat_localizedStringForKey:@"Apple Maps"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *_Nonnull action) { + CLLocationCoordinate2D loc = CLLocationCoordinate2DMake(coordinate.latitude, coordinate.longitude); + MKMapItem *currentLocation = [MKMapItem mapItemForCurrentLocation]; + MKMapItem *toLocation = [[MKMapItem alloc] initWithPlacemark:[[MKPlacemark alloc] initWithCoordinate:loc addressDictionary:nil]]; + toLocation.name = data[@"destination"] ? data[@"destination"] : @"未知地点"; + [MKMapItem openMapsWithItems:@[ currentLocation, toLocation ] + launchOptions:@{MKLaunchOptionsDirectionsModeKey : MKLaunchOptionsDirectionsModeDriving, + MKLaunchOptionsShowsTrafficKey : [NSNumber numberWithBool:YES]}]; + }]; + [appleAction setValue:[self labelColor] forKey:@"titleTextColor"]; + UIAlertAction *bdAction = [UIAlertAction actionWithTitle:[[FATClient sharedClient] fat_localizedStringForKey:@"Baidu Maps"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *_Nonnull action) { + NSString *urlString = [[NSString stringWithFormat:@"baidumap://map/direction?origin={{我的位置}}&destination=latlng:%f,%f|name=目的地&mode=driving&coord_type=gcj02", coordinate.latitude, coordinate.longitude] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString]]; + }]; + [bdAction setValue:[self labelColor] forKey:@"titleTextColor"]; + UIAlertAction *gdAction = [UIAlertAction actionWithTitle:[[FATClient sharedClient] fat_localizedStringForKey:@"Amap"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *_Nonnull action) { + NSString *urlString = [[NSString stringWithFormat:@"iosamap://path?sourceApplication=%@&backScheme=%@&dlat=%f&dlon=%f&dev=0&t=0&dname=%@", appName, @"iosamap://", coordinate.latitude, coordinate.longitude, data[@"destination"]] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString]]; + }]; + [gdAction setValue:[self labelColor] forKey:@"titleTextColor"]; + UIAlertAction *googleAction = [UIAlertAction actionWithTitle:[[FATClient sharedClient] fat_localizedStringForKey:@"Google Maps"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *_Nonnull action) { + NSString *urlString = [[NSString stringWithFormat:@"comgooglemaps://?x-source=%@&x-success=%@&saddr=&daddr=%f,%f&directionsmode=driving", appName, @"comgooglemaps://", coordinate.latitude, coordinate.longitude] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString]]; + }]; + [googleAction setValue:[self labelColor] forKey:@"titleTextColor"]; + UIAlertAction *tencentAction = [UIAlertAction actionWithTitle:[[FATClient sharedClient] fat_localizedStringForKey:@"Tencent Maps"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *_Nonnull action) { + NSString *urlString = [[NSString stringWithFormat:@"qqmap://map/routeplan?from=我的位置&type=drive&to=%@&tocoord=%f,%f&coord_type=1&referer={ios.blackfish.XHY}",data[@"destination"],coordinate.latitude,coordinate.longitude] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString]]; + }]; + [tencentAction setValue:[self labelColor] forKey:@"titleTextColor"]; + + NSString *cancel = [[FATClient sharedClient] fat_localizedStringForKey:@"Cancel"]; + UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:cancel style:UIAlertActionStyleCancel handler:^(UIAlertAction *_Nonnull action){ + // [alertController ] + }]; + [cancelAction setValue:[self labelColor] forKey:@"titleTextColor"]; + // 1.先检测有没有对应的app,有的话再加入 + [alertController addAction:appleAction]; + if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"baidumap://"]]) { + [alertController addAction:bdAction]; + } + if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"iosamap://"]]) { + [alertController addAction:gdAction]; + } + if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"comgooglemaps://"]]) { + [alertController addAction:googleAction]; + } + if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"qqmap://"]]){ + [alertController addAction:tencentAction]; + } + [alertController addAction:cancelAction]; + UIViewController *topVC = [[UIApplication sharedApplication] fat_topViewController]; + [topVC presentViewController:alertController animated:YES completion:nil]; +} + +- (UIColor *)labelColor { + if (@available(iOS 12.0, *)) { + BOOL isDark = (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark); + return isDark ? [UIColor colorWithRed:208/255.0 green:208/255.0 blue:208/255.0 alpha:1/1.0] : [UIColor colorWithRed:34/255.0 green:34/255.0 blue:34/255.0 alpha:1/1.0]; + } else { + return [UIColor colorWithRed:34/255.0 green:34/255.0 blue:34/255.0 alpha:1/1.0]; + } +} + +// 校验经度是否合规 +- (double)judgeLatition:(double)latitude { + if (latitude >= 90) { + latitude = 85.00; + } + if (latitude <= -90) { + latitude = -85.00; + } + return latitude; +} +// 校验纬度是否合规 +- (double)judgeLongitude:(double)longitude { + if (longitude >= 180) { + longitude = 180.00; + } + if (longitude <= -180) { + longitude = -180.00; + } + return longitude; +} + +@end diff --git a/ios/Classes/FinAppletExt/Common/Util/Record/FATExtRecorder.h b/ios/Classes/FinAppletExt/Common/Util/Record/FATExtRecorder.h new file mode 100644 index 0000000..3b2f53f --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/Record/FATExtRecorder.h @@ -0,0 +1,66 @@ +// +// FATExtRecorder.h +// HLProject +// +// Created by Haley on 2021/12/28. +// Copyright © 2021 Haley. All rights reserved. +// + +#import +#import + +typedef NS_ENUM(NSUInteger, FATFrameState) { + FATFrameStatePrepareToSend, + FATFrameStateAlreadyWillSend, + FATFrameStateAlreadySent +}; + +@class FATExtRecorder; +@protocol FATExtRecorderDelegate + +- (void)extRecorder:(FATExtRecorder *)recorder + onFrameData:(NSData *)frameData + frameIndex:(NSInteger)frameIndex + isLastFrame:(BOOL)isLastFrame; + +- (void)extRecorderDidCompletion:(FATExtRecorder *)recorder; + +- (void)extRecorderBeginInterruption:(FATExtRecorder *)recorder; + +- (void)extRecorderEndInterruption:(FATExtRecorder *)recorder withOptions:(NSUInteger)flags; + +- (void)extRecorder:(FATExtRecorder *)recorder onError:(NSError *)error; + +@end + +@interface FATExtRecorder : NSObject + +@property (nonatomic, assign) BOOL isStarted; + +@property (nonatomic, assign) BOOL isRecording; + +@property (nonatomic, assign) BOOL isPausing; + +@property (nonatomic, strong) NSString *recordFilePath; + +@property (nonatomic, weak) id delegate; + +@property (nonatomic, readonly, copy) NSString *recorderId; + +@property (nonatomic, strong) NSMutableArray *frameInfoArray; + +@property (nonatomic, assign) FATFrameState frameState; + +@property (nonatomic, assign) BOOL waitToSendBuffer; + +@property (nonatomic, copy) void(^eventCallBack)(NSInteger eventType,NSString *eventName, NSDictionary *paramDic,NSDictionary *extDic); + +- (BOOL)startRecordWithDict:(NSDictionary *)dict appId:(NSString *)appId; + +- (BOOL)pauseRecord; + +- (BOOL)resumeRecord; + +- (BOOL)stopRecord; + +@end diff --git a/ios/Classes/FinAppletExt/Common/Util/Record/FATExtRecorder.m b/ios/Classes/FinAppletExt/Common/Util/Record/FATExtRecorder.m new file mode 100644 index 0000000..af123d3 --- /dev/null +++ b/ios/Classes/FinAppletExt/Common/Util/Record/FATExtRecorder.m @@ -0,0 +1,799 @@ +// +// FATExtRecorder.m +// HLProject +// +// Created by Haley on 2021/12/28. +// Copyright © 2021 Haley. All rights reserved. +// + +#import "FATExtRecorder.h" +#import "FATExtUtil.h" +#import "lame.h" +#import + +#define FATDefaultBufferSize 4096 //设置录音的缓冲区默认大小为4096,实际会小于或等于4096,需要处理小于4096的情况 +#define QUEUE_BUFFER_SIZE 3 //输出音频队列缓冲个数 + + +@interface FATExtRecorder () +{ + AudioQueueRef audioQRef; //音频队列对象指针 + AudioStreamBasicDescription inputDescription; //音频流配置 + AudioStreamBasicDescription outputDescription; //音频流配置 + AudioQueueBufferRef audioBuffers[QUEUE_BUFFER_SIZE]; //音频流缓冲区对象 + + AudioConverterRef _encodeConvertRef; + + lame_t lame; + FILE *mp3; + + int pcm_buffer_size; + uint8_t pcm_buffer[FATDefaultBufferSize*2]; +} + +@property (nonatomic, assign) AudioFileID recordFileID; //音频文件标识 用于关联音频文件 +@property (nonatomic, assign) SInt64 recordPacketNum; + +@property (nonatomic, copy) NSString *recorderId; + +// 录音的声道数 +@property (nonatomic, assign) UInt32 mChannelsPerFrame; +// 录音采样率 +@property (nonatomic, assign) UInt32 mSampleRate; +// 编码码率 +@property (nonatomic, assign) UInt32 encodeBitRate; +// 录制的最大时长 +@property (nonatomic, assign) int duration; +// 录音文件格式 +@property (nonatomic, copy) NSString *format; +@property (nonatomic, assign) long long frameSize; +@property (nonatomic, assign) BOOL shouldFrameCallback; + +@property (nonatomic, assign) NSInteger currentBufferIndex; +// 当前这一帧的buffer数据 +@property (nonatomic, strong) NSMutableData *currentFrameData; +@property (nonatomic, assign) long long currentFrameOffset; + +@end + +@implementation FATExtRecorder + +void FATAudioInputCallbackHandler(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, const AudioTimeStamp *inStartTime, UInt32 inNumPackets, const AudioStreamPacketDescription *inPacketDesc) { + + FATExtRecorder *recorder = (__bridge FATExtRecorder *)(inUserData); + [recorder processAudioBuffer:inBuffer inStartTime:inStartTime inNumPackets:inNumPackets inPacketDesc:inPacketDesc audioQueue:inAQ]; +} + +OSStatus FATAudioConverterComplexInputDataProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) { + + FATExtRecorder *recorder = (__bridge FATExtRecorder *)(inUserData); + + BOOL result = [recorder handleConverterDataProcWithBufferList:ioData]; + if (result) { + return noErr; + } + + return -1; +} + +#pragma mark - override +- (instancetype)init { + self = [super init]; + if (self) { + [self p_addNotifications]; + } + return self; +} + +- (void)dealloc { +// NSLog(@"FATExtRecorder---dealloc"); + [[NSNotificationCenter defaultCenter] removeObserver:self]; + AudioQueueDispose(audioQRef, true); +} + +- (void)handleInterruption:(NSNotification *)notification { + NSDictionary *info = notification.userInfo; + AVAudioSessionInterruptionType type = [info[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue]; + if (type == AVAudioSessionInterruptionTypeBegan) { + if ([self.delegate respondsToSelector:@selector(extRecorderBeginInterruption:)]) { + [self.delegate extRecorderBeginInterruption:self]; + } + } else { + AVAudioSessionInterruptionOptions options = [info[AVAudioSessionInterruptionOptionKey] unsignedIntegerValue]; + if (options == AVAudioSessionInterruptionOptionShouldResume) { + //Handle Resume + } + if ([self.delegate respondsToSelector:@selector(extRecorderEndInterruption:withOptions:)]) { + [self.delegate extRecorderEndInterruption:self withOptions:options]; + } + } +} + +- (BOOL)startRecordWithDict:(NSDictionary *)dict appId:(NSString *)appId { + self.currentBufferIndex = 0; + self.currentFrameOffset = 0; + self.currentFrameData = [[NSMutableData alloc] init]; + self.frameState = FATFrameStatePrepareToSend; + self.frameInfoArray = [NSMutableArray array]; + + memset(pcm_buffer, 0, pcm_buffer_size); + pcm_buffer_size = 0; + + self.recorderId = appId; + // 1.配置录音的参数 + // 录音的音频输入源 + NSString *audioSource; + if (!audioSource || ![audioSource isKindOfClass:[NSString class]]) { + audioSource = @"auto"; + } else { + audioSource = dict[@"audioSource"]; + } + AVAudioSession *audioSession = [AVAudioSession sharedInstance]; + if ([audioSource isEqualToString:@"buildInMic"]) { + [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:NULL]; + } else if ([audioSource isEqualToString:@"headsetMic"]) { + [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:NULL]; + } else { + if (@available(iOS 10.0, *)) { + // 这里通过计算,找不到与运算后为45的结果(45为微信的值) +// NSUInteger options = AVAudioSessionCategoryOptionMixWithOthers | AVAudioSessionCategoryOptionAllowBluetooth | AVAudioSessionCategoryOptionAllowAirPlay; + [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord mode:AVAudioSessionModeDefault options:45 error:NULL]; + } else { + NSUInteger options = AVAudioSessionCategoryOptionMixWithOthers | AVAudioSessionCategoryOptionAllowBluetooth; + [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:options error:NULL]; + } + } + [audioSession setActive:YES error:NULL]; + + // 1.1 格式 + NSString *format = [self _format:dict]; + + self.format = format; + AudioFormatID formatID = [self _formatIDWithFormat:format]; + inputDescription.mFormatID = formatID; + + // 1.2.录音通道数 + NSString *numberOfChannelsString = dict[@"numberOfChannels"]; + if (![self _isValidWithNumberOfChannelsString:numberOfChannelsString]) { + if ([self.delegate respondsToSelector:@selector(extRecorder:onError:)]) { + NSError *error = [NSError errorWithDomain:@"FATExtRecorder" code:-1 userInfo:@{NSLocalizedDescriptionKey:@"operateRecorder:fail channel error"}]; + [self.delegate extRecorder:self onError:error]; + } + return NO; + } + + NSNumber *numberOfChannelsNub = [NSNumber numberWithInt:[numberOfChannelsString intValue]]; + if (numberOfChannelsString == nil) { + numberOfChannelsNub = @(2); + } + if ([format isEqualToString:@"aac"]) { + // aac 格式双声道有杂音,暂时只支持单声道 + numberOfChannelsNub = @(1); + } + + inputDescription.mChannelsPerFrame = [numberOfChannelsNub intValue]; + self.mChannelsPerFrame = [numberOfChannelsNub intValue]; + + // 1.3.采样率 + NSString *sampleRateString = dict[@"sampleRate"]; + NSNumber *sampleRateNub; + if (sampleRateString == nil || ![sampleRateString isKindOfClass:[NSString class]]) { + sampleRateNub = @(8000); + } else { + sampleRateNub = [NSNumber numberWithInt:[sampleRateString intValue]]; + } + inputDescription.mSampleRate = [sampleRateNub floatValue]; + self.mSampleRate = inputDescription.mSampleRate; + + // 1.4.编码比特率 + NSString *encodeBitRateString = dict[@"encodeBitRate"]; + NSNumber *encodeBitRateNub; + if (encodeBitRateString == nil || ![encodeBitRateString isKindOfClass:[NSString class]]) { + encodeBitRateNub = @(48000); + } else { + encodeBitRateNub = [NSNumber numberWithInt:[encodeBitRateString intValue]]; + } + self.encodeBitRate = [encodeBitRateNub floatValue]; + + NSString *durationString = dict[@"duration"]; + NSNumber *durationNub; + if (durationString == nil || ![durationString isKindOfClass:[NSString class]]) { + durationNub = @(60000); + } else { + durationNub = [NSNumber numberWithInt:[durationString intValue]]; + } + + if ([durationNub intValue] <= 0) { + durationNub = @(60000); + } + + if ([durationNub intValue] > 600000) { + durationNub = @(600000); + } + + self.duration = [durationNub intValue]; + + // 编码格式 + inputDescription.mFormatID = kAudioFormatLinearPCM; + inputDescription.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked; + + inputDescription.mBitsPerChannel = 16; + // 每帧的字节数 + inputDescription.mBytesPerFrame = (inputDescription.mBitsPerChannel / 8) * inputDescription.mChannelsPerFrame; + // 每个包的字节数 + inputDescription.mBytesPerPacket = inputDescription.mBytesPerFrame; + // 每个包的帧数 + inputDescription.mFramesPerPacket = 1; + + // 2. 生成文件名 + NSString *fileName = [self _fileNameWithFormat:format]; + NSString *filePath = [[FATExtUtil tmpDirWithAppletId:appId] stringByAppendingPathComponent:fileName]; + self.recordFilePath = filePath; + + if ([format isEqualToString:@"mp3"]) { + mp3 = fopen([self.recordFilePath cStringUsingEncoding:1], "wb"); + lame = lame_init(); + //采样率跟原音频参数设置一致 + lame_set_in_samplerate(lame, inputDescription.mSampleRate); + // 通道数跟原音频参数设置一致,不设置默认为双通道 + lame_set_num_channels(lame, inputDescription.mChannelsPerFrame); + lame_set_VBR(lame, vbr_default); + lame_init_params(lame); + } else if ([format isEqualToString:@"aac"]) { + NSError *error; + if (![self _createConverter:&error]) { + if ([self.delegate respondsToSelector:@selector(extRecorder:onError:)]) { + [self.delegate extRecorder:self onError:error]; + } + return NO; + } + [self copyEncoderCookieToFile]; + } + + OSStatus status = AudioQueueNewInput(&inputDescription, FATAudioInputCallbackHandler, (__bridge void *)(self), NULL, NULL, 0, &audioQRef); + if ( status != kAudioSessionNoError ) { + if ([self.delegate respondsToSelector:@selector(extRecorder:onError:)]) { + NSError *error = [NSError errorWithDomain:@"FATExtRecorder" code:-1 userInfo:@{NSLocalizedDescriptionKey:@"AudioQueueNewInput fail"}]; + [self.delegate extRecorder:self onError:error]; + } + return NO; + } + + // 1.5 frameSize + NSString *frameSizeString = dict[@"frameSize"]; + if (![frameSizeString isKindOfClass:[NSString class]]) { + frameSizeString = nil; + } + //设置的缓冲区有多大,那么在回调函数的时候得到的inbuffer的大小就是多大。callBack不足的时候,需要拼接,等待满足frameSize + long long bufferByteSize = FATDefaultBufferSize; + if ([frameSizeString floatValue]> 0.00 && [self _canCallbackFormat:format]) { + self.shouldFrameCallback = YES; + self.frameSize = (long long)[frameSizeString floatValue] * 1024; + } + + if ([format isEqualToString:@"aac"]) { + // aac格式需要1024帧,每帧2字节 + bufferByteSize = 1024 * 2 * self.mChannelsPerFrame; + } + + for (int i = 0; i < QUEUE_BUFFER_SIZE; i++){ + AudioQueueAllocateBuffer(audioQRef, bufferByteSize, &audioBuffers[i]); + AudioQueueEnqueueBuffer(audioQRef, audioBuffers[i], 0, NULL); + } + + CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, (CFStringRef)self.recordFilePath, NULL); + // 创建音频文件 + if ([format isEqualToString:@"aac"]) { + AudioFileCreateWithURL(url, kAudioFileCAFType, &outputDescription, kAudioFileFlags_EraseFile, &_recordFileID); + } else { + AudioFileCreateWithURL(url, kAudioFileCAFType, &inputDescription, kAudioFileFlags_EraseFile, &_recordFileID); + } + + CFRelease(url); + + self.recordPacketNum = 0; + status = AudioQueueStart(audioQRef, NULL); + if (status != kAudioSessionNoError) { + if ([self.delegate respondsToSelector:@selector(extRecorder:onError:)]) { + NSError *error = [NSError errorWithDomain:@"FATExtRecorder" code:-1 userInfo:@{NSLocalizedDescriptionKey:@"AudioQueueStart fail"}]; + [self.delegate extRecorder:self onError:error]; + } + AudioQueueStop(audioQRef, NULL); + return NO; + } + self.isStarted = YES; + self.isRecording = YES; + + return YES; +} + +- (BOOL)pauseRecord { + OSStatus status = AudioQueuePause(audioQRef); + if (status != kAudioSessionNoError) { + if ([self.delegate respondsToSelector:@selector(extRecorder:onError:)]) { + NSError *error = [NSError errorWithDomain:@"FATExtRecorder" code:-1 userInfo:@{NSLocalizedDescriptionKey:@"pause fail"}]; + [self.delegate extRecorder:self onError:error]; + } + return NO; + } + self.isRecording = NO; + self.isPausing = YES; + return YES; +} + +- (BOOL)resumeRecord { + OSStatus status = AudioQueueStart(audioQRef, NULL); + if (status != kAudioSessionNoError) { + if ([self.delegate respondsToSelector:@selector(extRecorder:onError:)]) { + NSError *error = [NSError errorWithDomain:@"FATExtRecorder" code:-1 userInfo:@{NSLocalizedDescriptionKey:@"resume fail"}]; + [self.delegate extRecorder:self onError:error]; + } + return NO; + } + self.isRecording = YES; + self.isPausing = NO; + return YES; +} + +- (BOOL)stopRecord { + if (self.isRecording) { + self.isRecording = NO; + } + self.isStarted = NO; + // 停止录音队列 + AudioQueueStop(audioQRef, true); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + AudioFileClose(_recordFileID); + [self didEndRecord]; + }); + + return YES; +} + +#pragma mark - private +- (void)processAudioBuffer:(AudioQueueBufferRef)inBuffer inStartTime:(AudioTimeStamp *)inStartTime inNumPackets:(UInt32)inNumPackets inPacketDesc:(AudioStreamPacketDescription *)inPacketDesc audioQueue:(AudioQueueRef)audioQueueRef +{ + const int audioSize = inBuffer->mAudioDataByteSize; +// NSLog(@"processAudioBuffer---inNumPackets:%d, size:%d", inNumPackets, audioSize); + if (inNumPackets > 0) { + if ([self.format isEqualToString:@"mp3"]) { + unsigned char mp3_buffer[audioSize]; + // 说法1:因为录音数据是char *类型的,一个char占一个字节。而这里要传的数据是short *类型的,一个short占2个字节 + // 说法2:每packet 有 1 帧; 单声道: 每 帧2 Bytes; 双声道: 每帧 4 Bytes。 + // 所以 单声道 nsamples = inBuffer->mAudioDataByteSize / 2; + // 双声道 nsamples = inBuffer->mAudioDataByteSize / 4; + // 结果 nsamples 正好等于 inNumPackets; + int nsamples = inNumPackets; + /** + 双声道必须要使用lame_encode_buffer_interleaved这个函数 + lame_encode_buffer //录音数据单声道16位整形用这个方法 + lame_encode_buffer_interleaved //录音数据双声道交错用这个方法 + lame_encode_buffer_float //录音数据采样深度32位浮点型用这个方法 + */ + int recvLen; + if (self.mChannelsPerFrame == 1) { + // 单声道音频转码 + recvLen = lame_encode_buffer(lame, inBuffer->mAudioData, NULL, nsamples, mp3_buffer, audioSize); + } else { + recvLen = lame_encode_buffer_interleaved(lame, inBuffer->mAudioData, nsamples, mp3_buffer, audioSize); + } + // 写入文件 + fwrite(mp3_buffer, recvLen, 1, mp3); + if (self.shouldFrameCallback) { + [self.currentFrameData appendBytes:mp3_buffer length:recvLen]; + } +// NSLog(@"%d", recvLen); + } else if ([self.format isEqualToString:@"pcm"]) { + // 非MP3格式直接写入文件 + void *bufferData = inBuffer->mAudioData; + UInt32 buffersize = inBuffer->mAudioDataByteSize; + if (self.shouldFrameCallback) { + [self.currentFrameData appendBytes:bufferData length:buffersize]; + } + AudioFileWritePackets(self.recordFileID, FALSE, audioSize, inPacketDesc, self.recordPacketNum, &inNumPackets, inBuffer->mAudioData); + self.recordPacketNum += inNumPackets; + } else if ([self.format isEqualToString:@"aac"]) { + memcpy(pcm_buffer + pcm_buffer_size, inBuffer->mAudioData, inBuffer->mAudioDataByteSize); + pcm_buffer_size = pcm_buffer_size + inBuffer->mAudioDataByteSize; + + long long willEncodePCMBufferSize = 1024 * 2 * self.mChannelsPerFrame; + if (pcm_buffer_size >= willEncodePCMBufferSize) { + AudioBufferList *bufferList = [self convertedAACBufferListWith:inBuffer]; + + memcpy(pcm_buffer, pcm_buffer + willEncodePCMBufferSize, pcm_buffer_size - willEncodePCMBufferSize); + pcm_buffer_size = pcm_buffer_size - willEncodePCMBufferSize; + + void *bufferData = inBuffer->mAudioData; + UInt32 buffersize = inBuffer->mAudioDataByteSize; + + if (self.shouldFrameCallback) { + [self.currentFrameData appendBytes:bufferData length:buffersize]; + } + // free memory + if (bufferList) { + free(bufferList->mBuffers[0].mData); + free(bufferList); + } + } + } else if ([self.format isEqualToString:@"wav"]) { + // 新增wav格式的音频。 + void *bufferData = inBuffer->mAudioData; + UInt32 buffersize = inBuffer->mAudioDataByteSize; + AudioFileWritePackets(self.recordFileID, FALSE, audioSize, inPacketDesc, self.recordPacketNum, &inNumPackets, inBuffer->mAudioData); + self.recordPacketNum += inNumPackets; + } + + if (self.shouldFrameCallback) { + if ([self.format isEqualToString:@"pcm"]) { + long long fileSize = [self _getFileSize:self.recordFilePath]; + long long currentFrameSize; + if (self.currentBufferIndex == 0) { + currentFrameSize = fileSize - FATDefaultBufferSize; + self.currentFrameOffset = FATDefaultBufferSize; + } else { + currentFrameSize = fileSize - self.currentFrameOffset; + } + + if (fileSize > self.frameSize) { + if ([self.delegate respondsToSelector:@selector(extRecorder:onFrameData:frameIndex:isLastFrame:)]) { + [self.delegate extRecorder:self onFrameData:self.currentFrameData frameIndex:self.currentBufferIndex isLastFrame:NO]; + } + self.currentFrameData = [[NSMutableData alloc] init]; + self.currentBufferIndex++; + } + + } else if ([self.format isEqualToString:@"mp3"]) { + long long fileSize = [self _getFileSize:self.recordFilePath]; + long long currentFrameSize; + if (self.currentBufferIndex == 0) { + currentFrameSize = fileSize - FATDefaultBufferSize; + self.currentFrameOffset = FATDefaultBufferSize; + } else { + currentFrameSize = fileSize - self.currentFrameOffset; + } + + if (currentFrameSize > self.frameSize) { + // 满足一帧 + NSString *frameFilePath = [self.recordFilePath stringByDeletingPathExtension]; + frameFilePath = [frameFilePath stringByAppendingFormat:@"_%d", self.currentBufferIndex]; + frameFilePath = [frameFilePath stringByAppendingPathExtension:self.recordFilePath.pathExtension]; + BOOL result = [self.currentFrameData writeToFile:frameFilePath atomically:YES]; +// NSLog(@"写入文件:%@, 结果:%d",frameFilePath.lastPathComponent, result); + + if ([self.delegate respondsToSelector:@selector(extRecorder:onFrameData:frameIndex:isLastFrame:)]) { + [self.delegate extRecorder:self onFrameData:self.currentFrameData frameIndex:self.currentBufferIndex isLastFrame:NO]; + } + + self.currentFrameData = [[NSMutableData alloc] init]; + self.currentFrameOffset += currentFrameSize; + self.currentBufferIndex++; + } + } else if ([self.format isEqualToString:@"aac"]) { + long long fileSize = [self _getFileSize:self.recordFilePath]; +// NSLog(@"fileSize:%lld", fileSize); + + long long currentFrameSize; + if (self.currentBufferIndex == 0) { + currentFrameSize = fileSize - FATDefaultBufferSize; + self.currentFrameOffset = FATDefaultBufferSize; + } else { + currentFrameSize = fileSize - self.currentFrameOffset; + } + + if (currentFrameSize > self.frameSize) { + // 满足一帧 +// NSLog(@"currentFrameSize:%d", currentFrameSize); + NSString *frameFilePath = [self.recordFilePath stringByDeletingPathExtension]; + frameFilePath = [frameFilePath stringByAppendingFormat:@"_%d", self.currentBufferIndex]; + frameFilePath = [frameFilePath stringByAppendingPathExtension:self.recordFilePath.pathExtension]; + BOOL result = [self.currentFrameData writeToFile:frameFilePath atomically:YES]; +// NSLog(@"写入文件:%@, 结果:%d",frameFilePath.lastPathComponent, result); + + if ([self.delegate respondsToSelector:@selector(extRecorder:onFrameData:frameIndex:isLastFrame:)]) { + [self.delegate extRecorder:self onFrameData:self.currentFrameData frameIndex:self.currentBufferIndex isLastFrame:NO]; + } + + self.currentFrameData = [[NSMutableData alloc] init]; + self.currentFrameOffset += currentFrameSize; + self.currentBufferIndex++; + } + } + } + } + + NSTimeInterval recordDuration = inStartTime->mSampleTime / self.mSampleRate * 1000.0; + if (recordDuration >= self.duration) { + if (self.isRecording) { + [self stopRecord]; + } + return; + } + + if (self.isRecording || self.isPausing) { + // 将缓冲器重新放入缓冲队列,以便重复使用该缓冲器 + AudioQueueEnqueueBuffer(audioQueueRef, inBuffer, 0, NULL); + } +} + +- (BOOL)handleConverterDataProcWithBufferList:(AudioBufferList *)bufferList +{ + if ([self.format isEqualToString:@"aac"]) { + bufferList->mBuffers[0].mData = pcm_buffer; + bufferList->mBuffers[0].mNumberChannels = self.mChannelsPerFrame; + bufferList->mBuffers[0].mDataByteSize = 1024 * 2 * self.mChannelsPerFrame; + + return YES; + } + + return NO; +} + +- (void)didEndRecord { + if ([self.format isEqualToString:@"mp3"]) { + // 写入VBR 头文件, 否则录音的时长不准 + lame_mp3_tags_fid(lame, mp3); + lame_close(lame); + fclose(mp3); + } + + if (self.currentFrameData.length > 0) { + NSString *frameFilePath = [self.recordFilePath stringByDeletingPathExtension]; + frameFilePath = [frameFilePath stringByAppendingFormat:@"_%d", self.currentBufferIndex]; + frameFilePath = [frameFilePath stringByAppendingPathExtension:self.recordFilePath.pathExtension]; + BOOL result = [self.currentFrameData writeToFile:frameFilePath atomically:YES]; +// NSLog(@"Last写入文件:%@, 结果:%d",frameFilePath.lastPathComponent, result); + + if ([self.delegate respondsToSelector:@selector(extRecorder:onFrameData:frameIndex:isLastFrame:)]) { + [self.delegate extRecorder:self onFrameData:self.currentFrameData frameIndex:self.currentBufferIndex isLastFrame:YES]; + } + + self.currentFrameData = nil; + self.currentFrameOffset = 0; + self.currentBufferIndex = 0; + } + + if ([self.delegate respondsToSelector:@selector(extRecorderDidCompletion:)]) { + [self.delegate extRecorderDidCompletion:self]; + } + + // 胶囊按钮(不管什么原因导致的结束录音,都在这里面恢复胶囊按钮的状态) + UIViewController *vc = [[UIApplication sharedApplication] fat_topViewController]; + UINavigationController *nav = (UINavigationController *)vc.navigationController; + if ([nav respondsToSelector:@selector(controlCapsuleStateButton:state:animate:)]) { + [nav controlCapsuleStateButton:YES state:FATCapsuleButtonStateMicroPhone animate:NO]; + } +} + +- (void)p_addNotifications { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:nil]; +} + +#pragma mark - tool method +- (NSString *)_format:(NSDictionary *)data { + NSString *format = data[@"format"]; + if (!format || ![format isKindOfClass:[NSString class]]) { + format = @"aac"; + } + format = [format lowercaseString]; + NSArray *validFormats = @[@"mp3", @"aac", @"wav", @"pcm"]; + if (![validFormats containsObject:format]) { + return @"aac"; + } + + return format; +} + +- (AudioFormatID)_formatIDWithFormat:(NSString *)format { + return kAudioFormatLinearPCM; +} + +- (BOOL)_isValidWithNumberOfChannelsString:(NSString *)numberOfChannelsString { + if (!numberOfChannelsString) { + return YES; + } + + if (![numberOfChannelsString isKindOfClass:[NSString class]]) { + return NO; + } + + if ([numberOfChannelsString isEqualToString:@"1"] || [numberOfChannelsString isEqualToString:@"2"]) { + return YES; + } + + return NO; +} + +- (NSString *)_fileNameWithFormat:(NSString *)format { + NSString *currentDt = [NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]]; + NSData *data = [currentDt dataUsingEncoding:NSUTF8StringEncoding]; + NSString *nameMD5 = [[FATExtUtil fat_md5WithBytes:(char *)[data bytes] length:data.length] lowercaseString]; + NSString *extensionName = [self _extensionNameWithFormat:format]; + NSString *fileName = [NSString stringWithFormat:@"tmp_%@%@", nameMD5, extensionName]; + return fileName; +} + +- (NSString *)_extensionNameWithFormat:(NSString *)format { +// if ([format isEqualToString:@"aac"]) { +// return @".m4a"; +// } + if ([format isEqualToString:@"pcm"]) { + return @".caf"; + } + + NSString *ext = [NSString stringWithFormat:@".%@", format]; + return ext; +} + +- (BOOL)_canCallbackFormat:(NSString *)format +{ + if ([format isEqualToString:@"mp3"]) { + return YES; + } + if ([format isEqualToString:@"pcm"]) { + return YES; + } + if ([format isEqualToString:@"aac"]) { + return YES; + } + + return NO; +} + +- (long long)_getFileSize:(NSString *)path +{ + NSFileManager *filemanager = [NSFileManager defaultManager]; + if (![filemanager fileExistsAtPath:path]) { + return 0; + } + + NSDictionary *attributes = [filemanager attributesOfItemAtPath:path error:nil]; + NSNumber *theFileSize = [attributes objectForKey:NSFileSize]; + return [theFileSize longLongValue]; +} + +- (BOOL)_createConverter:(NSError **)aError { + // 此处目标格式其他参数均为默认,系统会自动计算,否则无法进入encodeConverterComplexInputDataProc回调 + AudioStreamBasicDescription sourceDes = inputDescription; // 原始格式 + AudioStreamBasicDescription targetDes; // 转码后格式 + + // 设置目标格式及基本信息 + memset(&targetDes, 0, sizeof(targetDes)); + targetDes.mFormatID = kAudioFormatMPEG4AAC; + targetDes.mSampleRate = inputDescription.mSampleRate ; + targetDes.mChannelsPerFrame = inputDescription.mChannelsPerFrame; + targetDes.mFramesPerPacket = 1024; // 采集的为AAC需要将targetDes.mFramesPerPacket设置为1024,AAC软编码需要喂给转换器1024个样点才开始编码,这与回调函数中inNumPackets有关,不可随意更改 + + OSStatus status = 0; + UInt32 targetSize = sizeof(targetDes); + status = AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &targetSize, &targetDes); + + memset(&outputDescription, 0, sizeof(outputDescription)); + memcpy(&outputDescription, &targetDes, targetSize); + + // 选择软件编码 + AudioClassDescription audioClassDes; + status = AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders, + sizeof(targetDes.mFormatID), + &targetDes.mFormatID, + &targetSize); + + UInt32 numEncoders = targetSize/sizeof(AudioClassDescription); + AudioClassDescription audioClassArr[numEncoders]; + AudioFormatGetProperty(kAudioFormatProperty_Encoders, + sizeof(targetDes.mFormatID), + &targetDes.mFormatID, + &targetSize, + audioClassArr); + + for (int i = 0; i < numEncoders; i++) { + if (audioClassArr[i].mSubType == kAudioFormatMPEG4AAC && audioClassArr[i].mManufacturer == kAppleSoftwareAudioCodecManufacturer) { + memcpy(&audioClassDes, &audioClassArr[i], sizeof(AudioClassDescription)); + break; + } + } + + status = AudioConverterNewSpecific(&sourceDes, &targetDes, 1, &audioClassDes, &_encodeConvertRef); + + if (status != noErr) { + *aError = [NSError errorWithDomain:@"FATExtRecorder" code:-1 userInfo:@{NSLocalizedDescriptionKey:@"create converter fail"}]; +// NSLog(@"Error : New convertRef failed"); + return NO; + } + + targetSize = sizeof(sourceDes); + status = AudioConverterGetProperty(_encodeConvertRef, kAudioConverterCurrentInputStreamDescription, &targetSize, &sourceDes); + + targetSize = sizeof(targetDes); + status = AudioConverterGetProperty(_encodeConvertRef, kAudioConverterCurrentOutputStreamDescription, &targetSize, &targetDes); + + // 设置码率,需要和采样率对应 + UInt32 bitRate = self.encodeBitRate; + targetSize = sizeof(bitRate); + status = AudioConverterSetProperty(_encodeConvertRef, + kAudioConverterEncodeBitRate, + targetSize, &bitRate); + if (status != noErr) { + *aError = [NSError errorWithDomain:@"FATExtRecorder" code:-1 userInfo:@{NSLocalizedDescriptionKey:@"encodeBitRate not applicable"}]; +// NSLog(@"Error : set encodeBitRate failed"); + return NO; + } + + return YES; +} + +- (void)copyEncoderCookieToFile { + UInt32 cookieSize = 0; + OSStatus status = AudioConverterGetPropertyInfo(_encodeConvertRef, kAudioConverterCompressionMagicCookie, &cookieSize, NULL); + + if (status != noErr || cookieSize == 0) { + return; + } + + char *cookie = (char *)malloc(cookieSize * sizeof(char)); + status = AudioConverterGetProperty(_encodeConvertRef, kAudioConverterCompressionMagicCookie, &cookieSize, cookie); + + if (status == noErr) { + status = AudioFileSetProperty(_recordFileID, kAudioFilePropertyMagicCookieData, cookieSize, cookie); + if (status == noErr) { + UInt32 willEatTheCookie = false; + status = AudioFileGetPropertyInfo(_recordFileID, kAudioFilePropertyMagicCookieData, NULL, &willEatTheCookie); + printf("Writing magic cookie to destination file: %u\n cookie:%d \n", (unsigned int)cookieSize, willEatTheCookie); + } else { + printf("Even though some formats have cookies, some files don't take them and that's OK\n"); + } + } else { + printf("Could not Get kAudioConverterCompressionMagicCookie from Audio Converter!\n"); + } + + free(cookie); +} + +- (AudioBufferList *)convertedAACBufferListWith:(AudioQueueBufferRef)inBuffer +{ + UInt32 maxPacketSize = 0; + UInt32 size = sizeof(maxPacketSize); + OSStatus status; + + status = AudioConverterGetProperty(_encodeConvertRef, + kAudioConverterPropertyMaximumOutputPacketSize, + &size, + &maxPacketSize); + + AudioBufferList *bufferList = (AudioBufferList *)malloc(sizeof(AudioBufferList)); + bufferList->mNumberBuffers = 1; + bufferList->mBuffers[0].mNumberChannels = self.mChannelsPerFrame; + bufferList->mBuffers[0].mData = malloc(maxPacketSize); + bufferList->mBuffers[0].mDataByteSize = 1024 * 2 * self.mChannelsPerFrame; + + UInt32 inNumPackets = 1; + AudioStreamPacketDescription outputPacketDescriptions; + status = AudioConverterFillComplexBuffer(_encodeConvertRef, + FATAudioConverterComplexInputDataProc, + (__bridge void * _Nullable)(self), + &inNumPackets, + bufferList, + &outputPacketDescriptions); + + if (status != noErr) { + free(bufferList->mBuffers[0].mData); + free(bufferList); + return nil; + } + + status = AudioFileWritePackets(self.recordFileID, + FALSE, + bufferList->mBuffers[0].mDataByteSize, + &outputPacketDescriptions, + self.recordPacketNum, + &inNumPackets, + bufferList->mBuffers[0].mData); + if (status == noErr) { + self.recordPacketNum += inNumPackets; // 用于记录起始位置 + } else { +// NSLog(@"数据写入失败"); + } + + return bufferList; +} + +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/FATExtBaseApi.h b/ios/Classes/FinAppletExt/ExtensionApi/FATExtBaseApi.h new file mode 100644 index 0000000..77d9102 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/FATExtBaseApi.h @@ -0,0 +1,105 @@ +// +// FATExtBaseApi.h +// FinAppletExtension +// +// Created by Haley on 2020/8/11. +// Copyright © 2020 finogeeks. All rights reserved. +// + +#import +#import + +@class FATAppletInfo; + +@protocol FATApiHanderContextDelegate +//获取当前控制器 +- (UIViewController *)getCurrentViewController; + +//获取当前的页面id +- (NSString *)getCurrentPageId; + +/// API发送回调事件给service层或者page层 +/// eventName 事件名 +/// eventType 0: service层订阅事件(callSubscribeHandlerWithEvent) 1:page层订阅事件(callSubscribeHandlerWithEvent) int类型方便以后有需要再添加事件类型 +/// paramDic 回调事件的参数 +/// extDic 扩展参数,预留字段,方便以后扩展 可以包含webId,表示发送事件给某个指定页面 service事件可以包含jsContextKey和jsContextValue,可以给分包service的JSContext设置值,用在数据帧 +- (void)sendResultEvent:(NSInteger)eventType eventName:(NSString *)eventName eventParams:(NSDictionary *)param extParams:(NSDictionary *)extDic; + +@optional + +- (NSString *)insertChildView:(UIView *)childView viewId:(NSString *)viewId parentViewId:(NSString *)parentViewId isFixed:(BOOL)isFixed isHidden:(BOOL)isHidden; + +//更新hide属性为NO时使用 +/// @param childView 原生视图 +/// @param viewId 原生视图id +/// @param parentViewId 原生视图父视图id,即WkScrollView的id,对应事件的cid +/// @param isFixed 同层渲染这个字段不起作用,传NO或YES都可以,非同层渲染生效,传NO则组件会跟着页面滚动,传YES则组件固定在页面上,不会随着滚动 +- (void)updateChildViewHideProperty:(UIView *)childView viewId:(NSString *)viewId parentViewId:(NSString *)parentViewId isFixed:(BOOL)isFixed isHidden:(BOOL)isHidden complete:(void(^)(BOOL result))complete; + + +- (UIView *)getChildViewById:(NSString *)viewId; + +- (BOOL)removeChildView:(NSString *)viewId; + +@end + +@protocol FATApiProtocol + +@required + +@property (nonatomic, strong) FATAppletInfo *appletInfo; + +/** + api名称 + */ +@property (nonatomic, readonly, copy) NSString *command; + +/** + 原始参数 + */ +@property (nonatomic, readonly, copy) NSDictionary *param; + +@property (nonatomic, weak) id context; + + +/** + 设置API, 子类重写 + + @param success 成功回调 + @param failure 失败回调 + @param cancel 取消回调 + */ +- (void)setupApiWithSuccess:(void (^)(NSDictionary *successResult))success + failure:(void (^)(NSDictionary *failResult))failure + cancel:(void (^)(NSDictionary *cancelResult))cancel; + +@optional +/** + 同步api,子类重写 + */ +- (NSString *)setupSyncApi; + +@end + +@interface FATExtBaseApi : NSObject + +@property (nonatomic, copy, readonly) NSDictionary *param; + +@property (nonatomic, strong) FATAppletInfo *appletInfo; + +/** + api名称 + */ +@property (nonatomic, readonly, copy) NSString *command; + +@property (nonatomic, weak) id context; + + +//+ (FATExtBaseApi *)apiWithCommand:(NSString *)command param:(NSDictionary *)param; + +//- (void)setupApiWithCallback:(FATExtensionApiCallback)callback; + +//创建API对象,用于地图sdk的API,地图的API有点特殊,如果用户使用了高德或者百度地图,需要创建对应sdk的API对象 ++ (id)fat_apiWithApiClass:(NSString *)apiClassName params:(NSDictionary *)params; + +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/FATExtBaseApi.m b/ios/Classes/FinAppletExt/ExtensionApi/FATExtBaseApi.m new file mode 100644 index 0000000..6c70ad3 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/FATExtBaseApi.m @@ -0,0 +1,180 @@ +// +// FATExtBaseApi.m +// FinAppletExtension +// +// Created by Haley on 2020/8/11. +// Copyright © 2020 finogeeks. All rights reserved. +// + +#import "FATExtBaseApi.h" +#import + +@implementation FATExtBaseApi +- (void)setupApiWithSuccess:(void (^)(NSDictionary *successResult))success + failure:(void (^)(NSDictionary *failResult))failure + cancel:(void (^)(NSDictionary *cancelResult))cancel { + //默认实现,子类重写!!! + if (cancel) { + cancel(nil); + } + + if (success) { + success(@{}); + } + + if (failure) { + failure(nil); + } +} + +/** + 同步api,子类重写 + */ +- (NSString *)setupSyncApi { + return nil; +} + ++ (id)fat_apiWithApiClass:(NSString *)apiClassName params:(NSDictionary *)params { + if (!apiClassName) { + return nil; + } + Class apiClass = NSClassFromString(apiClassName); + if (!apiClass) { + return nil; + } + id apiObj = [[apiClass alloc]init]; + if (![apiObj conformsToProtocol:@protocol(FATApiProtocol)]) { + return nil; + } + id api = (id)apiObj; + NSString *apiName = @""; + //分离出事件名 + NSArray *apiNameArray = [apiClassName componentsSeparatedByString:@"_"]; + if (apiNameArray && apiNameArray.count > 1) { + apiName = apiNameArray[1]; + } + [self setAPiObjectProperty:api command:apiName params:params]; + return api; +} + ++ (void)setAPiObjectProperty:(id) api command:(NSString *)command params:(NSDictionary *)param { + if (![api isKindOfClass:NSObject.class]) { + return; + } + NSObject *apiObj = (NSObject *)api; + [apiObj setValue:command forKey:@"command"]; + [apiObj setValue:param forKey:@"param"]; + //postMessage事件传过来的params是NSString + if ([param isKindOfClass:NSDictionary.class]) { + for (NSString *datakey in param.allKeys) { + NSString *propertyKey = datakey; + @autoreleasepool { + objc_property_t property = class_getProperty([apiObj class], [propertyKey UTF8String]); + if (!property) { + continue; + } + + id value = [param objectForKey:datakey]; + id safetyValue = [self parseFromKeyValue:value]; + + if (!safetyValue) continue; + + NSString *propertyType = [NSString stringWithUTF8String:property_copyAttributeValue(property, "T")]; + propertyType = [propertyType stringByReplacingOccurrencesOfString:@"@" withString:@""]; + propertyType = [propertyType stringByReplacingOccurrencesOfString:@"\\" withString:@""]; + propertyType = [propertyType stringByReplacingOccurrencesOfString:@"\"" withString:@""]; + //NSLog(@"propertyType:%@,value是:%@", propertyType,value); + + //只检校以下几种类型,可变类型我们一般用不着,故不检校 + if ( + [propertyType isEqualToString:@"NSString"] || + [propertyType isEqualToString:@"NSArray"] || + [propertyType isEqualToString:@"NSDictionary"]) { + if (![safetyValue isKindOfClass:NSClassFromString(propertyType)]) { + continue; + } + } + + //NSNumber类型和基本类型统一处理为string,也不需要检校了 + //其他类型不检校 + [apiObj setValue:safetyValue forKey:propertyKey]; + } + } + } +} + +//+ (id)parseFromKeyValue:(id)value { +// //值无效 +// if ([value isKindOfClass:[NSNull class]]) { +// return nil; +// } +// +// if ([value isKindOfClass:[NSNumber class]]) { //统一处理为字符串 +// value = [NSString stringWithFormat:@"%@", value]; +// } +// +// return value; +//} + +//// 作空值过滤处理-任意对象 ++ (id)parseFromKeyValue:(id)value { + //值无效 + if ([value isKindOfClass:[NSNull class]]) { + return nil; + } + + if ([value isKindOfClass:[NSNumber class]]) { //统一处理为字符串 + value = [NSString stringWithFormat:@"%@", value]; + } else if ([value isKindOfClass:[NSArray class]]) { //数组 + value = [self parseFromArray:value]; + } else if ([value isKindOfClass:[NSDictionary class]]) { //字典 + value = [self parseFromDictionary:value]; + } + + return value; +} + +// 作空值过滤处理-字典对象 ++ (NSDictionary *)parseFromDictionary:(NSDictionary *)container { + if ([container isKindOfClass:[NSDictionary class]]) { + NSMutableDictionary *result = [NSMutableDictionary new]; + for (id key in container.allKeys) { + @autoreleasepool { + id value = container[key]; + + id safetyValue = [self parseFromKeyValue:value]; + if (!safetyValue) { + safetyValue = @""; + } + [result setObject:safetyValue forKey:key]; + } + } + return result; + } + return container; +} + +// 作空值过滤处理-数组对象 ++ (NSArray *)parseFromArray:(NSArray *)container { + if ([container isKindOfClass:[NSArray class]]) { + NSMutableArray *result = [NSMutableArray new]; + for (int i = 0; i < container.count; i++) { + @autoreleasepool { + id value = container[i]; + + id safetyValue = [self parseFromKeyValue:value]; + if (!safetyValue) { + safetyValue = @""; + } + + [result addObject:safetyValue]; + } + } + + return result; + } + + return container; +} + +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/FATExt_recorderManager.h b/ios/Classes/FinAppletExt/ExtensionApi/FATExt_recorderManager.h new file mode 100644 index 0000000..8fbb9a1 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/FATExt_recorderManager.h @@ -0,0 +1,27 @@ +// +// FATExt_recorderManager.h +// FinAppletExt +// +// Created by Haley on 2021/1/21. +// Copyright © 2021 finogeeks. All rights reserved. +// + +#import "FATExtBaseApi.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FATExt_recorderManager : FATExtBaseApi + +/** + 真实事件名 + */ +@property (nonatomic, copy) NSString *method; + +/** + 参数 + */ +@property (nonatomic, copy) NSDictionary *data; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/ExtensionApi/FATExt_recorderManager.m b/ios/Classes/FinAppletExt/ExtensionApi/FATExt_recorderManager.m new file mode 100644 index 0000000..d4a9f4d --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/FATExt_recorderManager.m @@ -0,0 +1,87 @@ +// +// FATExt_recorderManager.m +// FinAppletExt +// +// Created by Haley on 2021/1/21. +// Copyright © 2021 finogeeks. All rights reserved. +// + +#import "FATExt_recorderManager.h" +#import "FATExtRecordManager.h" +#import "FATClient+ext.h" + +#import "FATExtUtil.h" + +@implementation FATExt_recorderManager + +- (void)setupApiWithSuccess:(void (^)(NSDictionary *successResult))success + failure:(void (^)(NSDictionary *failResult))failure + cancel:(void (^)(NSDictionary *cancelResult))cancel { + NSArray *validMethods = @[@"start",@"pause",@"resume",@"stop",@"onFrameRecordedRemove"]; + NSString *method = self.method; + if (![validMethods containsObject:method]) { + return; + } + __block NSDictionary *dataDict = self.data; + FATAppletInfo *appInfo = [[FATClient sharedClient] currentApplet]; + BOOL result = [[FATExtRecordManager shareManager] checkRecordWithMethod:method data:dataDict appletId:appInfo.appId]; + if (!result) { + return; + } + if ([method isEqualToString:@"start"]) { + [[FATClient sharedClient] fat_requestAppletAuthorize:FATAuthorizationTypeMicrophone appletId:appInfo.appId complete:^(NSInteger status) { + if (status == 1) { //拒绝 + if (failure) { + failure(@{@"errMsg" : @"unauthorized,用户未授予麦克风权限"}); + } + NSDictionary *params = @{ + @"method" : @"onError", + @"data" : @{@"errMsg" : @"operateRecorder:fail fail_system permissionn denied"}, + }; + if (self.context) { + [self.context sendResultEvent:0 eventName:@"onRecorderManager" eventParams:params extParams:nil]; + } + return; + } + if (status == 2) { //sdk拒绝 + if (failure) { + failure(@{@"errMsg" : @"unauthorized disableauthorized,SDK被禁止申请麦克风权限"}); + } + return; + } + dataDict = [self checkAACAuioParams:dataDict]; + [[FATExtRecordManager shareManager] startRecordWithData:dataDict appletId:appInfo.appId eventBlock:^(NSInteger eventType, NSString *eventName, NSDictionary *paramDic, NSDictionary *extDic) { + if (self.context) { + [self.context sendResultEvent:eventType eventName:eventName eventParams:paramDic extParams:extDic]; + } + }]; + }]; + } else if ([method isEqualToString:@"pause"]) { + [[FATExtRecordManager shareManager] pauseRecordWithData:dataDict appletId:appInfo.appId]; + } else if ([method isEqualToString:@"resume"]) { + [[FATExtRecordManager shareManager] resumeRecordWithData:dataDict appletId:appInfo.appId]; + } else if ([method isEqualToString:@"stop"]) { + [[FATExtRecordManager shareManager] stopRecordWithData:dataDict appletId:appInfo.appId]; + } else if ([method isEqualToString:@"onFrameRecordedRemove"]) { + [[FATExtRecordManager shareManager] sendRecordFrameBufferWithData:dataDict appletId:appInfo.appId]; + } +} + + +/// 检测录音参数,如果是aac格式的音频,并且参数都是默认值,需要把sampleRate由8000改为16000。否则会录制失败。 +/// - Parameter dic: 录音参数。 +- (NSDictionary *)checkAACAuioParams:(NSDictionary *)dic { + NSMutableDictionary *data = [[NSMutableDictionary alloc] initWithDictionary:dic]; + if ([dic[@"format"] isEqualToString:@"aac"]) { + NSString *encodeBitRate = dic[@"encodeBitRate"]; + NSString *sampleRate = dic[@"sampleRate"]; + NSString *numberOfChannels = dic[@"numberOfChannels"]; + if ([encodeBitRate isEqualToString:@"48000"] && [numberOfChannels isEqualToString:@"2"] && [sampleRate isEqualToString:@"8000"]) { + [data setValue:@"16000" forKey:@"sampleRate"]; + } + } + return data; +} + +@end + diff --git a/ios/Classes/FinAppletExt/ExtensionApi/FATExt_startRecord.h b/ios/Classes/FinAppletExt/ExtensionApi/FATExt_startRecord.h new file mode 100644 index 0000000..0ab6411 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/FATExt_startRecord.h @@ -0,0 +1,16 @@ +// +// Copyright (c) 2017, finogeeks.com +// All rights reserved. +// +// +// + +#import "FATExtBaseApi.h" + +/** + 开始录音。当主动调用wx.stopRecord,或者录音超过1分钟时自动结束录音,返回录音文件的临时文件路径。 + 如果前一次录音还在录音 本次录音则不会开始 + */ +@interface FATExt_startRecord : FATExtBaseApi + +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/FATExt_startRecord.m b/ios/Classes/FinAppletExt/ExtensionApi/FATExt_startRecord.m new file mode 100644 index 0000000..baf6ba6 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/FATExt_startRecord.m @@ -0,0 +1,44 @@ +// +// Copyright (c) 2017, finogeeks.com +// All rights reserved. +// +// +// + +#import "FATExt_startRecord.h" +#import "FATExtAVManager.h" +#import "FATClient+ext.h" + +#import + +@implementation FATExt_startRecord + +- (void)setupApiWithSuccess:(void (^)(NSDictionary *successResult))success + failure:(void (^)(NSDictionary *failResult))failure + cancel:(void (^)(NSDictionary *cancelResult))cancel { + FATAppletInfo *appInfo = [[FATClient sharedClient] currentApplet]; + [[FATClient sharedClient] fat_requestAppletAuthorize:FATAuthorizationTypeMicrophone appletId:appInfo.appId complete:^(NSInteger status) { + if (status == 1) { + if (failure) { + failure(@{@"errMsg" : @"unauthorized,用户未授予麦克风权限"}); + } + return; + } else if (status == 2) { + if (failure) { + failure(@{@"errMsg" : @"unauthorized disableauthorized,SDK被禁止申请麦克风权限"}); + } + return; + } + [[FATExtAVManager sharedManager] startRecordWithSuccess:^(NSString *tempFilePath) { + if (success) { + success(@{@"tempFilePath" : tempFilePath}); + } + } fail:^(NSString *msg) { + if (failure) { + failure(@{@"errMsg" : msg}); + } + }]; + }]; +} + +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/FATExt_stopRecord.h b/ios/Classes/FinAppletExt/ExtensionApi/FATExt_stopRecord.h new file mode 100644 index 0000000..0fa35cf --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/FATExt_stopRecord.h @@ -0,0 +1,15 @@ +// +// Copyright (c) 2017, finogeeks.com +// All rights reserved. +// +// +// + +#import "FATExtBaseApi.h" + +/** + 主动调用停止录音。 + */ +@interface FATExt_stopRecord : FATExtBaseApi + +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/FATExt_stopRecord.m b/ios/Classes/FinAppletExt/ExtensionApi/FATExt_stopRecord.m new file mode 100644 index 0000000..add6dc6 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/FATExt_stopRecord.m @@ -0,0 +1,22 @@ +// +// Copyright (c) 2017, finogeeks.com +// All rights reserved. +// +// +// + +#import "FATExt_stopRecord.h" +#import "FATExtAVManager.h" + +@implementation FATExt_stopRecord + +- (void)setupApiWithSuccess:(void (^)(NSDictionary *successResult))success + failure:(void (^)(NSDictionary *failResult))failure + cancel:(void (^)(NSDictionary *cancelResult))cancel { + [[FATExtAVManager sharedManager] stopRecord]; + if (success) { + success(@{}); + } +} + +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExtNavigationController.h b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExtNavigationController.h new file mode 100644 index 0000000..affbc75 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExtNavigationController.h @@ -0,0 +1,17 @@ +// +// FATExtNavigationController.h +// FinAppletExt +// +// Created by 王兆耀 on 2022/10/28. +// Copyright © 2022 finogeeks. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface FATExtNavigationController : UINavigationController + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExtNavigationController.m b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExtNavigationController.m new file mode 100644 index 0000000..b89d85f --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExtNavigationController.m @@ -0,0 +1,32 @@ +// +// FATExtNavigationController.m +// FinAppletExt +// +// Created by 王兆耀 on 2022/10/28. +// Copyright © 2022 finogeeks. All rights reserved. +// + +#import "FATExtNavigationController.h" + +@interface FATExtNavigationController () + +@end + +@implementation FATExtNavigationController + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view. +} + +//支持旋转 +- (BOOL)shouldAutorotate { + return NO; +} + +//支持的方向 +- (UIInterfaceOrientationMask)supportedInterfaceOrientations { + return UIInterfaceOrientationMaskPortrait; +} + +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_LocationUpdateManager.h b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_LocationUpdateManager.h new file mode 100644 index 0000000..20dc948 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_LocationUpdateManager.h @@ -0,0 +1,38 @@ +// +// FATExt_LocationUpdateManager.h +// FinAppletExt +// +// Created by 王兆耀 on 2022/11/6. +// + +#import +#import "FATExtBaseApi.h" +#import "FATExtLocationManager.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FATExt_LocationUpdateManager : NSObject + ++ (instancetype)sharedManager; + +@property (nonatomic, strong) FATExtLocationManager *locationManager; + +@property (nonatomic, assign) BOOL locationIsInit; + +@property (nonatomic, copy) NSString *appletId; + +@property (nonatomic, weak) id context; + +- (void)startLocationUpdateType:(NSString *)type isAllowsBackgroundLocationUpdates:(BOOL)result withAppId:(NSString *)appId Success:(void (^)(NSDictionary *successResult))success + failure:(void (^)(NSDictionary *failResult))failure + cancel:(void (^)(NSDictionary *cancelResult))cancel; + +- (void)onLocationUpdate; + +- (void)stopLocationUpdate; + +- (void)checkLocationState; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_LocationUpdateManager.m b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_LocationUpdateManager.m new file mode 100644 index 0000000..05debb3 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_LocationUpdateManager.m @@ -0,0 +1,200 @@ +// +// FATExt_LocationUpdateManager.m +// FinAppletExt +// +// Created by 王兆耀 on 2022/11/6. +// + +#import "FATExt_LocationUpdateManager.h" +#import +#import "FATWGS84ConvertToGCJ02.h" + +#import + +static FATExt_LocationUpdateManager *instance = nil; + + +NSString *const FATExtAppletUpdateBackgroudPermissions = @"FATAppletUpdateBackgroudPermissions"; + +@interface FATExt_LocationUpdateManager () + + +@property (nonatomic, copy) NSString *type; + +@property (nonatomic, copy) void (^success)(NSDictionary *_Nonnull); +@property (nonatomic, copy) void (^failure)(NSDictionary *_Nullable); +@property (nonatomic, copy) void (^cancel)(NSDictionary *_Nullable); + +@end + +@implementation FATExt_LocationUpdateManager + ++ (instancetype)sharedManager { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[[self class] alloc] init]; + }); + return instance; +} + ++ (instancetype)allocWithZone:(struct _NSZone *)zone { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [super allocWithZone:zone]; + }); + return instance; +} + +- (instancetype)init { + self = [super init]; + if (self) { + //小程序关闭通知 + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appletClose:) name:FATAppletDestroyNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(permissionsUpdate:) name:FATExtAppletUpdateBackgroudPermissions object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appletEnterBackground:) name:FATAppletEnterBackgroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidForeground:) name:FATAppletForegroundNotification object:nil]; + } + + return self; +} + +#pragma 通知的相关处理 +- (void)appletClose:(NSNotification *)notification { + NSDictionary *dic = notification.userInfo; + NSString *appletId = [dic objectForKey:@"appletId"]; + // 关闭小程序时,需要停止获取定位。 + if (appletId && [appletId isEqualToString:self.appletId] && self.locationIsInit) { + [self stopLocationUpdate]; + } +} + +- (void)permissionsUpdate:(NSNotification *)notification { + NSDictionary *dic = notification.userInfo; + NSInteger type = [[dic objectForKey:@"type"] integerValue]; + if (type == 0 ) { + [self stopLocationUpdate]; + } + if (_locationManager.allowsBackgroundLocationUpdates && self.locationIsInit) { + if (type == 1) { + [self stopLocationUpdate]; + } + } +} + +- (void)appletEnterBackground:(NSNotification *)notification { + if (!_locationManager.allowsBackgroundLocationUpdates && self.locationIsInit) { + [_locationManager stopUpdatingLocation]; + } +} + +- (void)appDidForeground:(NSNotification *)notification { + if (!_locationManager.allowsBackgroundLocationUpdates && self.locationIsInit) { + if ([CLLocationManager locationServicesEnabled] && ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedWhenInUse || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedAlways)) { + [_locationManager startUpdatingLocation]; + } + } +} + +- (void)checkLocationState { + if (self.locationIsInit) { + // 胶囊按钮 + UIViewController *vc = [[UIApplication sharedApplication] fat_topViewController]; + UINavigationController *nav = (UINavigationController *)vc.navigationController; + if ([nav respondsToSelector:@selector(controlCapsuleStateButton:state:animate:)]) { + [nav controlCapsuleStateButton:NO state:FATCapsuleButtonStateLocation animate:YES]; + } + } +} + + +- (void)startLocationUpdateType:(NSString *)type isAllowsBackgroundLocationUpdates:(BOOL)result withAppId:(NSString *)appId Success:(void (^)(NSDictionary *successResult))success + failure:(void (^)(NSDictionary *failResult))failure + cancel:(void (^)(NSDictionary *cancelResult))cancel { + // 如果已经初始化了,并且参数一致,就终止 + if (self.locationIsInit && _locationManager.allowsBackgroundLocationUpdates == result) { + return; + } + //定位功能可用 + _locationManager = [[FATExtLocationManager alloc] init]; + _locationManager.delegate = self; + _locationManager.desiredAccuracy = kCLLocationAccuracyBest; + _locationManager.allowsBackgroundLocationUpdates = result; + _locationManager.pausesLocationUpdatesAutomatically = YES; + [_locationManager requestWhenInUseAuthorization]; + self.type = type; + self.locationIsInit = YES; + if ([CLLocationManager locationServicesEnabled] && ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedWhenInUse || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedAlways)) { + [self onLocationUpdate]; + } + self.appletId = appId; + self.success = success; + self.failure = failure; +} + +- (void)onLocationUpdate { + [_locationManager startUpdatingLocation]; +} + +- (void)stopLocationUpdate { + [_locationManager stopUpdatingLocation]; + if (self.context) { + [self.context sendResultEvent:0 eventName:@"offLocationChange" eventParams:@{} extParams:nil]; + } + self.appletId = @""; + self.locationIsInit = NO; +} + +#pragma mark - CLLocationManagerDelegate +- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { + CLLocation *newLocation = [locations objectAtIndex:0]; + // 把默认值改为gcj02 + NSString *typeString = @"gcj02"; + CLLocationCoordinate2D coord = [FATWGS84ConvertToGCJ02ForAMapView transformFromWGSToGCJ:[newLocation coordinate]]; + newLocation = [[CLLocation alloc] initWithLatitude:coord.latitude longitude:coord.longitude]; + + if ([self.type isEqualToString:@"wgs84"]) { + CLLocationCoordinate2D coord = [FATWGS84ConvertToGCJ02ForAMapView transformFromGCJToWGS:[newLocation coordinate]]; + newLocation = [[CLLocation alloc] initWithLatitude:coord.latitude longitude:coord.longitude]; + typeString = @"wgs84"; + } + + CLLocationCoordinate2D coordinate = newLocation.coordinate; + NSDictionary *params = @{@"altitude" : @(newLocation.altitude), + @"latitude" : @(coordinate.latitude), + @"longitude" : @(coordinate.longitude), + @"speed" : @(newLocation.speed), + @"accuracy" : @(newLocation.horizontalAccuracy), + @"type" : typeString, + @"verticalAccuracy" : @(newLocation.verticalAccuracy), + @"horizontalAccuracy" : @(newLocation.horizontalAccuracy) + }; + if (self.context) { + [self.context sendResultEvent:0 eventName:@"onLocationChange" eventParams:params extParams:nil]; + } +} + +- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { + if (self.context) { + [self.context sendResultEvent:0 eventName:@"onLocationChangeError" eventParams:@{} extParams:nil]; + } + [self stopLocationUpdate]; + if (self.failure) { + self.failure(@{}); + } +} + +//- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status { +// if (status == kCLAuthorizationStatusAuthorizedAlways || status == kCLAuthorizationStatusAuthorizedWhenInUse) { +// [self onLocationUpdate]; +// if (self.success) { +// self.success(@{}); +// } +// } else { +// if (self.failure) { +// self.failure(@{@"errMsg" : @"system permission denied"}); +// } +// [self stopLocationUpdate]; +// } +//} + +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_chooseLocation.h b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_chooseLocation.h new file mode 100644 index 0000000..3383a7f --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_chooseLocation.h @@ -0,0 +1,17 @@ +// +// FATExt_chooseLocation.h +// FinAppletExt +// +// Created by Haley on 2020/8/19. +// Copyright © 2020 finogeeks. All rights reserved. +// + +#import "FATExtBaseApi.h" + +@interface FATExt_chooseLocation : FATExtBaseApi +/// 目标地纬度 +@property (nonatomic, strong) NSString *latitude; +/// 目标地经度 +@property (nonatomic, strong) NSString *longitude; + +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_chooseLocation.m b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_chooseLocation.m new file mode 100644 index 0000000..d836017 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_chooseLocation.m @@ -0,0 +1,97 @@ +// +// FATExt_chooseLocation.m +// FinAppletExt +// +// Created by Haley on 2020/8/19. +// Copyright © 2020 finogeeks. All rights reserved. +// + +#import "FATExt_chooseLocation.h" +#import "FATMapViewController.h" +#import "FATClient+ext.h" +#import "FATExtMapManager.h" +#import "FATExtNavigationController.h" +#import "FATExt_locationAuthManager.h" +#import + +@implementation FATExt_chooseLocation + +- (void)setupApiWithSuccess:(void (^)(NSDictionary *successResult))success + failure:(void (^)(NSDictionary *failResult))failure + cancel:(void (^)(NSDictionary *cancelResult))cancel { +// if (![CLLocationManager locationServicesEnabled]) { +// if (callback) { +// callback(FATExtensionCodeFailure, @{@"errMsg" : @"location service not open"}); +// } +// return; +// } + NSString *mapClassStr = NSStringFromClass([FATExtMapManager shareInstance].mapClass); + NSString *apiName = nil; + if ([mapClassStr isEqualToString:@"FATBDMapView"]) { + apiName = @"FATBDExt_chooseLocation"; + } else if ([mapClassStr isEqualToString:@"FATTXMapView"]) { + apiName = @"FATTXExt_chooseLocation"; + } else if ([mapClassStr isEqualToString:@"FATGDMapView"]) { + apiName = @"FATGDExt_chooseLocation"; + } + if (apiName) { + id api = [self.class fat_apiWithApiClass:apiName params:self.param]; + if (api) { + api.appletInfo = self.appletInfo; + api.context = self.context; + [api setupApiWithSuccess:success failure:failure cancel:cancel]; + return; + } + } + + [[FATClient sharedClient] fat_requestAppletAuthorize:FATAuthorizationTypeLocation appletId:self.appletInfo.appId complete:^(NSInteger status) { + if (status == 0) { + [[FATExt_locationAuthManager shareInstance] fat_requestAppletLocationAuthorize:self.appletInfo isBackground:NO withComplete:^(BOOL status) { + if (status) { + [self callChooseLocationWithSuccess:success failure:failure cancel:cancel]; + } else { + if (failure) { + failure(@{@"errMsg" : @"system permission denied"}); + } + } + }]; + } else if (status == 1) { + //定位不能用 + if (failure) { + failure(@{@"errMsg" : @"unauthorized,用户未授予位置权限"}); + } + + } else { + if (failure) { + failure(@{@"errMsg" : @"unauthorized disableauthorized,SDK被禁止申请位置权限"}); + } + } + }]; +} + +- (void)callChooseLocationWithSuccess:(void (^)(NSDictionary *_Nonnull))success failure:(void (^)(NSDictionary *_Nullable))failure cancel:(void (^)(NSDictionary *cancelResult))cancel { + // 弹出定位选择界面 + UIViewController *topVC = [[UIApplication sharedApplication] fat_topViewController]; + + FATMapViewController *mapVC = [[FATMapViewController alloc] init]; + mapVC.latitude = self.latitude; + mapVC.longitude = self.longitude; + mapVC.cancelBlock = ^{ + cancel(nil); + }; + mapVC.sureBlock = ^(NSDictionary *locationInfo) { + success(locationInfo); + }; + FATExtNavigationController *nav = [[FATExtNavigationController alloc] initWithRootViewController:mapVC]; + if (@available(iOS 15, *)) { + UINavigationBarAppearance *barAppearance = [[UINavigationBarAppearance alloc] init]; + [barAppearance configureWithOpaqueBackground]; + barAppearance.backgroundColor = [UIColor whiteColor]; + nav.navigationBar.standardAppearance = barAppearance; + nav.navigationBar.scrollEdgeAppearance = barAppearance; + } + + [topVC presentViewController:nav animated:YES completion:nil]; +} + +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_choosePoi.h b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_choosePoi.h new file mode 100644 index 0000000..37178b3 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_choosePoi.h @@ -0,0 +1,16 @@ +// +// FATExt_choosePoi.h +// FinAppletExt +// +// Created by 王兆耀 on 2021/12/7. +// + +#import "FATExtBaseApi.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FATExt_choosePoi : FATExtBaseApi + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_choosePoi.m b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_choosePoi.m new file mode 100644 index 0000000..feb238f --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_choosePoi.m @@ -0,0 +1,92 @@ +// +// FATExt_choosePoi.m +// FinAppletExt +// +// Created by 王兆耀 on 2021/12/7. +// + +#import "FATExt_choosePoi.h" +#import "FATLocationManager.h" +#import "FATWGS84ConvertToGCJ02.h" +#import "FATClient+ext.h" +#import "FATExtChoosePoiViewController.h" +#import "FATExtMapManager.h" +#import "FATExtNavigationController.h" +#import "FATExt_locationAuthManager.h" +#import + +@implementation FATExt_choosePoi + +- (void)setupApiWithSuccess:(void (^)(NSDictionary *successResult))success + failure:(void (^)(NSDictionary *failResult))failure + cancel:(void (^)(NSDictionary *cancelResult))cancel { + // FATAppletInfo *appInfo = [[FATClient sharedClient] currentApplet]; + //如果使用的是百度地图sdk,需要调用百度地图的API方法 + NSString *mapClassStr = NSStringFromClass([FATExtMapManager shareInstance].mapClass); + NSString *apiName = nil; + if ([mapClassStr isEqualToString:@"FATBDMapView"]) { + apiName = @"FATBDExt_choosePoi"; + } else if ([mapClassStr isEqualToString:@"FATTXMapView"]) { + apiName = @"FATTXExt_choosePoi"; + } + if (apiName) { + id api = [self.class fat_apiWithApiClass:apiName params:self.param]; + if (api) { + api.appletInfo = self.appletInfo; + api.context = self.context; + [api setupApiWithSuccess:success failure:failure cancel:cancel]; + return; + } + } + [[FATClient sharedClient] fat_requestAppletAuthorize:FATAuthorizationTypeLocation appletId:self.appletInfo.appId complete:^(NSInteger status) { + if (status == 0) { + //定位功能可用 + [[FATExt_locationAuthManager shareInstance] fat_requestAppletLocationAuthorize:self.appletInfo isBackground:NO withComplete:^(BOOL status) { + if (status) { + [self callChooseLocationWithWithSuccess:success failure:failure cancel:cancel]; + } else { + if (failure) { + failure(@{@"errMsg" : @"system permission denied"}); + } + } + }]; + } else if (status == 1) { + //定位不能用 + if (failure) { + failure(@{@"errMsg" : @"unauthorized,用户未授予位置权限"}); + } + } else { + if (failure) { + failure(@{@"errMsg" : @"unauthorized disableauthorized,SDK被禁止申请位置权限"}); + } + } + }]; +} + + +- (void)callChooseLocationWithWithSuccess:(void (^)(NSDictionary *successResult))success + failure:(void (^)(NSDictionary *failResult))failure + cancel:(void (^)(NSDictionary *cancelResult))cancel { + // 弹出定位选择界面 + UIViewController *topVC = [[UIApplication sharedApplication] fat_topViewController]; + + FATExtChoosePoiViewController *mapVC = [[FATExtChoosePoiViewController alloc] init]; + mapVC.cancelBlock = ^{ + cancel(nil); + }; + mapVC.sureBlock = ^(NSDictionary *locationInfo) { + success(locationInfo); + }; + FATExtNavigationController *nav = [[FATExtNavigationController alloc] initWithRootViewController:mapVC]; + if (@available(iOS 15, *)) { + UINavigationBarAppearance *barAppearance = [[UINavigationBarAppearance alloc] init]; + [barAppearance configureWithOpaqueBackground]; + barAppearance.backgroundColor = [UIColor whiteColor]; + nav.navigationBar.standardAppearance = barAppearance; + nav.navigationBar.scrollEdgeAppearance = barAppearance; + } + + [topVC presentViewController:nav animated:YES completion:nil]; +} + +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_getLocation.h b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_getLocation.h new file mode 100644 index 0000000..f3d432d --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_getLocation.h @@ -0,0 +1,22 @@ +// +// FATExt_getLocation.h +// FinAppletExt +// +// Created by Haley on 2020/12/10. +// Copyright © 2020 finogeeks. All rights reserved. +// + +#import "FATExtBaseApi.h" + +@interface FATExt_getLocation : FATExtBaseApi + +@property (nonatomic, copy) NSString *type; + +@property (nonatomic, assign) BOOL altitude; + +@property (nonatomic, assign) BOOL isHighAccuracy; + +@property (nonatomic, assign) NSInteger highAccuracyExpireTime; + + +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_getLocation.m b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_getLocation.m new file mode 100644 index 0000000..023b128 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_getLocation.m @@ -0,0 +1,142 @@ +// +// FATExt_getLocation.m +// FinAppletExt +// +// Created by Haley on 2020/12/10. +// Copyright © 2020 finogeeks. All rights reserved. +// + +#import "FATExt_getLocation.h" +#import "FATLocationManager.h" +#import "FATWGS84ConvertToGCJ02.h" +#import "FATClient+ext.h" +#import "FATExtLocationManager.h" +#import "FATExtMapManager.h" +#import "FATExt_locationAuthManager.h" +#import + +@interface FATExt_getLocation () + +@property (nonatomic, strong) FATExtLocationManager *locationManager; + +@property (nonatomic, strong) FATExtBaseApi *strongSelf; +@property (nonatomic, copy) void (^success)(NSDictionary *_Nonnull); +@property (nonatomic, copy) void (^failure)(NSDictionary *_Nullable); +@property (nonatomic, strong) NSTimer *locationUpdateTimer; + +@end + +@implementation FATExt_getLocation + +- (void)setupApiWithSuccess:(void (^)(NSDictionary *successResult))success + failure:(void (^)(NSDictionary *failResult))failure + cancel:(void (^)(NSDictionary *cancelResult))cancel { + + NSString *mapClassStr = NSStringFromClass([FATExtMapManager shareInstance].mapClass); + NSString *apiName = nil; + if ([mapClassStr isEqualToString:@"FATBDMapView"]) { + apiName = @"FATBDExt_getLocation"; + } else if ([mapClassStr isEqualToString:@"FATTXMapView"]) { + apiName = @"FATTXExt_getLocation"; + } else if ([mapClassStr isEqualToString:@"FATGDMapView"]) { + apiName = @"FATGDExt_getLocation"; + } + if (apiName) { + id api = [self.class fat_apiWithApiClass:apiName params:self.param]; + if (api) { + api.appletInfo = self.appletInfo; + api.context = self.context; + [api setupApiWithSuccess:success failure:failure cancel:cancel]; + return; + } + } + + self.success = success; + self.failure = failure; +// FATAppletInfo *appInfo = [[FATClient sharedClient] currentApplet]; + [[FATClient sharedClient] fat_requestAppletAuthorize:FATAuthorizationTypeLocation appletId:self.appletInfo.appId complete:^(NSInteger status) { + if (status == 0) { + //定位功能可用 + [[FATExt_locationAuthManager shareInstance] fat_requestAppletLocationAuthorize:self.appletInfo isBackground:NO withComplete:^(BOOL status) { + if (status) { + [self startUpdateLocation]; + } else { + if (failure) { + failure(@{@"errMsg" : @"system permission denied"}); + } + } + }]; + } else if (status == 1) { + //定位不能用 + if (failure) { + failure(@{@"errMsg" : @"unauthorized,用户未授予位置权限"}); + } + } else { + if (failure) { + failure(@{@"errMsg" : @"unauthorized disableauthorized,SDK被禁止申请位置权限"}); + } + } + }]; +} + +- (void)startUpdateLocation { + _locationManager = [[FATExtLocationManager alloc] init]; + _locationManager.delegate = self; + if (self.isHighAccuracy) { + _locationManager.desiredAccuracy = kCLLocationAccuracyBest; + } else { + _locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters; + } + [_locationManager requestWhenInUseAuthorization]; + [_locationManager startUpdatingLocation]; + + _strongSelf = self; + __weak typeof(self) weak_self = self; + dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 10); + dispatch_after(time, dispatch_get_global_queue(0, 0), ^{ + weak_self.strongSelf = nil; + }); +} + +#pragma mark - CLLocationManagerDelegate +- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { + CLLocation *newLocation = [locations objectAtIndex:0]; + NSString *typeString = @"wgs84"; + //判断是不是属于国内范围 + if ([self.type isEqualToString:@"gcj02"]) { + //转换后的coord + CLLocationCoordinate2D coord = [FATWGS84ConvertToGCJ02ForAMapView transformFromWGSToGCJ:[newLocation coordinate]]; + newLocation = [[CLLocation alloc] initWithLatitude:coord.latitude longitude:coord.longitude]; + typeString = @"gcj02"; + } + + [FATLocationManager manager].location = newLocation; + CLLocationCoordinate2D coordinate = newLocation.coordinate; + + if (self.success) { + NSDictionary *params = @{@"altitude" : @(newLocation.altitude), + @"latitude" : @(coordinate.latitude), + @"longitude" : @(coordinate.longitude), + @"speed" : @(newLocation.speed), + @"accuracy" : @(newLocation.horizontalAccuracy), + @"type" : typeString, + @"verticalAccuracy" : @(newLocation.verticalAccuracy), + @"horizontalAccuracy" : @(newLocation.horizontalAccuracy) + }; + NSMutableDictionary *dataDic = [[NSMutableDictionary alloc] initWithDictionary:params]; + if (!self.altitude) { + [dataDic removeObjectForKey:@"altitude"]; + } + self.success(dataDic); + } + + [_locationManager stopUpdatingLocation]; +} + +- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { + if (self.failure) { + self.failure(nil); + } +} + +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_locationAuthManager.h b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_locationAuthManager.h new file mode 100644 index 0000000..2ac083a --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_locationAuthManager.h @@ -0,0 +1,21 @@ +// +// FATExt_locationAuthManager.h +// FinAppletExt +// +// Created by 王兆耀 on 2022/12/24. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface FATExt_locationAuthManager : NSObject + ++ (instancetype)shareInstance; + +- (void)fat_requestAppletLocationAuthorize:(FATAppletInfo *)appletInfo isBackground:(BOOL)isBackground withComplete:(void (^)(BOOL status))complete; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_locationAuthManager.m b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_locationAuthManager.m new file mode 100644 index 0000000..05c49a2 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_locationAuthManager.m @@ -0,0 +1,136 @@ +// +// FATExt_locationAuthManager.m +// FinAppletExt +// +// Created by 王兆耀 on 2022/12/24. +// + +#import "FATExt_locationAuthManager.h" +#import + + +@interface FATExt_locationAuthManager () + +@property (nonatomic, strong) CLLocationManager *locationManager; + +@property (nonatomic, strong) NSMutableArray *locationAuthCompleteArray; + +@property (nonatomic, strong) NSMutableArray *appletInfoArray; +@property (nonatomic, strong) NSMutableDictionary *authTypeDic; //key是小程序Id value是权限类型,用来区分后台定位和正常位置权限 +@end + +static FATExt_locationAuthManager *instance = nil; + +@implementation FATExt_locationAuthManager + ++ (instancetype)shareInstance { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[FATExt_locationAuthManager alloc] init]; + }); + + return instance; +} + ++ (instancetype)allocWithZone:(struct _NSZone *)zone { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [super allocWithZone:zone]; + }); + + return instance; +} + +- (instancetype)init { + self = [super init]; + if (self) { + [self p_init]; + } + return self; +} + +- (void)p_init { + _locationAuthCompleteArray = [NSMutableArray array]; + _appletInfoArray = [[NSMutableArray alloc]init]; + _authTypeDic = [[NSMutableDictionary alloc]init]; +} + +- (void)fat_requestAppletLocationAuthorize:(FATAppletInfo *)appletInfo isBackground:(BOOL)isBackground withComplete:(void (^)(BOOL status))complete { + CLAuthorizationStatus status = [CLLocationManager authorizationStatus]; + if (status == kCLAuthorizationStatusAuthorizedWhenInUse || status == kCLAuthorizationStatusAuthorizedAlways) { + [self notifyApp:appletInfo authType:isBackground ? FATAuthorizationTypeLocationBackground : FATAuthorizationTypeLocation authResult:FATAuthorizationStatusAuthorized]; + if (complete) { + complete(YES); + } + return; + } + + if (status != kCLAuthorizationStatusNotDetermined) { + [self notifyApp:appletInfo authType:isBackground ? FATAuthorizationTypeLocationBackground : FATAuthorizationTypeLocation authResult:FATAuthorizationStatusDenied]; + if (complete) { + complete(NO); + } + return; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + [self.locationAuthCompleteArray addObject:complete]; + if (appletInfo) { + [self.appletInfoArray addObject:appletInfo]; + if (appletInfo.appId) { + [self.authTypeDic setValue:isBackground ? @(FATAuthorizationTypeLocationBackground) : @(FATAuthorizationTypeLocation) forKey:appletInfo.appId]; + } + } + self.locationManager = [[CLLocationManager alloc] init]; + self.locationManager.delegate = self; + [self.locationManager requestWhenInUseAuthorization]; + }); + return; +} + +- (void)notifyApp:(FATAppletInfo *)appletInfo authType:(FATAuthorizationType)type authResult:(FATAuthorizationStatus)result { + id delegate = [FATClient sharedClient].authDelegate; + if (delegate && [delegate respondsToSelector:@selector(applet:didRequestAuth:withResult:)]) { + [delegate applet:appletInfo didRequestAuth:type withResult:result]; + } +} + +- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status { + + if (status == kCLAuthorizationStatusNotDetermined) { + return; + } + for (FATAppletInfo *appInfo in self.appletInfoArray) { + FATAuthorizationStatus authStatus = FATAuthorizationStatusDenied; + if (status == kCLAuthorizationStatusAuthorizedWhenInUse || status == kCLAuthorizationStatusAuthorizedAlways) { + authStatus = FATAuthorizationStatusAuthorized; + } + NSNumber *authtype = [self.authTypeDic objectForKey:appInfo.appId]; + FATAuthorizationType type = FATAuthorizationTypeLocation; + if (authtype) { + type = [authtype integerValue]; + } + [self notifyApp:appInfo authType:type authResult:authStatus]; + } + [self.appletInfoArray removeAllObjects]; + [self.authTypeDic removeAllObjects]; + if (status == kCLAuthorizationStatusAuthorizedWhenInUse || status == kCLAuthorizationStatusAuthorizedAlways) { + for (int i = 0; i < self.locationAuthCompleteArray.count; i++) { + void(^locationComplete)(BOOL status) = self.locationAuthCompleteArray[i]; + locationComplete(YES); + } + [self.locationAuthCompleteArray removeAllObjects]; + return; + } + + for (int i = 0; i < self.locationAuthCompleteArray.count; i++) { + void(^locationComplete)(BOOL status) = self.locationAuthCompleteArray[i]; + locationComplete(NO); + } + + [self.locationAuthCompleteArray removeAllObjects]; +} + + +@end + diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_locationChange.h b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_locationChange.h new file mode 100644 index 0000000..f978c55 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_locationChange.h @@ -0,0 +1,18 @@ +// +// FATExt_locationChange.h +// FinAppletExt +// +// Created by 王兆耀 on 2022/11/6. +// + +#import "FATExtBaseApi.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FATExt_locationChange : FATExtBaseApi + +@property (nonatomic, assign) BOOL enable; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_locationChange.m b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_locationChange.m new file mode 100644 index 0000000..35eead6 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_locationChange.m @@ -0,0 +1,30 @@ +// +// FATExt_locationChange.m +// FinAppletExt +// +// Created by 王兆耀 on 2022/11/6. +// + +#import "FATExt_locationChange.h" +#import "FATClient+ext.h" +#import "FATExt_LocationUpdateManager.h" + +@implementation FATExt_locationChange + +- (void)setupApiWithSuccess:(void (^)(NSDictionary *successResult))success + failure:(void (^)(NSDictionary *failResult))failure + cancel:(void (^)(NSDictionary *cancelResult))cancel { +// if (self.enable) { +// [FATExt_LocationUpdateManager sharedManager].context = self.context; +// [[FATExt_LocationUpdateManager sharedManager] onLocationUpdate]; +// } else { +// [FATExt_LocationUpdateManager sharedManager].context = self.context; +// [[FATExt_LocationUpdateManager sharedManager] stopLocationUpdate]; +// } + + if (success) { + success(@{}); + } +} + +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_openLocation.h b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_openLocation.h new file mode 100644 index 0000000..cd9fb9c --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_openLocation.h @@ -0,0 +1,27 @@ +// +// FATExt_openLocation.h +// FinAppletExt +// +// Created by 王兆耀 on 2021/12/7. +// + +#import "FATExtBaseApi.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FATExt_openLocation : FATExtBaseApi + +/// 目标地纬度 +@property (nonatomic, strong) NSString *latitude; +/// 目标地经度 +@property (nonatomic, strong) NSString *longitude; + +@property (nonatomic, strong) NSString *scale; + +@property (nonatomic, strong) NSString *name; + +@property (nonatomic, strong) NSString *address; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_openLocation.m b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_openLocation.m new file mode 100644 index 0000000..ef806da --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_openLocation.m @@ -0,0 +1,98 @@ +// +// FATExt_openLocation.m +// FinAppletExt +// +// Created by 王兆耀 on 2021/12/7. +// + +#import "FATExt_openLocation.h" +#import "FATWGS84ConvertToGCJ02.h" +#import "FATClient+ext.h" +#import "FATOpenLocationViewController.h" +#import "FATExtMapManager.h" +#import "FATExtNavigationController.h" +#import "FATExt_locationAuthManager.h" +#import + +@implementation FATExt_openLocation + +- (void)setupApiWithSuccess:(void (^)(NSDictionary *successResult))success + failure:(void (^)(NSDictionary *failResult))failure + cancel:(void (^)(NSDictionary *cancelResult))cancel { +// if (![CLLocationManager locationServicesEnabled]) { +// if (callback) { +// callback(FATExtensionCodeFailure, @{@"errMsg" : @"location service not open"}); +// } +// return; +// } + + NSString *mapClassStr = NSStringFromClass([FATExtMapManager shareInstance].mapClass); + NSString *apiName = nil; + if ([mapClassStr isEqualToString:@"FATBDMapView"]) { + apiName = @"FATBDExt_openLocation"; + } else if ([mapClassStr isEqualToString:@"FATGDMapView"]) { + apiName = @"FATGDExt_openLocation"; + } else if ([mapClassStr isEqualToString:@"FATTXMapView"]) { + apiName = @"FATTXExt_openLocation"; + } + if (apiName) { + id api = [self.class fat_apiWithApiClass:apiName params:self.param]; + if (api) { + api.appletInfo = self.appletInfo; + api.context = self.context; + [api setupApiWithSuccess:success failure:failure cancel:cancel]; + return; + } + } + + [[FATClient sharedClient] fat_requestAppletAuthorize:FATAuthorizationTypeLocation appletId:self.appletInfo.appId complete:^(NSInteger status) { + if (status == 0) { + //定位功能可用 + [[FATExt_locationAuthManager shareInstance] fat_requestAppletLocationAuthorize:self.appletInfo isBackground:NO withComplete:^(BOOL status) { + if (status) { + [self callChooseLocationWithCallback]; + if (success) { + success(@{}); + } + } else { + if (failure) { + failure(@{@"errMsg" : @"system permission denied"}); + } + } + }]; + } else if (status == 1) { + //定位不能用 + if (failure) { + failure(@{@"errMsg" : @"unauthorized,用户未授予位置权限"}); + } + } else { + if (failure) { + failure(@{@"errMsg" : @"unauthorized disableauthorized,SDK被禁止申请位置权限"}); + } + } + }]; +} + +- (void)callChooseLocationWithCallback { + // 弹出定位选择界面 + UIViewController *topVC = [[UIApplication sharedApplication] fat_topViewController]; + + FATOpenLocationViewController *mapVC = [[FATOpenLocationViewController alloc] init]; + mapVC.latitude = self.latitude; + mapVC.longitude = self.longitude; + mapVC.scale = self.scale; + mapVC.name = self.name ? self.name : @"[位置]"; + mapVC.address = self.address; + FATExtNavigationController *nav = [[FATExtNavigationController alloc] initWithRootViewController:mapVC]; + if (@available(iOS 15, *)) { + UINavigationBarAppearance *barAppearance = [[UINavigationBarAppearance alloc] init]; + [barAppearance configureWithOpaqueBackground]; + barAppearance.backgroundColor = [UIColor whiteColor]; + nav.navigationBar.standardAppearance = barAppearance; + nav.navigationBar.scrollEdgeAppearance = barAppearance; + } + + [topVC presentViewController:nav animated:YES completion:nil]; +} + +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_startLocationUpdate.h b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_startLocationUpdate.h new file mode 100644 index 0000000..dc09134 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_startLocationUpdate.h @@ -0,0 +1,18 @@ +// +// FATExt_startLocationUpdate.h +// FinAppletExt +// +// Created by 王兆耀 on 2022/11/3. +// + +#import "FATExtBaseApi.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FATExt_startLocationUpdate : FATExtBaseApi + +@property (nonatomic, copy) NSString *type; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_startLocationUpdate.m b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_startLocationUpdate.m new file mode 100644 index 0000000..cd61b11 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_startLocationUpdate.m @@ -0,0 +1,44 @@ +// +// FATExt_startLocationUpdate.m +// FinAppletExt +// +// Created by 王兆耀 on 2022/11/3. +// + +#import "FATExt_startLocationUpdate.h" +#import "FATClient+ext.h" +#import "FATExt_LocationUpdateManager.h" +#import "FATExt_locationAuthManager.h" + +@implementation FATExt_startLocationUpdate + +- (void)setupApiWithSuccess:(void (^)(NSDictionary *successResult))success + failure:(void (^)(NSDictionary *failResult))failure + cancel:(void (^)(NSDictionary *cancelResult))cancel { + + [[FATClient sharedClient] fat_requestAppletAuthorize:FATAuthorizationTypeLocation appletId:self.appletInfo.appId complete:^(NSInteger status) { + if (status == 0) { + [[FATExt_locationAuthManager shareInstance] fat_requestAppletLocationAuthorize:self.appletInfo isBackground:NO withComplete:^(BOOL status) { + if (status) { + [FATExt_LocationUpdateManager sharedManager].context = self.context; + [[FATExt_LocationUpdateManager sharedManager] startLocationUpdateType:self.type isAllowsBackgroundLocationUpdates:NO withAppId:self.appletInfo.appId Success:success failure:failure cancel:cancel]; + } else { + if (failure) { + failure(@{@"errMsg" : @"system permission denied"}); + } + } + }]; + } else if (status == 1) { + //定位不能用 + if (failure) { + failure(@{@"errMsg" : @"unauthorized,用户未授予位置权限"}); + } + } else { + if (failure) { + failure(@{@"errMsg" : @"unauthorized disableauthorized,SDK被禁止申请位置权限"}); + } + } + }]; +} + +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_startLocationUpdateBackground.h b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_startLocationUpdateBackground.h new file mode 100644 index 0000000..1bb97d4 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_startLocationUpdateBackground.h @@ -0,0 +1,18 @@ +// +// FATExt_startLocationUpdateBackground.h +// FinAppletExt +// +// Created by 王兆耀 on 2022/11/3. +// + +#import "FATExtBaseApi.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FATExt_startLocationUpdateBackground : FATExtBaseApi + +@property (nonatomic, copy) NSString *type; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_startLocationUpdateBackground.m b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_startLocationUpdateBackground.m new file mode 100644 index 0000000..4854a97 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_startLocationUpdateBackground.m @@ -0,0 +1,58 @@ +// +// FATExt_startLocationUpdateBackground.m +// FinAppletExt +// +// Created by 王兆耀 on 2022/11/3. +// + +#import "FATExt_startLocationUpdateBackground.h" +#import "FATClient+ext.h" +#import "FATExt_LocationUpdateManager.h" +#import "FATExt_locationAuthManager.h" + +@implementation FATExt_startLocationUpdateBackground + +- (void)setupApiWithSuccess:(void (^)(NSDictionary *successResult))success + failure:(void (^)(NSDictionary *failResult))failure + cancel:(void (^)(NSDictionary *cancelResult))cancel { + if ([FATExt_LocationUpdateManager sharedManager].locationIsInit && [FATExt_LocationUpdateManager sharedManager].locationManager.allowsBackgroundLocationUpdates && ![self.appletInfo.appId isEqualToString:[FATExt_LocationUpdateManager sharedManager].appletId]) { + if (failure) { + failure(@{@"errMsg" : @"reach max concurrent background count"}); + } + return; + } + [[FATClient sharedClient] fat_requestAppletAuthorize:FATAuthorizationTypeLocationBackground appletId:self.appletInfo.appId complete:^(NSInteger status) { + NSInteger SDKSatus = status; + if (status == 3) { + //定位功能可用 + [[FATExt_locationAuthManager shareInstance] fat_requestAppletLocationAuthorize:self.appletInfo isBackground:YES withComplete:^(BOOL status) { + if (status) { + [FATExt_LocationUpdateManager sharedManager].context = self.context; + [[FATExt_LocationUpdateManager sharedManager] startLocationUpdateType:self.type isAllowsBackgroundLocationUpdates:SDKSatus == 3 ? YES : NO withAppId:self.appletInfo.appId Success:success failure:failure cancel:cancel]; + } else { + if (failure) { + failure(@{@"errMsg" : @"system permission denied"}); + } + } + }]; + } else if (status == 2) { +// [[FATExt_LocationUpdateManager sharedManager] stopLocationUpdate]; + if (failure) { + failure(@{@"errMsg" : @"unauthorized,用户未授予位置权限"}); + } + } else if (status == 1) { +// [[FATExt_LocationUpdateManager sharedManager] stopLocationUpdate]; + if (failure) { + failure(@{@"errMsg" : @"unauthorized,用户未授予后台定位权限"}); + } + } else { +// [[FATExt_LocationUpdateManager sharedManager] stopLocationUpdate]; + if (failure) { + failure(@{@"errMsg" : @"unauthorized disableauthorized,SDK被禁止申请位置权限"}); + } + } + }]; +} + + +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_stopLocationUpdate.h b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_stopLocationUpdate.h new file mode 100644 index 0000000..59eba1e --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_stopLocationUpdate.h @@ -0,0 +1,16 @@ +// +// FATExt_stopLocationUpdate.h +// FinAppletExt +// +// Created by 王兆耀 on 2022/11/18. +// + +#import "FATExtBaseApi.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FATExt_stopLocationUpdate : FATExtBaseApi + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_stopLocationUpdate.m b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_stopLocationUpdate.m new file mode 100644 index 0000000..2dc0c3c --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Location/FATExt_stopLocationUpdate.m @@ -0,0 +1,30 @@ +// +// FATExt_stopLocationUpdate.m +// FinAppletExt +// +// Created by 王兆耀 on 2022/11/18. +// + +#import "FATExt_stopLocationUpdate.h" +#import "FATClient+ext.h" +#import "FATExt_LocationUpdateManager.h" + +@implementation FATExt_stopLocationUpdate + +- (void)setupApiWithSuccess:(void (^)(NSDictionary *successResult))success + failure:(void (^)(NSDictionary *failResult))failure + cancel:(void (^)(NSDictionary *cancelResult))cancel { + if (![self.appletInfo.appId isEqualToString:[FATExt_LocationUpdateManager sharedManager].appletId]) { + if (success) { + success(@{}); + } + return; + } + [FATExt_LocationUpdateManager sharedManager].context = self.context; + [[FATExt_LocationUpdateManager sharedManager] stopLocationUpdate]; + if (success) { + success(@{}); + } +} + +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_insertNativeMap.h b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_insertNativeMap.h new file mode 100644 index 0000000..529de22 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_insertNativeMap.h @@ -0,0 +1,17 @@ +// +// FATExt_insertNativeMap.h +// FinAppletExt +// +// Created by 滔 on 2022/9/18. +// Copyright © 2022 finogeeks. All rights reserved. +// + +#import "FATExtBaseApi.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FATExt_insertNativeMap : FATExtBaseApi + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_insertNativeMap.m b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_insertNativeMap.m new file mode 100644 index 0000000..2009fb0 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_insertNativeMap.m @@ -0,0 +1,50 @@ +// +// FATExt_insertNativeMap.m +// FinAppletExt +// +// Created by 滔 on 2022/9/18. +// Copyright © 2022 finogeeks. All rights reserved. +// + +#import "FATExt_insertNativeMap.h" +#import "FATMapViewDelegate.h" +#import "FATMapView.h" +#import "FATExtMapManager.h" + +@implementation FATExt_insertNativeMap + +- (void)setupApiWithSuccess:(void (^)(NSDictionary *successResult))success + failure:(void (^)(NSDictionary *failResult))failure + cancel:(void (^)(NSDictionary *cancelResult))cancel { + UIView *mapObject = [[FATExtMapManager shareInstance].mapClass alloc]; + UIView *mapView = [mapObject initWithParam:self.param mapPageId:self.appletInfo.appId]; + mapView.eventCallBack = ^(NSString *eventName, NSDictionary *paramDic) { + if (self.context) { + [self.context sendResultEvent:1 eventName:eventName eventParams:paramDic extParams:nil]; + } + }; + if (self.context && [self.context respondsToSelector:@selector(insertChildView:viewId:parentViewId:isFixed:isHidden:)]) { + NSString *parentId = self.param[@"cid"]; + if (parentId && parentId.length > 0) { //同层渲染 + CGRect frame = mapView.frame; + frame.origin = CGPointZero; + mapView.frame = frame; + } + BOOL isHidden = [self.param[@"hide"] boolValue]; + NSString *viewId = [self.context insertChildView:mapView viewId:self.param[@"mapId"] parentViewId:self.param[@"cid"] isFixed:NO isHidden:isHidden]; + if (viewId && viewId.length > 0) { + if (success) { + success(@{}); + } + } else { + if (failure) { + failure(@{}); + } + } + } else { + if (failure) { + failure(@{}); + } + } +} +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_invokeMapTask.h b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_invokeMapTask.h new file mode 100644 index 0000000..d1cacdb --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_invokeMapTask.h @@ -0,0 +1,26 @@ +// +// FATExt_invokeMapTask.h +// FBRetainCycleDetector +// +// Created by 王兆耀 on 2021/9/2. +// + +#import "FATExtBaseApi.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FATExt_invokeMapTask : FATExtBaseApi + +@property (nonatomic, copy) NSString *eventName; + +@property (nonatomic, copy) NSString *mapId; + +@property (nonatomic, copy) NSString *key; + +@property (nonatomic, copy) NSDictionary *data; + +@property (nonatomic, assign) NSInteger nativeViewId; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_invokeMapTask.m b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_invokeMapTask.m new file mode 100644 index 0000000..9f7c468 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_invokeMapTask.m @@ -0,0 +1,169 @@ +// +// FATExt_invokeMapTask.m +// FBRetainCycleDetector +// +// Created by 王兆耀 on 2021/9/2. +// + +#import "FATExt_invokeMapTask.h" +#import "FATMapView.h" +#import "FATExtMapManager.h" +#import "FATExtUtil.h" + +@implementation FATExt_invokeMapTask + +- (void)setupApiWithSuccess:(void (^)(NSDictionary *successResult))success + failure:(void (^)(NSDictionary *failResult))failure + cancel:(void (^)(NSDictionary *cancelResult))cancel { + UIView *map = nil; + if (self.context && [self.context respondsToSelector:@selector(getChildViewById:)]) { + UIView *targetView = [self.context getChildViewById:self.param[@"mapId"]]; + if (targetView && [targetView conformsToProtocol:@protocol(FATMapViewDelegate)]) { + map = (UIView *)targetView; + } + } + if (!map) { + if (failure) { + failure(@{@"errMsg" : @"map view not exist"}); + } + return; + } + if ([self.eventName isEqualToString:@"getCenterLocation"]) { + NSMutableDictionary *dic = [[NSMutableDictionary alloc] initWithDictionary:[map fat_getCenter]]; + if (success) { + success(dic); + } + } else if ([self.eventName isEqualToString:@"getRegion"]) { + NSDictionary *dic = [map fat_mapgetRegion]; + if (success) { + success(dic); + } + } else if ([self.eventName isEqualToString:@"getScale"]) { + double scale = [map fat_getScale]; + if (success) { + success(@{@"scale" : [NSNumber numberWithDouble:scale]}); + } + } else if ([self.eventName isEqualToString:@"includePoints"]) { + [map fat_includePoints:self.data]; + if (success) { + success(@{}); + } + } else if ([self.eventName isEqualToString:@"moveToLocation"]) { + NSString *status = [map fat_moveToLocation:self.data]; + if ([status isEqualToString:@"fail"]) { + if (failure) { + failure(@{@"errMsg" : @"not show user location"}); + } + } else { + if (success) { + success(@{}); + } + } + } else if ([self.eventName isEqualToString:@"fromScreenLocation"]) { + NSDictionary *dic = [map fat_fromScreenLocation]; + if (success) { + success(dic); + } + } else if ([self.eventName isEqualToString:@"toScreenLocation"]) { + // 暂时有bug,微信端有问题。 + CGPoint point = [map fat_toScreenLocation:self.data]; + if (success) { + success(@{@"x" : @(point.x), + @"y" : @(point.y)}); + }; + } else if ([self.eventName isEqualToString:@"openMapApp"]) { + [map fat_openMapApp:self.data]; + if (success) { + success(@{}); + }; + } else if ([self.eventName isEqualToString:@"addMarkers"]) { + [map fat_addMarkers:self.data]; + if (success) { + success(@{}); + }; + } else if ([self.eventName isEqualToString:@"removeMarkers"]) { + [map fat_removeMarkers:self.data]; + if (success) { + success(@{}); + }; + } else if ([self.eventName isEqualToString:@"translateMarker"]) { + BOOL isExit = [map fat_translateMarker:self.data]; + if (isExit) { + if (success) { + success(@{}); + }; + } else { + if (failure) { + failure(@{@"errMsg" : @"error markerid"}); + } + } + } else if ([self.eventName isEqualToString:@"moveAlong"]) { + BOOL isExit = [map fat_moveAlong:self.data]; + if (isExit) { + if (success) { + success(@{}); + }; + } else { + NSArray *pathArray = [[NSArray alloc] initWithArray:self.data[@"path"]]; + if (pathArray.count == 0 || ![pathArray isKindOfClass:[NSArray class]]) { + if (failure) { + failure(@{@"errMsg" : @"parameter error: parameter.duration should be Number instead of Undefined;"}); + } + } else { + if (failure) { + failure(@{@"errMsg" : @"error markerid"}); + } + } + } + } else if ([self.eventName isEqualToString:@"setCenterOffset"]) { + if ([map respondsToSelector:@selector(mapSetCenterOffset:)]) { + [map mapSetCenterOffset:self.data]; + if (success) { + success(@{}); + }; + } else { + if (failure) { + failure(@{@"errMsg" : @"not support"}); + }; + } + } else if ([self.eventName isEqualToString:@"getRotate"]) { + if ([map respondsToSelector:@selector(fat_getRotate)]) { + NSDictionary *dic = [map fat_getRotate]; + if (success) { + success(dic); + } + } else { + if (failure) { + failure(@{@"errMsg" : @"not support"}); + }; + } + } else if ([self.eventName isEqualToString:@"getSkew"]) { + if ([map respondsToSelector:@selector(fat_getskew)]) { + NSDictionary *dic = [map fat_getskew]; + if (success) { + success(dic); + } + } else { + if (failure) { + failure(@{@"errMsg" : @"not support"}); + }; + } + } else if ([self.eventName isEqualToString:@"initMarkerCluster"]) { + if (failure) { + failure(@{@"errMsg" : @"not support"}); + }; + } else if ([self.eventName isEqualToString:@"setLocMarkerIcon"]) { + if ([map respondsToSelector:@selector(fat_setLocMarkerIcon:)]) { + [map fat_setLocMarkerIcon:self.data]; + if (success) { + success(@{}); + } + } else { + if (failure) { + failure(@{@"errMsg" : @"not support"}); + }; + } + } +} + +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_removeNativeMap.h b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_removeNativeMap.h new file mode 100644 index 0000000..0dd03d6 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_removeNativeMap.h @@ -0,0 +1,17 @@ +// +// FATExt_removeNativeMap.h +// FinAppletExt +// +// Created by 滔 on 2022/9/18. +// Copyright © 2022 finogeeks. All rights reserved. +// + +#import "FATExtBaseApi.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FATExt_removeNativeMap : FATExtBaseApi + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_removeNativeMap.m b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_removeNativeMap.m new file mode 100644 index 0000000..0be5861 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_removeNativeMap.m @@ -0,0 +1,32 @@ +// +// FATExt_removeNativeMap.m +// FinAppletExt +// +// Created by 滔 on 2022/9/18. +// Copyright © 2022 finogeeks. All rights reserved. +// + +#import "FATExt_removeNativeMap.h" + +@implementation FATExt_removeNativeMap +- (void)setupApiWithSuccess:(void (^)(NSDictionary *successResult))success + failure:(void (^)(NSDictionary *failResult))failure + cancel:(void (^)(NSDictionary *cancelResult))cancel { + if (self.context && [self.context respondsToSelector:@selector(removeChildView:)]) { + BOOL result = [self.context removeChildView:self.param[@"mapId"]]; + if (result) { + if (success) { + success(@{}); + } + } else { + if (failure) { + failure(@{}); + } + } + } else { + if (failure) { + failure(@{}); + } + } +} +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_updateNativeMap.h b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_updateNativeMap.h new file mode 100644 index 0000000..3b2c56c --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_updateNativeMap.h @@ -0,0 +1,17 @@ +// +// FATExt_updateNativeMap.h +// FinAppletExt +// +// Created by 滔 on 2022/9/18. +// Copyright © 2022 finogeeks. All rights reserved. +// + +#import "FATExtBaseApi.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FATExt_updateNativeMap : FATExtBaseApi + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_updateNativeMap.m b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_updateNativeMap.m new file mode 100644 index 0000000..6c49cbd --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_updateNativeMap.m @@ -0,0 +1,51 @@ +// +// FATExt_updateNativeMap.m +// FinAppletExt +// +// Created by 滔 on 2022/9/18. +// Copyright © 2022 finogeeks. All rights reserved. +// + +#import "FATExt_updateNativeMap.h" +#import "FATMapViewDelegate.h" + +@implementation FATExt_updateNativeMap + +- (void)setupApiWithSuccess:(void (^)(NSDictionary *successResult))success + failure:(void (^)(NSDictionary *failResult))failure + cancel:(void (^)(NSDictionary *cancelResult))cancel { + if (self.context && [self.context respondsToSelector:@selector(getChildViewById:)]) { + UIView *targetView = [self.context getChildViewById:self.param[@"mapId"]]; + if (targetView && [targetView conformsToProtocol:@protocol(FATMapViewDelegate)]) { + UIView *mapView = (UIView *)targetView; + if (mapView && [mapView respondsToSelector:@selector(updateWithParam:)]) { + //是否是更新hide属性 + if ([self.param objectForKey:@"hide"]) { + BOOL hide = [[self.param objectForKey:@"hide"] boolValue]; + if (hide) { + [targetView removeFromSuperview]; + } else { + if (self.context && [self.context respondsToSelector:@selector(updateChildViewHideProperty:viewId:parentViewId:isFixed:isHidden:complete:)]) { + [self.context updateChildViewHideProperty:targetView viewId:self.param[@"mapId"] parentViewId:self.param[@"cid"] isFixed:NO isHidden:hide complete:nil]; + } + } + } + [mapView updateWithParam:self.param]; + if (success) { + success(@{}); + } + } else { + if (failure) { + NSDictionary *dictParam = @{}; + failure(dictParam); + } + } + } + } else { + if (failure) { + NSDictionary *dictParam = @{}; + failure(dictParam); + } + } +} +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_updateNativeMapMarkers.h b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_updateNativeMapMarkers.h new file mode 100644 index 0000000..297bd47 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_updateNativeMapMarkers.h @@ -0,0 +1,17 @@ +// +// FATExt_updateNativeMapMarkers.h +// FinAppletExt +// +// Created by 滔 on 2022/9/18. +// Copyright © 2022 finogeeks. All rights reserved. +// + +#import "FATExtBaseApi.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FATExt_updateNativeMapMarkers : FATExtBaseApi + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_updateNativeMapMarkers.m b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_updateNativeMapMarkers.m new file mode 100644 index 0000000..22b4dd7 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATExt_updateNativeMapMarkers.m @@ -0,0 +1,39 @@ +// +// FATExt_updateNativeMapMarkers.m +// FinAppletExt +// +// Created by 滔 on 2022/9/18. +// Copyright © 2022 finogeeks. All rights reserved. +// + +#import "FATExt_updateNativeMapMarkers.h" +#import "FATMapViewDelegate.h" + +@implementation FATExt_updateNativeMapMarkers +- (void)setupApiWithSuccess:(void (^)(NSDictionary *successResult))success + failure:(void (^)(NSDictionary *failResult))failure + cancel:(void (^)(NSDictionary *cancelResult))cancel { + + if (self.context && [self.context respondsToSelector:@selector(getChildViewById:)]) { + UIView *targetView = [self.context getChildViewById:self.param[@"mapId"]]; + if (targetView && [targetView conformsToProtocol:@protocol(FATMapViewDelegate)]) { + UIView *mapView = (UIView *)targetView; + if (mapView && [mapView respondsToSelector:@selector(updateNativeMapMarkers:)]) { + [mapView updateNativeMapMarkers:self.param]; + } else { + if (failure) { + NSDictionary *dictParam = @{}; + failure(dictParam); + } + } + } + } else { + if (failure) { + NSDictionary *dictParam = @{}; + failure(dictParam); + } + } + + +} +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Map/FATMapView.h b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATMapView.h new file mode 100644 index 0000000..09b7db2 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATMapView.h @@ -0,0 +1,70 @@ +// +// FATMapView.h +// FBRetainCycleDetector +// +// Created by 王兆耀 on 2021/10/9. +// + +#import +#import "FATMapViewDelegate.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FATMapView : MKMapView + +/// 发送Subscribe事件给page层 +/// eventName 事件名 +/// resultDic 事件的参数 +@property (nonatomic, copy) void(^eventCallBack)(NSString *eventName, NSDictionary *paramDic); + +- (instancetype)initWithParam:(NSDictionary *)param mapPageId:(NSString *)pageId; + +- (void)updateWithParam:(NSDictionary *)param; + +/// 获取中心点的经纬度 +- (NSDictionary *)fat_getCenter; + +/// 获取当前地图的倾斜角 +- (NSDictionary *)fat_getskew; + +/// 计算缩放级别 +- (double)fat_getScale; + +/// 移动到指定位置 +/// @param data 要移动的经纬度 +- (NSString *)fat_moveToLocation:(NSDictionary *)data; + +/// 缩放地图,展示所有的经纬度 +/// @param data 对应的数据 +- (void)fat_includePoints:(NSDictionary *)data; + +/// 获取左下,右上角的经纬度信息 +- (NSDictionary *)fat_mapgetRegion; + +/// 获取屏幕上的点对应的经纬度,坐标原点为地图左上角。 +- (NSDictionary *)fat_fromScreenLocation; + +/// 获取经纬度对应的屏幕坐标,坐标原点为地图左上角 +/// @param data 包含经纬度信息 +- (CGPoint)fat_toScreenLocation:(NSDictionary *)data; + +/// 打开地图app进行导航 +- (void)fat_openMapApp:(NSDictionary *)data; + +/// 在地图上添加大头针 +- (void)fat_addMarkers:(NSDictionary *)data; + +/// 在地图上移除大头针 +- (void)fat_removeMarkers:(NSDictionary *)data; + +/// 在地图上平移大头针 +- (BOOL)fat_translateMarker:(NSDictionary *)data; + +/// 沿指定路径移动 marker +- (BOOL)fat_moveAlong:(NSDictionary *)data; + +//- (void)fat_setLocMarkerIcon:(NSDictionary *)data; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Map/FATMapView.m b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATMapView.m new file mode 100644 index 0000000..2f9bd8b --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Map/FATMapView.m @@ -0,0 +1,1026 @@ +// +// FATMapView.m +// FBRetainCycleDetector +// +// Created by 王兆耀 on 2021/9/1. +// + +#import "FATMapView.h" +#import "MKMarkerView.h" +#import "FATExtHelper.h" +#import "FATExtUtil.h" +#import "FATWGS84ConvertToGCJ02.h" +#import "FATExtLocationManager.h" + +#import +#import +#import + +@interface FATMapView () + +@property (nonatomic, copy) NSString *pageId; +@property (nonatomic, strong) FATExtLocationManager *locationManager; +@property (nonatomic, assign) double Delta; +@property (nonatomic, strong) NSMutableDictionary *centerDic; +@property (nonatomic, strong) NSMutableArray *markerArray; +@property (nonatomic, assign) CGPoint centerOffset; +@property (nonatomic, assign) CLLocationCoordinate2D defaluStartCoordinate; +@property (nonatomic, assign) CLLocationCoordinate2D locationCoordinate; +@property (nonatomic, assign) double maxScale; +@property (nonatomic, assign) double minScale; +@property (nonatomic, assign) double settingScale; +@property (nonatomic, copy) NSDictionary *paramDic;// 记录初始化的参数 +@property (nonatomic, strong) NSMutableArray *polylineArray; +@property (nonatomic, strong) NSMutableArray *circleArray; +@property (nonatomic, strong) NSMutableArray *polygonsArray; +@property (nonatomic, assign) NSUInteger polylineArrayCount; +@property (nonatomic, assign) NSUInteger circleArrayCount; +@property (nonatomic, assign) NSUInteger polygonsArrayCount; +@property (nonatomic, copy) NSString *markerIcon; +@property (nonatomic, assign) double latitudeAbnormal; // 记录纬度的异常值 +@property (nonatomic, assign) double longitudeAbnormal; + + +@end + +@implementation FATMapView + + +- (NSMutableArray *)markerArray { + if (!_markerArray) { + _markerArray = [[NSMutableArray alloc] init]; + } + return _markerArray; +} + +- (NSDictionary *)paramDic { + if (!_paramDic) { + _paramDic = [[NSDictionary alloc] init]; + } + return _paramDic; +} + +- (FATExtLocationManager *)locationManager +{ + if (_locationManager == nil) { + _locationManager = [[FATExtLocationManager alloc]init]; + _locationManager.delegate = self; + } + return _locationManager; +} + +- (instancetype)initWithParam:(NSDictionary *)param mapPageId:(NSString *)pageId { + self = [super init]; + self.paramDic = param; + NSDictionary *style = [param objectForKey:@"style"]; + if (style) { + self.frame = [self getMapFrame:param]; + } + self.delegate = self; + self.pageId = pageId; + self.maxScale = [param[@"maxScale"] doubleValue]; + self.minScale = [param[@"minScale"] doubleValue]; + self.showsCompass = NO; + self.showsScale = NO; + self.rotateEnabled = NO; + self.zoomEnabled = YES; + self.scrollEnabled = YES; + self.showsTraffic = NO; + self.showsPointsOfInterest = YES; + self.pitchEnabled = NO; + [self updateMap:param]; + if (@available(iOS 11.0, *)) { + [self registerClass:MKAnnotationView.class forAnnotationViewWithReuseIdentifier:@"markerView"]; + } + return self; +} + + +- (void)updateWithParam:(NSDictionary *)param { + NSDictionary *style = [param objectForKey:@"style"]; + if (style) { + self.frame = [self getMapFrame:param]; + } + [self updateMap:param]; +} + +- (CGRect)getMapFrame:(NSDictionary *)param { + CGRect frame = CGRectZero; + NSDictionary *style = [param objectForKey:@"style"]; + CGFloat x = [[style objectForKey:@"left"] floatValue]; + CGFloat y = [[style objectForKey:@"top"] floatValue]; + if ([self.paramDic.allKeys containsObject:@"cid"]) { + x = 0.0; + y= 0.0; + } + CGFloat height = [[style objectForKey:@"height"] floatValue]; + CGFloat width = [[style objectForKey:@"width"] floatValue]; + frame = CGRectMake(x, y, width, height); + return frame; +} + +- (void)updateMap:(NSDictionary *)param { + NSDictionary *dic; + NSDictionary *setting; + if ([param objectForKey:@"setting"]) { + setting = [[NSDictionary alloc] initWithDictionary:param[@"setting"]]; + } + if (setting.allKeys.count > 0 ) { + dic = param[@"setting"]; + } else { + dic = param; + } + if (dic[@"showCompass"]) { + if (@available(iOS 9.0, *)) { + self.showsCompass = [dic[@"showCompass"] boolValue]; + } else { + // Fallback on earlier versions + } + } + if (dic[@"showScale"]) { + if (@available(iOS 9.0, *)) { + self.showsScale = [dic[@"showScale"] boolValue]; + } else { + // Fallback on earlier versions + } + } + if (dic[@"enableRotate"]) {// 设置地图可旋转 + self.rotateEnabled = [dic[@"enableRotate"] boolValue]; + } + if (dic[@"showLocation"]) {//显示当前位置 + self.showsUserLocation = [dic[@"showLocation"] boolValue]; + if ([dic[@"showLocation"] boolValue]) { + [self startStandardUpdates]; + } + } + if (dic[@"enable3D"]) {//是否显示3D + self.pitchEnabled = [dic[@"enable3D"] boolValue]; + } + if (dic[@"enableTraffic"]) {//是否显示实时路况 + if (@available(iOS 9.0, *)) { + self.showsTraffic = [dic[@"enableTraffic"] boolValue]; + } else { + // Fallback on earlier versions + } + } + if (dic[@"enablePoi"]) {//是否显示POI + self.showsPointsOfInterest = [dic[@"enablePoi"] boolValue]; + } + if (dic[@"enableScroll"]) { + self.scrollEnabled = [dic[@"enableScroll"] boolValue]; + } + if (dic[@"enableBuilding"]) { + self.showsBuildings = [dic[@"enableBuilding"] boolValue]; + } + if (dic[@"enableSatellite"]) { + self.mapType = [dic[@"enableSatellite"] boolValue] ? MKMapTypeSatellite : MKMapTypeStandard; + } + // 关闭暗黑模式 + // if (@available(iOS 13.0, *)) { + // self.overrideUserInterfaceStyle = UIUserInterfaceStyleLight; + // } + if (param[@"polygons"]) { + [self setpolygonView:param[@"polygons"]]; + } + if (param[@"polyline"]) { + [self setPolylineView:param[@"polyline"]]; + } + if (param[@"circles"]) { + [self setMKCircle:param[@"circles"]]; + } + if (param[@"markers"]) { + [self fat_removeAllMarker]; + [self fat_addMarkers:param isUpdateEvent:YES]; + } + if (param[@"maxScale"]) { + self.maxScale = [param[@"maxScale"] doubleValue]; + [self changeScale]; + } + if (param[@"minScale"]) { + self.minScale = [param[@"minScale"] doubleValue]; + [self changeScale]; + } + if (dic[@"enableZoom"]) {// 设置地图可缩放 + self.zoomEnabled = [dic[@"enableZoom"] boolValue]; + } + + if (param[@"latitude"] && param[@"longitude"]) { + CLLocationCoordinate2D centerCoord = { [self judgeLatition:[param[@"latitude"] doubleValue]], [self judgeLongitude:[param[@"longitude"] doubleValue]] }; + [self setRegion:MKCoordinateRegionMake(centerCoord, MKCoordinateSpanMake(self.Delta, self.Delta)) animated:YES]; + self.centerDic = [[NSMutableDictionary alloc] initWithDictionary:@{@"longitude":@([param[@"longitude"] doubleValue]), @"latitude":@([param[@"latitude"] doubleValue])}]; + self.longitudeAbnormal = [param[@"longitude"] doubleValue]; + self.latitudeAbnormal = [param[@"latitude"] doubleValue]; + + } + double latitude = [self judgeLatition:[self.centerDic[@"latitude"] doubleValue]]; + double longitude = [self judgeLongitude:[self.centerDic[@"longitude"] doubleValue]]; + + if (param[@"latitude"] && !param[@"longitude"]) { + double latitudes = [self judgeLatition:[param[@"latitude"] doubleValue]]; + self.latitudeAbnormal = latitudes; + CLLocationCoordinate2D centerCoord = { latitudes, longitude }; + [self setRegion:MKCoordinateRegionMake(centerCoord, MKCoordinateSpanMake(self.Delta, self.Delta)) animated:YES]; + } + if (param[@"longitude"] && !param[@"latitude"]) { + double longitudes = [self judgeLongitude:[param[@"longitude"] doubleValue]]; + self.longitudeAbnormal = longitudes; + CLLocationCoordinate2D centerCoord = { latitude, longitudes }; + [self setRegion:MKCoordinateRegionMake(centerCoord, MKCoordinateSpanMake(self.Delta, self.Delta)) animated:YES]; + } + + if (param[@"scale"]) { + double scale = [param[@"scale"] doubleValue]; + if ([param[@"scale"] doubleValue] > self.maxScale) { + scale = self.maxScale; + } + if ([param[@"scale"] doubleValue] < self.minScale) { + scale = self.minScale; + } + double LongitudeDelta = [self fat_getLongitudeDelta:scale]; + self.Delta = LongitudeDelta; + self.settingScale = scale; + CLLocationCoordinate2D centerCoord = { latitude, longitude }; + [self setRegion:MKCoordinateRegionMake(centerCoord, MKCoordinateSpanMake(self.Delta, self.Delta)) animated:YES]; + } + if (param[@"includePoints"]) { + NSArray *arrary = [[NSArray alloc] initWithArray:param[@"includePoints"]]; + [self fat_includePoints:@{@"points":arrary}]; + } +} + + +- (void)startStandardUpdates{ + + if (![FATExtLocationManager locationServicesEnabled]) { + return; + } + + CLAuthorizationStatus status = [FATExtLocationManager authorizationStatus]; + if (status == kCLAuthorizationStatusAuthorizedWhenInUse || + status == kCLAuthorizationStatusAuthorizedAlways || + status == kCLAuthorizationStatusNotDetermined) { + //定位功能可用 + + self.locationManager.delegate = self; + self.locationManager.desiredAccuracy = kCLLocationAccuracyBest; + [self.locationManager requestWhenInUseAuthorization]; + [self.locationManager startUpdatingLocation]; + + } else if (status == kCLAuthorizationStatusDenied) { + } +} + + +- (void)setpolygonView:(NSArray *)data { + + data = [self checkArrayData:data]; + NSMutableArray *lineArray = [[NSMutableArray alloc] initWithArray:self.overlays]; + [lineArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { + if ([obj isKindOfClass:MKPolygon.class]) { + [self removeOverlay:obj]; + } + }]; + self.polygonsArrayCount = 0; + self.polygonsArray = [[NSMutableArray alloc] initWithArray:data]; + for (NSDictionary *dic in data) { + if ([dic[@"points"] isKindOfClass:NSArray.class]) { + NSArray *polygonsArray = [[NSArray alloc] initWithArray:dic[@"points"]]; + CLLocationCoordinate2D points[polygonsArray.count + 1]; + + for (int i = 0; i 0) { + //计算中心点 + CLLocationCoordinate2D centCoor; + centCoor.latitude = (CLLocationDegrees)((_maxLat+_minLat) * 0.5f); + centCoor.longitude = (CLLocationDegrees)((_maxLon+_minLon) * 0.5f); + MKCoordinateSpan span; + //计算地理位置的跨度 + span.latitudeDelta = _maxLat - _minLat; + span.longitudeDelta = _maxLon - _minLon; + //得出数据的坐标区域 + MKCoordinateRegion region = MKCoordinateRegionMake(centCoor, span); + [self setRegion:region]; + self.latitudeAbnormal = region.center.latitude; + self.longitudeAbnormal = region.center.longitude; + } + } +} + +- (NSDictionary *)fat_mapgetRegion { + CGPoint southWestPoint = CGPointMake(self.frame.origin.x, self.frame.size.height); + CGPoint northEastPoint = CGPointMake(self.frame.size.width, self.frame.origin.x); + // 将屏幕坐标转换为经纬度 + CLLocationCoordinate2D southWestCpprdonate = [self convertPoint:southWestPoint toCoordinateFromView:self]; + CLLocationCoordinate2D northEastCpprdonate = [self convertPoint:northEastPoint toCoordinateFromView:self]; + // NSLog(@"西南角经纬度=%@,东北角经纬度=%@",southWestCpprdonate,northEastCpprdonate); + NSDictionary *dic = @{@"southwest":@{@"longitude":@(southWestCpprdonate.longitude), + @"latitude":@(southWestCpprdonate.latitude)}, + @"northeast":@{@"longitude":@(northEastCpprdonate.longitude), + @"latitude":@(northEastCpprdonate.latitude)} + }; + return dic; +} + +- (NSDictionary *)fat_fromScreenLocation { + CGPoint startingCpprdonatePoint = CGPointMake(self.frame.origin.x, self.frame.origin.x); + // 将屏幕坐标转换为经纬度 + CLLocationCoordinate2D startingCpprdonate = [self convertPoint:startingCpprdonatePoint toCoordinateFromView:self]; + // NSLog(@"西南角经纬度=%@,东北角经纬度=%@",southWestCpprdonate,northEastCpprdonate); + NSDictionary *dic = @{@"longitude":@(startingCpprdonate.longitude), + @"latitude":@(startingCpprdonate.latitude)}; + return dic; +} + +- (CGPoint)fat_toScreenLocation:(NSDictionary *)data { + CLLocationCoordinate2D centerCoord = { [data[@"latitude"] doubleValue], [data[@"longitude"] doubleValue] }; + CGPoint point = [self convertCoordinate:centerCoord toPointToView:self]; + return point; +} + +- (void)fat_openMapApp:(NSDictionary *)data { + + NSString *appName = [FATExtUtil getAppName]; + NSString *title = [NSString stringWithFormat:@"%@%@", [[FATClient sharedClient] fat_localizedStringForKey:@"Navigate to"], data[@"destination"]]; + CLLocationCoordinate2D coordinate = {[data[@"latitude"] doubleValue], [data[@"longitude"] doubleValue]}; + + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title + message:nil + preferredStyle:UIAlertControllerStyleActionSheet]; + UIAlertAction *appleAction = [UIAlertAction actionWithTitle:[[FATClient sharedClient] fat_localizedStringForKey:@"Apple Maps"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *_Nonnull action) { + CLLocationCoordinate2D loc = CLLocationCoordinate2DMake(coordinate.latitude, coordinate.longitude); + MKMapItem *currentLocation = [MKMapItem mapItemForCurrentLocation]; + MKMapItem *toLocation = [[MKMapItem alloc] initWithPlacemark:[[MKPlacemark alloc] initWithCoordinate:loc addressDictionary:nil]]; + [MKMapItem openMapsWithItems:@[ currentLocation, toLocation ] + launchOptions:@{MKLaunchOptionsDirectionsModeKey : MKLaunchOptionsDirectionsModeDriving, + MKLaunchOptionsShowsTrafficKey : [NSNumber numberWithBool:YES]}]; + }]; + [appleAction setValue:[self labelColor] forKey:@"titleTextColor"]; + UIAlertAction *bdAction = [UIAlertAction actionWithTitle:[[FATClient sharedClient] fat_localizedStringForKey:@"Baidu Maps"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *_Nonnull action) { + NSString *urlString = [[NSString stringWithFormat:@"baidumap://map/direction?origin={{我的位置}}&destination=latlng:%f,%f|name=目的地&mode=driving&coord_type=gcj02", coordinate.latitude, coordinate.longitude] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString]]; + }]; + [bdAction setValue:[self labelColor] forKey:@"titleTextColor"]; + UIAlertAction *gdAction = [UIAlertAction actionWithTitle:[[FATClient sharedClient] fat_localizedStringForKey:@"Amap"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *_Nonnull action) { + NSString *urlString = [[NSString stringWithFormat:@"iosamap://path?sourceApplication=%@&backScheme=%@&dlat=%f&dlon=%f&dev=0&t=0&dname=%@", appName, @"iosamap://", coordinate.latitude, coordinate.longitude, data[@"destination"]] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString]]; + }]; + [gdAction setValue:[self labelColor] forKey:@"titleTextColor"]; + UIAlertAction *googleAction = [UIAlertAction actionWithTitle:[[FATClient sharedClient] fat_localizedStringForKey:@"Google Maps"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *_Nonnull action) { + NSString *urlString = [[NSString stringWithFormat:@"comgooglemaps://?x-source=%@&x-success=%@&saddr=&daddr=%f,%f&directionsmode=driving", appName, @"comgooglemaps://", coordinate.latitude, coordinate.longitude] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString]]; + }]; + [googleAction setValue:[self labelColor] forKey:@"titleTextColor"]; + UIAlertAction *tencentAction = [UIAlertAction actionWithTitle:[[FATClient sharedClient] fat_localizedStringForKey:@"Tencent Maps"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *_Nonnull action) { + NSString *urlString = [[NSString stringWithFormat:@"qqmap://map/routeplan?from=我的位置&type=drive&to=%@&tocoord=%f,%f&coord_type=1&referer={ios.blackfish.XHY}",data[@"destination"],coordinate.latitude,coordinate.longitude] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString]]; + }]; + [tencentAction setValue:[self labelColor] forKey:@"titleTextColor"]; + NSString *cancel = [[FATClient sharedClient] fat_localizedStringForKey:@"Cancel"]; + UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:cancel style:UIAlertActionStyleCancel handler:^(UIAlertAction *_Nonnull action){ + // [alertController ] + }]; + [cancelAction setValue:[self labelColor] forKey:@"titleTextColor"]; + // 1.先检测有没有对应的app,有的话再加入 + [alertController addAction:appleAction]; + if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"baidumap://"]]) { + [alertController addAction:bdAction]; + } + if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"iosamap://"]]) { + [alertController addAction:gdAction]; + } + if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"comgooglemaps://"]]) { + [alertController addAction:googleAction]; + } + if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"qqmap://"]]){ + [alertController addAction:tencentAction]; + } + [alertController addAction:cancelAction]; + UIViewController *topVC = [[UIApplication sharedApplication] fat_topViewController]; + [topVC presentViewController:alertController animated:YES completion:nil]; +} + +- (void)fat_addMarkers:(NSDictionary *)data { + [self fat_addMarkers:data isUpdateEvent:NO]; +} + +// 如果是update事件里调用的话,参数为空需要移除 +- (void)fat_addMarkers:(NSDictionary *)data isUpdateEvent:(BOOL)isUpdate { + NSArray *markerArray = data[@"markers"]; + if (isUpdate && markerArray.count == 0) { + [self fat_removeAllMarker]; + return; + } + if (data[@"clear"] && [data[@"clear"] boolValue]) { + [self fat_removeAllMarker]; + } + // 添加大头针 + [self addMarker:markerArray]; +} + +-(void)fat_removeAllMarker { + [self.markerArray removeAllObjects]; + NSArray *array = self.annotations; + dispatch_async(dispatch_get_main_queue(), ^{ + [self removeAnnotations:array]; + }); +} + +- (void)addMarker:(NSArray *)array { + for (NSDictionary *dic in array) { + CLLocationCoordinate2D centerCoord = { [dic[@"latitude"] doubleValue], [dic[@"longitude"] doubleValue] }; + MKMarker *marker = [[MKMarker alloc] init]; + marker.idString = [NSString stringWithFormat:@"%@", dic[@"id"]]; + if (dic[@"iconPath"]) { + NSString *filePath; + if (![dic[@"iconPath"] containsString:@"http"]) { + filePath= [[FATClient sharedClient] getFileAddressWithfileName:dic[@"iconPath"]]; + } else { + filePath = dic[@"iconPath"]; + } + marker.image = [UIImage fat_getImageWithUrl:filePath]; + } else { + marker.image = [FATExtHelper fat_ext_imageFromBundleWithName:@"fav_fileicon_loc90"]; + } + marker.coordinate = centerCoord; + [self.markerArray addObject:marker]; + marker.title = dic[@"label"][@"content"]; + marker.subtitle = dic[@"callout"][@"content"]; + } + dispatch_async(dispatch_get_main_queue(), ^{ + [self addAnnotations:self.markerArray]; + }); +} + +- (void)fat_removeMarkers:(NSDictionary *)data { + if ([data.allKeys containsObject:@"markerIds"]) { + NSArray *dataArray = [[NSArray alloc] initWithArray:data[@"markerIds"]]; + NSMutableArray *deleteArray = [[NSMutableArray alloc] init]; + [dataArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + NSString *idNumber = [NSString stringWithFormat:@"%@",obj]; + for (MKMarker *marker in self.markerArray) { + if (idNumber == marker.idString) { + [deleteArray addObject:marker]; + } + } + }]; + + [deleteArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + MKMarker *marker = obj; + [self.markerArray removeObject:marker]; + }]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self removeAnnotations:deleteArray]; + }); + } +} + +- (BOOL)fat_translateMarker:(NSDictionary *)data { + NSString *idNumber = [NSString stringWithFormat:@"%@",data[@"markerId"]]; + BOOL markerIdIsExit = NO; + for (MKMarker *marker in self.markerArray) { + if (idNumber == marker.idString) { + markerIdIsExit = YES; + CLLocationCoordinate2D centerCoord = { [data[@"destination"][@"latitude"] doubleValue], [data[@"destination"][@"longitude"] doubleValue] }; + NSInteger duration = ([data[@"duration"] integerValue] ? [data[@"duration"] integerValue] : 1000) / 1000; + // 1.把坐标点转化为frame + NSValue *value2 = [NSValue valueWithMKCoordinate:centerCoord]; + __block NSArray *array = @[value2]; + [self newStartMoving:marker pointArray:array duration:duration]; + } + } + return markerIdIsExit; + +} + +- (void)newStartMoving:(MKMarker *)marker pointArray:(NSArray *)array duration:(NSInteger)duration { + + __block NSInteger number = 0; + + [UIView animateWithDuration:duration animations:^{ + NSValue *value = array[number]; + CLLocationCoordinate2D coord = [value MKCoordinateValue]; + marker.coordinate = coord; + number++; + } completion:^(BOOL finished) { + if (index < array.count-1) { + [self newStartMoving:marker pointArray:array duration:duration]; + } + }]; +} + +- (BOOL)fat_moveAlong:(NSDictionary *)data { + [self.layer removeAllAnimations]; + NSArray *pathArray = [[NSArray alloc] initWithArray:data[@"path"]]; + if (pathArray.count == 0 || ![pathArray isKindOfClass:[NSArray class]]) { + return NO; + } + NSString *idNumber = [NSString stringWithFormat:@"%@", data[@"markerId"]]; + NSInteger duration = ([data[@"duration"] integerValue] ? [data[@"duration"] integerValue] : 1000) / 1000; + BOOL markerIdIsExit = NO; + for (MKMarker *marker in self.markerArray) { + if (idNumber == marker.idString) { + markerIdIsExit = YES; + [self moveAlong:marker pathArray:pathArray duration:duration Count:0]; + } + } + return markerIdIsExit; +} + +- (void)moveAlong:(MKMarker *)marker pathArray:(NSArray *)array duration:(NSInteger)duration Count:(NSInteger)count { + __weak typeof(self) weakSelf = self; + CGFloat time =(CGFloat) duration/array.count; + [UIView animateWithDuration:time animations:^{ + NSDictionary *dic = array[count]; + CLLocationCoordinate2D centerCoord = {[dic[@"latitude"] doubleValue], [dic[@"longitude"] doubleValue]}; + CLLocationCoordinate2D coord = centerCoord; + marker.coordinate = coord; + } completion:^(BOOL finished) { + if (count < array.count - 1) { +// __weak id weakSelf = self; + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [weakSelf moveAlong:marker pathArray:array duration:duration Count:count + 1]; + }]; + } + }]; +} + +#pragma mark - MKMapViewDelegate +- (nullable MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id )annotation{ + + // If the annotation is the user location, just return nil.(如果是显示用户位置的Annotation,则使用默认的蓝色圆点) + if ([annotation isKindOfClass:[MKUserLocation class]]) { + return nil; + } + if ([annotation isKindOfClass:MKMarker.class]) { + MKMarker *marker = (MKMarker *)annotation; + MKAnnotationView *markerView = [mapView dequeueReusableAnnotationViewWithIdentifier:@"markerView"]; + markerView.canShowCallout = YES; + markerView.annotation = marker; + if (self.markerIcon) { + markerView.image = [UIImage fat_getImageWithUrl:self.markerIcon]; + } else { + markerView.image = marker.image; + } + markerView.centerOffset = CGPointMake(0, -12.5); + return markerView; + } + return nil; +} +/// 气泡的点击事件 +-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view{ + if ([view.annotation isKindOfClass:MKMarker.class]) { + MKMarker *marker = (MKMarker *)view.annotation; + NSDictionary *dic = @{@"mapId":self.paramDic[@"mapId"], + @"pageId":self.pageId, + @"eventName":@"markertap", + @"detail":@{@"markerId":marker.idString} + }; + if (self.eventCallBack) { + self.eventCallBack(@"custom_event_onMapTask",dic); + } + } +} + +/// 气泡的点击事件 +-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control{ +} + +- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views{ + for (MKAnnotationView *annotationView in views) { + [mapView deselectAnnotation:annotationView.annotation animated:YES]; + } +} + +- (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView { + if (self.defaluStartCoordinate.latitude > 0) { + return; + } + CGPoint southWestPoint = CGPointMake(self.frame.origin.x, self.frame.size.height); + // 将屏幕坐标转换为经纬度 + CLLocationCoordinate2D southWestCpprdonate = [self convertPoint:southWestPoint toCoordinateFromView:self]; + self.defaluStartCoordinate = southWestCpprdonate; + NSDictionary *dic = @{@"mapId":self.paramDic[@"mapId"], + @"pageId":self.pageId, + @"eventName":@"updated", + @"detail":@{} + }; + if (self.eventCallBack) { + self.eventCallBack(@"custom_event_onMapTask",dic); + } + +// FATAppletInfo *appInfo = [[FATClient sharedClient] currentApplet]; +// [[FATExtCoreEventManager shareInstance] sendComponentWithAppId:appInfo.appId eventName:@"custom_event_onMapTask" paramDict:dic]; +// +} + +//当拖拽,放大,缩小,双击手势开始时调用 +- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated { + + NSDictionary *skew = [self fat_getskew]; + double scale = log2(360 * self.frame.size.width / 256.0 / self.region.span.latitudeDelta); + NSDictionary *regionDic = [self fat_mapgetRegion]; + if (!self.centerDic) { + self.centerDic = [[NSMutableDictionary alloc] initWithDictionary:@{@"0":@"0"}]; + } + NSDictionary *dic = @{@"mapId":self.paramDic[@"mapId"], + @"pageId":self.pageId, + @"eventName":@"regionchange", + @"type":@"begin", + @"detail":@{@"rotate":@"", + @"skew":skew[@"skew"], + @"scale":[NSString stringWithFormat:@"%f", scale], + @"centerLocation":self.centerDic, + @"region":regionDic + } + }; + + if (self.eventCallBack) { + self.eventCallBack(@"custom_event_onMapTask",dic); + } + +// FATAppletInfo *appInfo = [[FATClient sharedClient] currentApplet]; +// [[FATExtCoreEventManager shareInstance] sendComponentWithAppId:appInfo.appId eventName:@"custom_event_onMapTask" paramDict:dic]; +} + + +#pragma mark-CLLocationManagerDelegate + +// 获取到的地图中心点坐标,传递给基础库 +- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated { + MKCoordinateRegion region; + CLLocationCoordinate2D centerCoordinate = mapView.region.center; + region.center = mapView.region.center; + //判断是不是属于国内范围 + if (![FATWGS84ConvertToGCJ02ForAMapView isLocationOutOfChina:centerCoordinate]) { + //转换后的coord + CLLocationCoordinate2D coord = [FATWGS84ConvertToGCJ02ForAMapView transformFromWGSToGCJ:centerCoordinate]; + region.center = coord; + } + + if (self.latitudeAbnormal == 0 || self.latitudeAbnormal == 85 || self.latitudeAbnormal == -85) { + [self.centerDic setValue:@(self.latitudeAbnormal) forKey:@"latitude"]; + } else { + [self.centerDic setValue:@(region.center.latitude) forKey:@"latitude"]; + } + if (self.longitudeAbnormal == 0 || self.longitudeAbnormal == 180 || self.longitudeAbnormal == -180) { + [self.centerDic setValue:@(self.longitudeAbnormal) forKey:@"longitude"]; + } else { + [self.centerDic setValue:@(region.center.longitude) forKey:@"longitude"]; + } + // self.centerDic = @{@"longitude":@(region.center.longitude),@"latitude":@(region.center.latitude)}; + + NSDictionary *skew = [self fat_getskew]; + double scale = log2(360 * self.frame.size.width / 256.0 / self.region.span.latitudeDelta); + NSDictionary *regionDic = [self fat_mapgetRegion]; + if (!self.centerDic) { + self.centerDic = [[NSMutableDictionary alloc] initWithDictionary:@{@"0":@"0"}]; + } + NSDictionary *dic = @{@"mapId":self.paramDic[@"mapId"], + @"eventName":@"regionchange", + @"pageId":self.pageId, + @"type" : @"end", + @"detail":@{@"rotate":@"", + @"skew":skew[@"skew"], + @"scale":[NSString stringWithFormat:@"%f", scale], + @"centerLocation":self.centerDic, + @"region":regionDic + } + }; + + if (self.eventCallBack) { + self.eventCallBack(@"custom_event_onMapTask",dic); + } + +// FATAppletInfo *appInfo = [[FATClient sharedClient] currentApplet]; +// [[FATExtCoreEventManager shareInstance] sendComponentWithAppId:appInfo.appId eventName:@"custom_event_onMapTask" paramDict:dic]; +} + +/** + * 更新到位置之后调用 + * + * @param manager 位置管理者 + * @param locations 位置数组 + */ +-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:( NSArray *)locations +{ + CLLocation *location = [locations firstObject]; + //位置更新后的经纬度 + CLLocationCoordinate2D theCoordinate = location.coordinate; + self.locationCoordinate = theCoordinate; + //设置地图显示的中心及范围 + // MKCoordinateRegion theRegion; + // theRegion.center = theCoordinate; + // NSLog(@" 经纬度 %f,%f",theCoordinate.latitude, theCoordinate.longitude); + // CLLocationCoordinate2D centerCoord = { theCoordinate.latitude, theCoordinate.longitude }; + // [self setRegion:MKCoordinateRegionMake(centerCoord, MKCoordinateSpanMake(self.Delta, self.Delta)) animated:YES]; + [self.locationManager stopUpdatingLocation]; +} + +/// 在地图上绘制对应的多边形,圆形,和路线 +- (MKOverlayView*)mapView:(MKMapView*)mapView viewForOverlay:(id)overlay { + if([overlay isKindOfClass:[MKPolygon class]]) { + NSMutableArray *array = self.polygonsArray; + NSDictionary *dic; + if (array.count > self.polygonsArrayCount) { + dic = array[self.polygonsArrayCount]; + } else { + dic = [array lastObject]; + } + MKPolygonView *polygonview = [[MKPolygonView alloc]initWithPolygon:(MKPolygon*)overlay]; + polygonview.fillColor = [UIColor fat_colorWithARGBHexString:dic[@"fillColor"]]; + polygonview.strokeColor = [UIColor fat_colorWithARGBHexString:dic[@"strokeColor"]]; + polygonview.lineWidth = [dic[@"strokeWidth"] floatValue]; + return polygonview; + } else if ([overlay isKindOfClass:[MKPolyline class]]) { + NSMutableArray *array = self.polylineArray; + NSDictionary *dic; + if (array.count > self.polylineArrayCount) { + dic = array[self.polylineArrayCount]; + } else { + dic = [array lastObject]; + } + MKPolylineView *lineview = [[MKPolylineView alloc]initWithOverlay:(MKPolyline*)overlay]; + lineview.lineCap = kCGLineCapRound; + lineview.strokeColor = [UIColor fat_colorWithARGBHexString:dic[@"color"] defaultHexString:@"#000000"]; + lineview.fillColor = [UIColor fat_colorWithARGBHexString:dic[@"borderColor"]]; + lineview.lineWidth = [dic[@"strokeWidth"] floatValue]; + lineview.layer.shouldRasterize = YES; + return lineview; + } else if ([overlay isKindOfClass:[MKCircle class]]) { + NSMutableArray *array = self.circleArray; + NSDictionary *dic; + if (array.count > self.circleArrayCount) { + dic = array[self.circleArrayCount]; + } else { + dic = [array lastObject]; + } + + MKCircleView *corcleView = [[MKCircleView alloc] initWithCircle:overlay] ; + corcleView.fillColor =  [UIColor fat_colorWithARGBHexString:dic[@"fillColor"]]; + corcleView.strokeColor = [UIColor fat_colorWithARGBHexString:dic[@"color"]]; + corcleView.lineWidth = [dic[@"strokeWidth"] floatValue]; + return corcleView; + } + return [MKOverlayView new]; +} + + +#pragma mark -- CLLocationManagerDelegate +- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading +{ + if (newHeading.headingAccuracy < 0) return; + +// CLLocationDirection heading = newHeading.trueHeading > 0 ? newHeading.trueHeading : newHeading.magneticHeading; +// CGFloat rotation = heading/180 * M_PI; + // self.arrowImageView.transform = CGAffineTransformMakeRotation(rotation); +} + +//- (void)tapPress:(UIGestureRecognizer *)gestureRecognizer { +// +// CGPoint touchPoint = [gestureRecognizer locationInView:self ]; +// CLLocationCoordinate2D touchMapCoordinate = +// [self convertPoint:touchPoint toCoordinateFromView:self]; +// //点击位置的经纬度 +// NSLog(@"%f %f",touchMapCoordinate.latitude, touchMapCoordinate.longitude); +// NSDictionary *dic = @{@"mapId":self.paramDic[@"mapId"], +// @"eventName":@"tap", +// @"detail":@{@"latitude":@(touchMapCoordinate.latitude), +// @"longitude":@(touchMapCoordinate.longitude) +// } +// }; +// FATAppletInfo *appInfo = [[FATClient sharedClient] currentApplet]; +// [[FATExtCoreEventManager shareInstance] sendComponentWithAppId:appInfo.appId eventName:@"custom_event_onMapTask" paramDict:dic]; +//} + + +#pragma mark -- tools method + +- (NSArray *)checkArrayData:(NSArray *)array { + // 清除数组中的异常数据 + NSMutableArray *dataArray = [[NSMutableArray alloc] initWithArray:array]; + [dataArray enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + if (![obj isKindOfClass:[NSDictionary class]]) { + [dataArray removeObject:obj]; + } + }]; + return dataArray; +} + +- (void)changeScale { + if (self.maxScale == self.minScale) { + self.zoomEnabled = NO; + } else { + if (self.paramDic[@"enableZoom"]) {// 设置地图可缩放 + self.zoomEnabled = [self.paramDic[@"enableZoom"] boolValue]; + } + } +} + +//两个经纬度之间的角度 +-(double)getBearingWithLat1:(double)lat1 whitLng1:(double)lng1 whitLat2:(double)lat2 whitLng2:(double)lng2{ + + double d = 0; + double radLat1 = [self radian:lat1]; + double radLat2 = [self radian:lat2]; + double radLng1 = [self radian:lng1]; + double radLng2 = [self radian:lng2]; + d = sin(radLat1)*sin(radLat2)+cos(radLat1)*cos(radLat2)*cos(radLng2-radLng1); + d = sqrt(1-d*d); + d = cos(radLat2)*sin(radLng2-radLng1)/d; + d = [self angle:asin(d)]; + return d; +} +//根据角度计算弧度 +-(double)radian:(double)d { + + return d * M_PI/180.0; +} +//根据弧度计算角度 +-(double)angle:(double)r { + + return r * 180/M_PI; +} +// 校验经度是否合规 +-(double)judgeLatition:(double)latitude { + if (latitude >= 90) { + latitude = 85.00; + } + if (latitude <= -90) { + latitude = -85.00; + } + return latitude; +} +// 校验纬度是否合规 +-(double)judgeLongitude:(double)longitude { + if (longitude >= 180) { + longitude = 180.00; + } + if (longitude <= -180) { + longitude = -180.00; + } + return longitude; +} + + +- (UIColor *)labelColor { + if (@available(iOS 12.0, *)) { + BOOL isDark = (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark); + return isDark ? [UIColor colorWithRed:208/255.0 green:208/255.0 blue:208/255.0 alpha:1/1.0] : [UIColor colorWithRed:34/255.0 green:34/255.0 blue:34/255.0 alpha:1/1.0]; + } else { + return [UIColor colorWithRed:34/255.0 green:34/255.0 blue:34/255.0 alpha:1/1.0]; + } +} + +@end diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Map/MKMarkerView.h b/ios/Classes/FinAppletExt/ExtensionApi/Map/MKMarkerView.h new file mode 100644 index 0000000..6148ab0 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Map/MKMarkerView.h @@ -0,0 +1,26 @@ +// +// MKMarkerView.h +// FBRetainCycleDetector +// +// Created by 王兆耀 on 2021/9/14. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///标记 +@interface MKMarker : NSObject +@property (nonatomic, copy) NSString *title; +@property (nonatomic, copy) NSString *subtitle; +@property (nonatomic, strong) UIImage *image; +@property (nonatomic, copy) NSString *idString; +@end + +@interface MKMarkerView : MKAnnotationView + ++ (instancetype)dequeueMarkerViewWithMap:(MKMapView *)mapView annotation:(id)annotation; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/ExtensionApi/Map/MKMarkerView.m b/ios/Classes/FinAppletExt/ExtensionApi/Map/MKMarkerView.m new file mode 100644 index 0000000..7df0d59 --- /dev/null +++ b/ios/Classes/FinAppletExt/ExtensionApi/Map/MKMarkerView.m @@ -0,0 +1,62 @@ +// +// MKMarkerView.h +// FBRetainCycleDetector +// +// Created by 王兆耀 on 2021/9/14. +// + +#import "MKMarkerView.h" + +@implementation MKMarker +@synthesize title; +@synthesize coordinate = _coordinate; + +- (void)setCoordinate:(CLLocationCoordinate2D)newCoordinate { + _coordinate = newCoordinate; +} + +- (CLLocationCoordinate2D)coordinate { + return _coordinate; +} + +@end + +@interface MKMarkerView () +@property (nonatomic, strong) UIImageView *imageView; +@end + +@implementation MKMarkerView + ++ (instancetype)dequeueMarkerViewWithMap:(MKMapView *)mapView annotation:(id)annotation { + if ([annotation isKindOfClass:MKMarker.class]) { + MKMarker *marker = (MKMarker *)annotation; + MKMarkerView *markerView = (MKMarkerView *)[mapView dequeueReusableAnnotationViewWithIdentifier:@"markerView"]; + if (markerView == nil) { + markerView = [[MKMarkerView alloc] initWithAnnotation:marker reuseIdentifier:@"markerView"]; + } + markerView.annotation = marker; + markerView.image = marker.image; + return markerView; + } + return nil; +} + +- (instancetype)initWithAnnotation:(id)annotation reuseIdentifier:(NSString *)reuseIdentifier { + self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]; + if (self) { + // self.calloutOffset = CGPointMake(0.5f, 0.97f); + self.canShowCallout = YES; //显示标题与描述 + // [self addSubview:self.imageView]; + } + return self; +} + +- (BOOL)isEnabled { + return YES; +} + +- (BOOL)isSelected { + return YES; +} + +@end diff --git a/ios/Classes/FinAppletExt/FinAppletExt.h b/ios/Classes/FinAppletExt/FinAppletExt.h new file mode 100644 index 0000000..f03ae5c --- /dev/null +++ b/ios/Classes/FinAppletExt/FinAppletExt.h @@ -0,0 +1,11 @@ +// +// FinAppletExtension.h +// FinAppletExtension +// +// Created by Haley on 2020/08/11. +// Copyright © 2019 finogeeks. All rights reserved. +// + +// In this header, you should import all the public headers of your framework using statements like #import + +#import "FATExtClient.h" diff --git a/ios/Classes/FinAppletExt/LICENSE b/ios/Classes/FinAppletExt/LICENSE new file mode 100644 index 0000000..5ee6919 --- /dev/null +++ b/ios/Classes/FinAppletExt/LICENSE @@ -0,0 +1,25 @@ + Copyright (c) 2017, weidian.com + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/default_win_location_normal_Normal@2x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/default_win_location_normal_Normal@2x.png new file mode 100644 index 0000000..7ad9d66 Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/default_win_location_normal_Normal@2x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/default_win_location_normal_Normal@3x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/default_win_location_normal_Normal@3x.png new file mode 100644 index 0000000..c061c9c Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/default_win_location_normal_Normal@3x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/fav_fileicon_loc90@3x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/fav_fileicon_loc90@3x.png new file mode 100644 index 0000000..aa5f4df Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/fav_fileicon_loc90@3x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_back_n@2x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_back_n@2x.png new file mode 100644 index 0000000..15e69a9 Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_back_n@2x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_back_n@3x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_back_n@3x.png new file mode 100644 index 0000000..7ac4ff3 Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_back_n@3x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_base@2x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_base@2x.png new file mode 100644 index 0000000..5312a7e Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_base@2x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_base@3x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_base@3x.png new file mode 100644 index 0000000..3418b81 Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_base@3x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_dn@2x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_dn@2x.png new file mode 100644 index 0000000..a85b6d1 Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_dn@2x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_dn@3x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_dn@3x.png new file mode 100644 index 0000000..f1393a1 Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_dn@3x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_dp@2x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_dp@2x.png new file mode 100644 index 0000000..1e32b6d Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_dp@2x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_dp@3x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_dp@3x.png new file mode 100644 index 0000000..7955b5a Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_dp@3x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_ln@2x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_ln@2x.png new file mode 100644 index 0000000..773d658 Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_ln@2x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_ln@3x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_ln@3x.png new file mode 100644 index 0000000..25e1752 Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_ln@3x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_lp@2x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_lp@2x.png new file mode 100644 index 0000000..cc09229 Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_lp@2x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_lp@3x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_lp@3x.png new file mode 100644 index 0000000..da16d83 Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_fold_lp@3x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_dn@2x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_dn@2x.png new file mode 100644 index 0000000..04243fb Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_dn@2x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_dn@3x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_dn@3x.png new file mode 100644 index 0000000..df0232a Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_dn@3x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_dp@2x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_dp@2x.png new file mode 100644 index 0000000..6364d58 Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_dp@2x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_dp@3x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_dp@3x.png new file mode 100644 index 0000000..6ef0bb0 Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_dp@3x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_ln@2x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_ln@2x.png new file mode 100644 index 0000000..32aa010 Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_ln@2x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_ln@3x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_ln@3x.png new file mode 100644 index 0000000..b28ddeb Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_ln@3x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_lp@2x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_lp@2x.png new file mode 100644 index 0000000..b4d218b Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_lp@2x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_lp@3x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_lp@3x.png new file mode 100644 index 0000000..37915e3 Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_location_lp@3x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_navigation_n@2x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_navigation_n@2x.png new file mode 100644 index 0000000..eab2b06 Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_navigation_n@2x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_navigation_n@3x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_navigation_n@3x.png new file mode 100644 index 0000000..3f81058 Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_navigation_n@3x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_navigation_p@2x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_navigation_p@2x.png new file mode 100644 index 0000000..ba3636b Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_navigation_p@2x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_navigation_p@3x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_navigation_p@3x.png new file mode 100644 index 0000000..5a18d10 Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_navigation_p@3x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_target@2x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_target@2x.png new file mode 100644 index 0000000..af13d9f Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_target@2x.png differ diff --git a/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_target@3x.png b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_target@3x.png new file mode 100644 index 0000000..c4dcfb9 Binary files /dev/null and b/ios/Classes/FinAppletExt/Resource/FinAppletExt.bundle/map_target@3x.png differ diff --git a/ios/Classes/FinAppletExt/Vendor/Lame/lame.h b/ios/Classes/FinAppletExt/Vendor/Lame/lame.h new file mode 100644 index 0000000..791d491 --- /dev/null +++ b/ios/Classes/FinAppletExt/Vendor/Lame/lame.h @@ -0,0 +1,1323 @@ +/* + * Interface to MP3 LAME encoding engine + * + * Copyright (c) 1999 Mark Taylor + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* $Id: lame.h,v 1.189.2.1 2012/01/08 23:49:58 robert Exp $ */ + +#ifndef LAME_LAME_H +#define LAME_LAME_H + +/* for size_t typedef */ +#include +/* for va_list typedef */ +#include +/* for FILE typedef, TODO: remove when removing lame_mp3_tags_fid */ +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef void (*lame_report_function)(const char *format, va_list ap); + +#if defined(WIN32) || defined(_WIN32) +#undef CDECL +#define CDECL __cdecl +#else +#define CDECL +#endif + +#define DEPRECATED_OR_OBSOLETE_CODE_REMOVED 1 + +typedef enum vbr_mode_e { + vbr_off=0, + vbr_mt, /* obsolete, same as vbr_mtrh */ + vbr_rh, + vbr_abr, + vbr_mtrh, + vbr_max_indicator, /* Don't use this! It's used for sanity checks. */ + vbr_default=vbr_mtrh /* change this to change the default VBR mode of LAME */ +} vbr_mode; + + +/* MPEG modes */ +typedef enum MPEG_mode_e { + STEREO = 0, + JOINT_STEREO, + DUAL_CHANNEL, /* LAME doesn't supports this! */ + MONO, + NOT_SET, + MAX_INDICATOR /* Don't use this! It's used for sanity checks. */ +} MPEG_mode; + +/* Padding types */ +typedef enum Padding_type_e { + PAD_NO = 0, + PAD_ALL, + PAD_ADJUST, + PAD_MAX_INDICATOR /* Don't use this! It's used for sanity checks. */ +} Padding_type; + + + +/*presets*/ +typedef enum preset_mode_e { + /*values from 8 to 320 should be reserved for abr bitrates*/ + /*for abr I'd suggest to directly use the targeted bitrate as a value*/ + ABR_8 = 8, + ABR_320 = 320, + + V9 = 410, /*Vx to match Lame and VBR_xx to match FhG*/ + VBR_10 = 410, + V8 = 420, + VBR_20 = 420, + V7 = 430, + VBR_30 = 430, + V6 = 440, + VBR_40 = 440, + V5 = 450, + VBR_50 = 450, + V4 = 460, + VBR_60 = 460, + V3 = 470, + VBR_70 = 470, + V2 = 480, + VBR_80 = 480, + V1 = 490, + VBR_90 = 490, + V0 = 500, + VBR_100 = 500, + + + + /*still there for compatibility*/ + R3MIX = 1000, + STANDARD = 1001, + EXTREME = 1002, + INSANE = 1003, + STANDARD_FAST = 1004, + EXTREME_FAST = 1005, + MEDIUM = 1006, + MEDIUM_FAST = 1007 +} preset_mode; + + +/*asm optimizations*/ +typedef enum asm_optimizations_e { + MMX = 1, + AMD_3DNOW = 2, + SSE = 3 +} asm_optimizations; + + +/* psychoacoustic model */ +typedef enum Psy_model_e { + PSY_GPSYCHO = 1, + PSY_NSPSYTUNE = 2 +} Psy_model; + + +/* buffer considerations */ +typedef enum buffer_constraint_e { + MDB_DEFAULT=0, + MDB_STRICT_ISO=1, + MDB_MAXIMUM=2 +} buffer_constraint; + + +struct lame_global_struct; +typedef struct lame_global_struct lame_global_flags; +typedef lame_global_flags *lame_t; + + + + +/*********************************************************************** + * + * The LAME API + * These functions should be called, in this order, for each + * MP3 file to be encoded. See the file "API" for more documentation + * + ***********************************************************************/ + + +/* + * REQUIRED: + * initialize the encoder. sets default for all encoder parameters, + * returns NULL if some malloc()'s failed + * otherwise returns pointer to structure needed for all future + * API calls. + */ +lame_global_flags * CDECL lame_init(void); +#if DEPRECATED_OR_OBSOLETE_CODE_REMOVED +#else +/* obsolete version */ +int CDECL lame_init_old(lame_global_flags *); +#endif + +/* + * OPTIONAL: + * set as needed to override defaults + */ + +/******************************************************************** + * input stream description + ***********************************************************************/ +/* number of samples. default = 2^32-1 */ +int CDECL lame_set_num_samples(lame_global_flags *, unsigned long); +unsigned long CDECL lame_get_num_samples(const lame_global_flags *); + +/* input sample rate in Hz. default = 44100hz */ +int CDECL lame_set_in_samplerate(lame_global_flags *, int); +int CDECL lame_get_in_samplerate(const lame_global_flags *); + +/* number of channels in input stream. default=2 */ +int CDECL lame_set_num_channels(lame_global_flags *, int); +int CDECL lame_get_num_channels(const lame_global_flags *); + +/* + scale the input by this amount before encoding. default=1 + (not used by decoding routines) +*/ +int CDECL lame_set_scale(lame_global_flags *, float); +float CDECL lame_get_scale(const lame_global_flags *); + +/* + scale the channel 0 (left) input by this amount before encoding. default=1 + (not used by decoding routines) +*/ +int CDECL lame_set_scale_left(lame_global_flags *, float); +float CDECL lame_get_scale_left(const lame_global_flags *); + +/* + scale the channel 1 (right) input by this amount before encoding. default=1 + (not used by decoding routines) +*/ +int CDECL lame_set_scale_right(lame_global_flags *, float); +float CDECL lame_get_scale_right(const lame_global_flags *); + +/* + output sample rate in Hz. default = 0, which means LAME picks best value + based on the amount of compression. MPEG only allows: + MPEG1 32, 44.1, 48khz + MPEG2 16, 22.05, 24 + MPEG2.5 8, 11.025, 12 + (not used by decoding routines) +*/ +int CDECL lame_set_out_samplerate(lame_global_flags *, int); +int CDECL lame_get_out_samplerate(const lame_global_flags *); + + +/******************************************************************** + * general control parameters + ***********************************************************************/ +/* 1=cause LAME to collect data for an MP3 frame analyzer. default=0 */ +int CDECL lame_set_analysis(lame_global_flags *, int); +int CDECL lame_get_analysis(const lame_global_flags *); + +/* + 1 = write a Xing VBR header frame. + default = 1 + this variable must have been added by a Hungarian notation Windows programmer :-) +*/ +int CDECL lame_set_bWriteVbrTag(lame_global_flags *, int); +int CDECL lame_get_bWriteVbrTag(const lame_global_flags *); + +/* 1=decode only. use lame/mpglib to convert mp3/ogg to wav. default=0 */ +int CDECL lame_set_decode_only(lame_global_flags *, int); +int CDECL lame_get_decode_only(const lame_global_flags *); + +#if DEPRECATED_OR_OBSOLETE_CODE_REMOVED +#else +/* 1=encode a Vorbis .ogg file. default=0 */ +/* DEPRECATED */ +int CDECL lame_set_ogg(lame_global_flags *, int); +int CDECL lame_get_ogg(const lame_global_flags *); +#endif + +/* + internal algorithm selection. True quality is determined by the bitrate + but this variable will effect quality by selecting expensive or cheap algorithms. + quality=0..9. 0=best (very slow). 9=worst. + recommended: 2 near-best quality, not too slow + 5 good quality, fast + 7 ok quality, really fast +*/ +int CDECL lame_set_quality(lame_global_flags *, int); +int CDECL lame_get_quality(const lame_global_flags *); + +/* + mode = 0,1,2,3 = stereo, jstereo, dual channel (not supported), mono + default: lame picks based on compression ration and input channels +*/ +int CDECL lame_set_mode(lame_global_flags *, MPEG_mode); +MPEG_mode CDECL lame_get_mode(const lame_global_flags *); + +#if DEPRECATED_OR_OBSOLETE_CODE_REMOVED +#else +/* + mode_automs. Use a M/S mode with a switching threshold based on + compression ratio + DEPRECATED +*/ +int CDECL lame_set_mode_automs(lame_global_flags *, int); +int CDECL lame_get_mode_automs(const lame_global_flags *); +#endif + +/* + force_ms. Force M/S for all frames. For testing only. + default = 0 (disabled) +*/ +int CDECL lame_set_force_ms(lame_global_flags *, int); +int CDECL lame_get_force_ms(const lame_global_flags *); + +/* use free_format? default = 0 (disabled) */ +int CDECL lame_set_free_format(lame_global_flags *, int); +int CDECL lame_get_free_format(const lame_global_flags *); + +/* perform ReplayGain analysis? default = 0 (disabled) */ +int CDECL lame_set_findReplayGain(lame_global_flags *, int); +int CDECL lame_get_findReplayGain(const lame_global_flags *); + +/* decode on the fly. Search for the peak sample. If the ReplayGain + * analysis is enabled then perform the analysis on the decoded data + * stream. default = 0 (disabled) + * NOTE: if this option is set the build-in decoder should not be used */ +int CDECL lame_set_decode_on_the_fly(lame_global_flags *, int); +int CDECL lame_get_decode_on_the_fly(const lame_global_flags *); + +#if DEPRECATED_OR_OBSOLETE_CODE_REMOVED +#else +/* DEPRECATED: now does the same as lame_set_findReplayGain() + default = 0 (disabled) */ +int CDECL lame_set_ReplayGain_input(lame_global_flags *, int); +int CDECL lame_get_ReplayGain_input(const lame_global_flags *); + +/* DEPRECATED: now does the same as + lame_set_decode_on_the_fly() && lame_set_findReplayGain() + default = 0 (disabled) */ +int CDECL lame_set_ReplayGain_decode(lame_global_flags *, int); +int CDECL lame_get_ReplayGain_decode(const lame_global_flags *); + +/* DEPRECATED: now does the same as lame_set_decode_on_the_fly() + default = 0 (disabled) */ +int CDECL lame_set_findPeakSample(lame_global_flags *, int); +int CDECL lame_get_findPeakSample(const lame_global_flags *); +#endif + +/* counters for gapless encoding */ +int CDECL lame_set_nogap_total(lame_global_flags*, int); +int CDECL lame_get_nogap_total(const lame_global_flags*); + +int CDECL lame_set_nogap_currentindex(lame_global_flags* , int); +int CDECL lame_get_nogap_currentindex(const lame_global_flags*); + + +/* + * OPTIONAL: + * Set printf like error/debug/message reporting functions. + * The second argument has to be a pointer to a function which looks like + * void my_debugf(const char *format, va_list ap) + * { + * (void) vfprintf(stdout, format, ap); + * } + * If you use NULL as the value of the pointer in the set function, the + * lame buildin function will be used (prints to stderr). + * To quiet any output you have to replace the body of the example function + * with just "return;" and use it in the set function. + */ +int CDECL lame_set_errorf(lame_global_flags *, lame_report_function); +int CDECL lame_set_debugf(lame_global_flags *, lame_report_function); +int CDECL lame_set_msgf (lame_global_flags *, lame_report_function); + + + +/* set one of brate compression ratio. default is compression ratio of 11. */ +int CDECL lame_set_brate(lame_global_flags *, int); +int CDECL lame_get_brate(const lame_global_flags *); +int CDECL lame_set_compression_ratio(lame_global_flags *, float); +float CDECL lame_get_compression_ratio(const lame_global_flags *); + + +int CDECL lame_set_preset( lame_global_flags* gfp, int ); +int CDECL lame_set_asm_optimizations( lame_global_flags* gfp, int, int ); + + + +/******************************************************************** + * frame params + ***********************************************************************/ +/* mark as copyright. default=0 */ +int CDECL lame_set_copyright(lame_global_flags *, int); +int CDECL lame_get_copyright(const lame_global_flags *); + +/* mark as original. default=1 */ +int CDECL lame_set_original(lame_global_flags *, int); +int CDECL lame_get_original(const lame_global_flags *); + +/* error_protection. Use 2 bytes from each frame for CRC checksum. default=0 */ +int CDECL lame_set_error_protection(lame_global_flags *, int); +int CDECL lame_get_error_protection(const lame_global_flags *); + +#if DEPRECATED_OR_OBSOLETE_CODE_REMOVED +#else +/* padding_type. 0=pad no frames 1=pad all frames 2=adjust padding(default) */ +int CDECL lame_set_padding_type(lame_global_flags *, Padding_type); +Padding_type CDECL lame_get_padding_type(const lame_global_flags *); +#endif + +/* MP3 'private extension' bit Meaningless. default=0 */ +int CDECL lame_set_extension(lame_global_flags *, int); +int CDECL lame_get_extension(const lame_global_flags *); + +/* enforce strict ISO compliance. default=0 */ +int CDECL lame_set_strict_ISO(lame_global_flags *, int); +int CDECL lame_get_strict_ISO(const lame_global_flags *); + + +/******************************************************************** + * quantization/noise shaping + ***********************************************************************/ + +/* disable the bit reservoir. For testing only. default=0 */ +int CDECL lame_set_disable_reservoir(lame_global_flags *, int); +int CDECL lame_get_disable_reservoir(const lame_global_flags *); + +/* select a different "best quantization" function. default=0 */ +int CDECL lame_set_quant_comp(lame_global_flags *, int); +int CDECL lame_get_quant_comp(const lame_global_flags *); +int CDECL lame_set_quant_comp_short(lame_global_flags *, int); +int CDECL lame_get_quant_comp_short(const lame_global_flags *); + +int CDECL lame_set_experimentalX(lame_global_flags *, int); /* compatibility*/ +int CDECL lame_get_experimentalX(const lame_global_flags *); + +/* another experimental option. for testing only */ +int CDECL lame_set_experimentalY(lame_global_flags *, int); +int CDECL lame_get_experimentalY(const lame_global_flags *); + +/* another experimental option. for testing only */ +int CDECL lame_set_experimentalZ(lame_global_flags *, int); +int CDECL lame_get_experimentalZ(const lame_global_flags *); + +/* Naoki's psycho acoustic model. default=0 */ +int CDECL lame_set_exp_nspsytune(lame_global_flags *, int); +int CDECL lame_get_exp_nspsytune(const lame_global_flags *); + +void CDECL lame_set_msfix(lame_global_flags *, double); +float CDECL lame_get_msfix(const lame_global_flags *); + + +/******************************************************************** + * VBR control + ***********************************************************************/ +/* Types of VBR. default = vbr_off = CBR */ +int CDECL lame_set_VBR(lame_global_flags *, vbr_mode); +vbr_mode CDECL lame_get_VBR(const lame_global_flags *); + +/* VBR quality level. 0=highest 9=lowest */ +int CDECL lame_set_VBR_q(lame_global_flags *, int); +int CDECL lame_get_VBR_q(const lame_global_flags *); + +/* VBR quality level. 0=highest 9=lowest, Range [0,...,10[ */ +int CDECL lame_set_VBR_quality(lame_global_flags *, float); +float CDECL lame_get_VBR_quality(const lame_global_flags *); + +/* Ignored except for VBR=vbr_abr (ABR mode) */ +int CDECL lame_set_VBR_mean_bitrate_kbps(lame_global_flags *, int); +int CDECL lame_get_VBR_mean_bitrate_kbps(const lame_global_flags *); + +int CDECL lame_set_VBR_min_bitrate_kbps(lame_global_flags *, int); +int CDECL lame_get_VBR_min_bitrate_kbps(const lame_global_flags *); + +int CDECL lame_set_VBR_max_bitrate_kbps(lame_global_flags *, int); +int CDECL lame_get_VBR_max_bitrate_kbps(const lame_global_flags *); + +/* + 1=strictly enforce VBR_min_bitrate. Normally it will be violated for + analog silence +*/ +int CDECL lame_set_VBR_hard_min(lame_global_flags *, int); +int CDECL lame_get_VBR_hard_min(const lame_global_flags *); + +/* for preset */ +#if DEPRECATED_OR_OBSOLETE_CODE_REMOVED +#else +int CDECL lame_set_preset_expopts(lame_global_flags *, int); +#endif + +/******************************************************************** + * Filtering control + ***********************************************************************/ +/* freq in Hz to apply lowpass. Default = 0 = lame chooses. -1 = disabled */ +int CDECL lame_set_lowpassfreq(lame_global_flags *, int); +int CDECL lame_get_lowpassfreq(const lame_global_flags *); +/* width of transition band, in Hz. Default = one polyphase filter band */ +int CDECL lame_set_lowpasswidth(lame_global_flags *, int); +int CDECL lame_get_lowpasswidth(const lame_global_flags *); + +/* freq in Hz to apply highpass. Default = 0 = lame chooses. -1 = disabled */ +int CDECL lame_set_highpassfreq(lame_global_flags *, int); +int CDECL lame_get_highpassfreq(const lame_global_flags *); +/* width of transition band, in Hz. Default = one polyphase filter band */ +int CDECL lame_set_highpasswidth(lame_global_flags *, int); +int CDECL lame_get_highpasswidth(const lame_global_flags *); + + +/******************************************************************** + * psycho acoustics and other arguments which you should not change + * unless you know what you are doing + ***********************************************************************/ + +/* only use ATH for masking */ +int CDECL lame_set_ATHonly(lame_global_flags *, int); +int CDECL lame_get_ATHonly(const lame_global_flags *); + +/* only use ATH for short blocks */ +int CDECL lame_set_ATHshort(lame_global_flags *, int); +int CDECL lame_get_ATHshort(const lame_global_flags *); + +/* disable ATH */ +int CDECL lame_set_noATH(lame_global_flags *, int); +int CDECL lame_get_noATH(const lame_global_flags *); + +/* select ATH formula */ +int CDECL lame_set_ATHtype(lame_global_flags *, int); +int CDECL lame_get_ATHtype(const lame_global_flags *); + +/* lower ATH by this many db */ +int CDECL lame_set_ATHlower(lame_global_flags *, float); +float CDECL lame_get_ATHlower(const lame_global_flags *); + +/* select ATH adaptive adjustment type */ +int CDECL lame_set_athaa_type( lame_global_flags *, int); +int CDECL lame_get_athaa_type( const lame_global_flags *); + +#if DEPRECATED_OR_OBSOLETE_CODE_REMOVED +#else +/* select the loudness approximation used by the ATH adaptive auto-leveling */ +int CDECL lame_set_athaa_loudapprox( lame_global_flags *, int); +int CDECL lame_get_athaa_loudapprox( const lame_global_flags *); +#endif + +/* adjust (in dB) the point below which adaptive ATH level adjustment occurs */ +int CDECL lame_set_athaa_sensitivity( lame_global_flags *, float); +float CDECL lame_get_athaa_sensitivity( const lame_global_flags* ); + +#if DEPRECATED_OR_OBSOLETE_CODE_REMOVED +#else +/* OBSOLETE: predictability limit (ISO tonality formula) */ +int CDECL lame_set_cwlimit(lame_global_flags *, int); +int CDECL lame_get_cwlimit(const lame_global_flags *); +#endif + +/* + allow blocktypes to differ between channels? + default: 0 for jstereo, 1 for stereo +*/ +int CDECL lame_set_allow_diff_short(lame_global_flags *, int); +int CDECL lame_get_allow_diff_short(const lame_global_flags *); + +/* use temporal masking effect (default = 1) */ +int CDECL lame_set_useTemporal(lame_global_flags *, int); +int CDECL lame_get_useTemporal(const lame_global_flags *); + +/* use temporal masking effect (default = 1) */ +int CDECL lame_set_interChRatio(lame_global_flags *, float); +float CDECL lame_get_interChRatio(const lame_global_flags *); + +/* disable short blocks */ +int CDECL lame_set_no_short_blocks(lame_global_flags *, int); +int CDECL lame_get_no_short_blocks(const lame_global_flags *); + +/* force short blocks */ +int CDECL lame_set_force_short_blocks(lame_global_flags *, int); +int CDECL lame_get_force_short_blocks(const lame_global_flags *); + +/* Input PCM is emphased PCM (for instance from one of the rarely + emphased CDs), it is STRONGLY not recommended to use this, because + psycho does not take it into account, and last but not least many decoders + ignore these bits */ +int CDECL lame_set_emphasis(lame_global_flags *, int); +int CDECL lame_get_emphasis(const lame_global_flags *); + + + +/************************************************************************/ +/* internal variables, cannot be set... */ +/* provided because they may be of use to calling application */ +/************************************************************************/ +/* version 0=MPEG-2 1=MPEG-1 (2=MPEG-2.5) */ +int CDECL lame_get_version(const lame_global_flags *); + +/* encoder delay */ +int CDECL lame_get_encoder_delay(const lame_global_flags *); + +/* + padding appended to the input to make sure decoder can fully decode + all input. Note that this value can only be calculated during the + call to lame_encoder_flush(). Before lame_encoder_flush() has + been called, the value of encoder_padding = 0. +*/ +int CDECL lame_get_encoder_padding(const lame_global_flags *); + +/* size of MPEG frame */ +int CDECL lame_get_framesize(const lame_global_flags *); + +/* number of PCM samples buffered, but not yet encoded to mp3 data. */ +int CDECL lame_get_mf_samples_to_encode( const lame_global_flags* gfp ); + +/* + size (bytes) of mp3 data buffered, but not yet encoded. + this is the number of bytes which would be output by a call to + lame_encode_flush_nogap. NOTE: lame_encode_flush() will return + more bytes than this because it will encode the reamining buffered + PCM samples before flushing the mp3 buffers. +*/ +int CDECL lame_get_size_mp3buffer( const lame_global_flags* gfp ); + +/* number of frames encoded so far */ +int CDECL lame_get_frameNum(const lame_global_flags *); + +/* + lame's estimate of the total number of frames to be encoded + only valid if calling program set num_samples +*/ +int CDECL lame_get_totalframes(const lame_global_flags *); + +/* RadioGain value. Multiplied by 10 and rounded to the nearest. */ +int CDECL lame_get_RadioGain(const lame_global_flags *); + +/* AudiophileGain value. Multipled by 10 and rounded to the nearest. */ +int CDECL lame_get_AudiophileGain(const lame_global_flags *); + +/* the peak sample */ +float CDECL lame_get_PeakSample(const lame_global_flags *); + +/* Gain change required for preventing clipping. The value is correct only if + peak sample searching was enabled. If negative then the waveform + already does not clip. The value is multiplied by 10 and rounded up. */ +int CDECL lame_get_noclipGainChange(const lame_global_flags *); + +/* user-specified scale factor required for preventing clipping. Value is + correct only if peak sample searching was enabled and no user-specified + scaling was performed. If negative then either the waveform already does + not clip or the value cannot be determined */ +float CDECL lame_get_noclipScale(const lame_global_flags *); + + + + + + + +/* + * REQUIRED: + * sets more internal configuration based on data provided above. + * returns -1 if something failed. + */ +int CDECL lame_init_params(lame_global_flags *); + + +/* + * OPTIONAL: + * get the version number, in a string. of the form: + * "3.63 (beta)" or just "3.63". + */ +const char* CDECL get_lame_version ( void ); +const char* CDECL get_lame_short_version ( void ); +const char* CDECL get_lame_very_short_version ( void ); +const char* CDECL get_psy_version ( void ); +const char* CDECL get_lame_url ( void ); +const char* CDECL get_lame_os_bitness ( void ); + +/* + * OPTIONAL: + * get the version numbers in numerical form. + */ +typedef struct { + /* generic LAME version */ + int major; + int minor; + int alpha; /* 0 if not an alpha version */ + int beta; /* 0 if not a beta version */ + + /* version of the psy model */ + int psy_major; + int psy_minor; + int psy_alpha; /* 0 if not an alpha version */ + int psy_beta; /* 0 if not a beta version */ + + /* compile time features */ + const char *features; /* Don't make assumptions about the contents! */ +} lame_version_t; +void CDECL get_lame_version_numerical(lame_version_t *); + + +/* + * OPTIONAL: + * print internal lame configuration to message handler + */ +void CDECL lame_print_config(const lame_global_flags* gfp); + +void CDECL lame_print_internals( const lame_global_flags *gfp); + + +/* + * input pcm data, output (maybe) mp3 frames. + * This routine handles all buffering, resampling and filtering for you. + * + * return code number of bytes output in mp3buf. Can be 0 + * -1: mp3buf was too small + * -2: malloc() problem + * -3: lame_init_params() not called + * -4: psycho acoustic problems + * + * The required mp3buf_size can be computed from num_samples, + * samplerate and encoding rate, but here is a worst case estimate: + * + * mp3buf_size in bytes = 1.25*num_samples + 7200 + * + * I think a tighter bound could be: (mt, March 2000) + * MPEG1: + * num_samples*(bitrate/8)/samplerate + 4*1152*(bitrate/8)/samplerate + 512 + * MPEG2: + * num_samples*(bitrate/8)/samplerate + 4*576*(bitrate/8)/samplerate + 256 + * + * but test first if you use that! + * + * set mp3buf_size = 0 and LAME will not check if mp3buf_size is + * large enough. + * + * NOTE: + * if gfp->num_channels=2, but gfp->mode = 3 (mono), the L & R channels + * will be averaged into the L channel before encoding only the L channel + * This will overwrite the data in buffer_l[] and buffer_r[]. + * +*/ +int CDECL lame_encode_buffer ( + lame_global_flags* gfp, /* global context handle */ + const short int buffer_l [], /* PCM data for left channel */ + const short int buffer_r [], /* PCM data for right channel */ + const int nsamples, /* number of samples per channel */ + unsigned char* mp3buf, /* pointer to encoded MP3 stream */ + const int mp3buf_size ); /* number of valid octets in this + stream */ + +/* + * as above, but input has L & R channel data interleaved. + * NOTE: + * num_samples = number of samples in the L (or R) + * channel, not the total number of samples in pcm[] + */ +int CDECL lame_encode_buffer_interleaved( + lame_global_flags* gfp, /* global context handlei */ + short int pcm[], /* PCM data for left and right + channel, interleaved */ + int num_samples, /* number of samples per channel, + _not_ number of samples in + pcm[] */ + unsigned char* mp3buf, /* pointer to encoded MP3 stream */ + int mp3buf_size ); /* number of valid octets in this + stream */ + + +/* as lame_encode_buffer, but for 'float's. + * !! NOTE: !! data must still be scaled to be in the same range as + * short int, +/- 32768 + */ +int CDECL lame_encode_buffer_float( + lame_global_flags* gfp, /* global context handle */ + const float pcm_l [], /* PCM data for left channel */ + const float pcm_r [], /* PCM data for right channel */ + const int nsamples, /* number of samples per channel */ + unsigned char* mp3buf, /* pointer to encoded MP3 stream */ + const int mp3buf_size ); /* number of valid octets in this + stream */ + +/* as lame_encode_buffer, but for 'float's. + * !! NOTE: !! data must be scaled to +/- 1 full scale + */ +int CDECL lame_encode_buffer_ieee_float( + lame_t gfp, + const float pcm_l [], /* PCM data for left channel */ + const float pcm_r [], /* PCM data for right channel */ + const int nsamples, + unsigned char * mp3buf, + const int mp3buf_size); +int CDECL lame_encode_buffer_interleaved_ieee_float( + lame_t gfp, + const float pcm[], /* PCM data for left and right + channel, interleaved */ + const int nsamples, + unsigned char * mp3buf, + const int mp3buf_size); + +/* as lame_encode_buffer, but for 'double's. + * !! NOTE: !! data must be scaled to +/- 1 full scale + */ +int CDECL lame_encode_buffer_ieee_double( + lame_t gfp, + const double pcm_l [], /* PCM data for left channel */ + const double pcm_r [], /* PCM data for right channel */ + const int nsamples, + unsigned char * mp3buf, + const int mp3buf_size); +int CDECL lame_encode_buffer_interleaved_ieee_double( + lame_t gfp, + const double pcm[], /* PCM data for left and right + channel, interleaved */ + const int nsamples, + unsigned char * mp3buf, + const int mp3buf_size); + +/* as lame_encode_buffer, but for long's + * !! NOTE: !! data must still be scaled to be in the same range as + * short int, +/- 32768 + * + * This scaling was a mistake (doesn't allow one to exploit full + * precision of type 'long'. Use lame_encode_buffer_long2() instead. + * + */ +int CDECL lame_encode_buffer_long( + lame_global_flags* gfp, /* global context handle */ + const long buffer_l [], /* PCM data for left channel */ + const long buffer_r [], /* PCM data for right channel */ + const int nsamples, /* number of samples per channel */ + unsigned char* mp3buf, /* pointer to encoded MP3 stream */ + const int mp3buf_size ); /* number of valid octets in this + stream */ + +/* Same as lame_encode_buffer_long(), but with correct scaling. + * !! NOTE: !! data must still be scaled to be in the same range as + * type 'long'. Data should be in the range: +/- 2^(8*size(long)-1) + * + */ +int CDECL lame_encode_buffer_long2( + lame_global_flags* gfp, /* global context handle */ + const long buffer_l [], /* PCM data for left channel */ + const long buffer_r [], /* PCM data for right channel */ + const int nsamples, /* number of samples per channel */ + unsigned char* mp3buf, /* pointer to encoded MP3 stream */ + const int mp3buf_size ); /* number of valid octets in this + stream */ + +/* as lame_encode_buffer, but for int's + * !! NOTE: !! input should be scaled to the maximum range of 'int' + * If int is 4 bytes, then the values should range from + * +/- 2147483648. + * + * This routine does not (and cannot, without loosing precision) use + * the same scaling as the rest of the lame_encode_buffer() routines. + * + */ +int CDECL lame_encode_buffer_int( + lame_global_flags* gfp, /* global context handle */ + const int buffer_l [], /* PCM data for left channel */ + const int buffer_r [], /* PCM data for right channel */ + const int nsamples, /* number of samples per channel */ + unsigned char* mp3buf, /* pointer to encoded MP3 stream */ + const int mp3buf_size ); /* number of valid octets in this + stream */ + + + + + +/* + * REQUIRED: + * lame_encode_flush will flush the intenal PCM buffers, padding with + * 0's to make sure the final frame is complete, and then flush + * the internal MP3 buffers, and thus may return a + * final few mp3 frames. 'mp3buf' should be at least 7200 bytes long + * to hold all possible emitted data. + * + * will also write id3v1 tags (if any) into the bitstream + * + * return code = number of bytes output to mp3buf. Can be 0 + */ +int CDECL lame_encode_flush( + lame_global_flags * gfp, /* global context handle */ + unsigned char* mp3buf, /* pointer to encoded MP3 stream */ + int size); /* number of valid octets in this stream */ + +/* + * OPTIONAL: + * lame_encode_flush_nogap will flush the internal mp3 buffers and pad + * the last frame with ancillary data so it is a complete mp3 frame. + * + * 'mp3buf' should be at least 7200 bytes long + * to hold all possible emitted data. + * + * After a call to this routine, the outputed mp3 data is complete, but + * you may continue to encode new PCM samples and write future mp3 data + * to a different file. The two mp3 files will play back with no gaps + * if they are concatenated together. + * + * This routine will NOT write id3v1 tags into the bitstream. + * + * return code = number of bytes output to mp3buf. Can be 0 + */ +int CDECL lame_encode_flush_nogap( + lame_global_flags * gfp, /* global context handle */ + unsigned char* mp3buf, /* pointer to encoded MP3 stream */ + int size); /* number of valid octets in this stream */ + +/* + * OPTIONAL: + * Normally, this is called by lame_init_params(). It writes id3v2 and + * Xing headers into the front of the bitstream, and sets frame counters + * and bitrate histogram data to 0. You can also call this after + * lame_encode_flush_nogap(). + */ +int CDECL lame_init_bitstream( + lame_global_flags * gfp); /* global context handle */ + + + +/* + * OPTIONAL: some simple statistics + * a bitrate histogram to visualize the distribution of used frame sizes + * a stereo mode histogram to visualize the distribution of used stereo + * modes, useful in joint-stereo mode only + * 0: LR left-right encoded + * 1: LR-I left-right and intensity encoded (currently not supported) + * 2: MS mid-side encoded + * 3: MS-I mid-side and intensity encoded (currently not supported) + * + * attention: don't call them after lame_encode_finish + * suggested: lame_encode_flush -> lame_*_hist -> lame_close + */ + +void CDECL lame_bitrate_hist( + const lame_global_flags * gfp, + int bitrate_count[14] ); +void CDECL lame_bitrate_kbps( + const lame_global_flags * gfp, + int bitrate_kbps [14] ); +void CDECL lame_stereo_mode_hist( + const lame_global_flags * gfp, + int stereo_mode_count[4] ); + +void CDECL lame_bitrate_stereo_mode_hist ( + const lame_global_flags * gfp, + int bitrate_stmode_count[14][4] ); + +void CDECL lame_block_type_hist ( + const lame_global_flags * gfp, + int btype_count[6] ); + +void CDECL lame_bitrate_block_type_hist ( + const lame_global_flags * gfp, + int bitrate_btype_count[14][6] ); + +#if (DEPRECATED_OR_OBSOLETE_CODE_REMOVED && 0) +#else +/* + * OPTIONAL: + * lame_mp3_tags_fid will rewrite a Xing VBR tag to the mp3 file with file + * pointer fid. These calls perform forward and backwards seeks, so make + * sure fid is a real file. Make sure lame_encode_flush has been called, + * and all mp3 data has been written to the file before calling this + * function. + * NOTE: + * if VBR tags are turned off by the user, or turned off by LAME because + * the output is not a regular file, this call does nothing + * NOTE: + * LAME wants to read from the file to skip an optional ID3v2 tag, so + * make sure you opened the file for writing and reading. + * NOTE: + * You can call lame_get_lametag_frame instead, if you want to insert + * the lametag yourself. +*/ +void CDECL lame_mp3_tags_fid(lame_global_flags *, FILE* fid); +#endif + +/* + * OPTIONAL: + * lame_get_lametag_frame copies the final LAME-tag into 'buffer'. + * The function returns the number of bytes copied into buffer, or + * the required buffer size, if the provided buffer is too small. + * Function failed, if the return value is larger than 'size'! + * Make sure lame_encode flush has been called before calling this function. + * NOTE: + * if VBR tags are turned off by the user, or turned off by LAME, + * this call does nothing and returns 0. + * NOTE: + * LAME inserted an empty frame in the beginning of mp3 audio data, + * which you have to replace by the final LAME-tag frame after encoding. + * In case there is no ID3v2 tag, usually this frame will be the very first + * data in your mp3 file. If you put some other leading data into your + * file, you'll have to do some bookkeeping about where to write this buffer. + */ +size_t CDECL lame_get_lametag_frame( + const lame_global_flags *, unsigned char* buffer, size_t size); + +/* + * REQUIRED: + * final call to free all remaining buffers + */ +int CDECL lame_close (lame_global_flags *); + +#if DEPRECATED_OR_OBSOLETE_CODE_REMOVED +#else +/* + * OBSOLETE: + * lame_encode_finish combines lame_encode_flush() and lame_close() in + * one call. However, once this call is made, the statistics routines + * will no longer work because the data will have been cleared, and + * lame_mp3_tags_fid() cannot be called to add data to the VBR header + */ +int CDECL lame_encode_finish( + lame_global_flags* gfp, + unsigned char* mp3buf, + int size ); +#endif + + + + + + +/********************************************************************* + * + * decoding + * + * a simple interface to mpglib, part of mpg123, is also included if + * libmp3lame is compiled with HAVE_MPGLIB + * + *********************************************************************/ + +struct hip_global_struct; +typedef struct hip_global_struct hip_global_flags; +typedef hip_global_flags *hip_t; + + +typedef struct { + int header_parsed; /* 1 if header was parsed and following data was + computed */ + int stereo; /* number of channels */ + int samplerate; /* sample rate */ + int bitrate; /* bitrate */ + int mode; /* mp3 frame type */ + int mode_ext; /* mp3 frame type */ + int framesize; /* number of samples per mp3 frame */ + + /* this data is only computed if mpglib detects a Xing VBR header */ + unsigned long nsamp; /* number of samples in mp3 file. */ + int totalframes; /* total number of frames in mp3 file */ + + /* this data is not currently computed by the mpglib routines */ + int framenum; /* frames decoded counter */ +} mp3data_struct; + +/* required call to initialize decoder */ +hip_t CDECL hip_decode_init(void); + +/* cleanup call to exit decoder */ +int CDECL hip_decode_exit(hip_t gfp); + +/* HIP reporting functions */ +void CDECL hip_set_errorf(hip_t gfp, lame_report_function f); +void CDECL hip_set_debugf(hip_t gfp, lame_report_function f); +void CDECL hip_set_msgf (hip_t gfp, lame_report_function f); + +/********************************************************************* + * input 1 mp3 frame, output (maybe) pcm data. + * + * nout = hip_decode(hip, mp3buf,len,pcm_l,pcm_r); + * + * input: + * len : number of bytes of mp3 data in mp3buf + * mp3buf[len] : mp3 data to be decoded + * + * output: + * nout: -1 : decoding error + * 0 : need more data before we can complete the decode + * >0 : returned 'nout' samples worth of data in pcm_l,pcm_r + * pcm_l[nout] : left channel data + * pcm_r[nout] : right channel data + * + *********************************************************************/ +int CDECL hip_decode( hip_t gfp + , unsigned char * mp3buf + , size_t len + , short pcm_l[] + , short pcm_r[] + ); + +/* same as hip_decode, and also returns mp3 header data */ +int CDECL hip_decode_headers( hip_t gfp + , unsigned char* mp3buf + , size_t len + , short pcm_l[] + , short pcm_r[] + , mp3data_struct* mp3data + ); + +/* same as hip_decode, but returns at most one frame */ +int CDECL hip_decode1( hip_t gfp + , unsigned char* mp3buf + , size_t len + , short pcm_l[] + , short pcm_r[] + ); + +/* same as hip_decode1, but returns at most one frame and mp3 header data */ +int CDECL hip_decode1_headers( hip_t gfp + , unsigned char* mp3buf + , size_t len + , short pcm_l[] + , short pcm_r[] + , mp3data_struct* mp3data + ); + +/* same as hip_decode1_headers, but also returns enc_delay and enc_padding + from VBR Info tag, (-1 if no info tag was found) */ +int CDECL hip_decode1_headersB( hip_t gfp + , unsigned char* mp3buf + , size_t len + , short pcm_l[] + , short pcm_r[] + , mp3data_struct* mp3data + , int *enc_delay + , int *enc_padding + ); + + + +/* OBSOLETE: + * lame_decode... functions are there to keep old code working + * but it is strongly recommended to replace calls by hip_decode... + * function calls, see above. + */ +#if DEPRECATED_OR_OBSOLETE_CODE_REMOVED +#else +int CDECL lame_decode_init(void); +int CDECL lame_decode( + unsigned char * mp3buf, + int len, + short pcm_l[], + short pcm_r[] ); +int CDECL lame_decode_headers( + unsigned char* mp3buf, + int len, + short pcm_l[], + short pcm_r[], + mp3data_struct* mp3data ); +int CDECL lame_decode1( + unsigned char* mp3buf, + int len, + short pcm_l[], + short pcm_r[] ); +int CDECL lame_decode1_headers( + unsigned char* mp3buf, + int len, + short pcm_l[], + short pcm_r[], + mp3data_struct* mp3data ); +int CDECL lame_decode1_headersB( + unsigned char* mp3buf, + int len, + short pcm_l[], + short pcm_r[], + mp3data_struct* mp3data, + int *enc_delay, + int *enc_padding ); +int CDECL lame_decode_exit(void); + +#endif /* obsolete lame_decode API calls */ + + +/********************************************************************* + * + * id3tag stuff + * + *********************************************************************/ + +/* + * id3tag.h -- Interface to write ID3 version 1 and 2 tags. + * + * Copyright (C) 2000 Don Melton. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +/* utility to obtain alphabetically sorted list of genre names with numbers */ +void CDECL id3tag_genre_list( + void (*handler)(int, const char *, void *), + void* cookie); + +void CDECL id3tag_init (lame_t gfp); + +/* force addition of version 2 tag */ +void CDECL id3tag_add_v2 (lame_t gfp); + +/* add only a version 1 tag */ +void CDECL id3tag_v1_only (lame_t gfp); + +/* add only a version 2 tag */ +void CDECL id3tag_v2_only (lame_t gfp); + +/* pad version 1 tag with spaces instead of nulls */ +void CDECL id3tag_space_v1 (lame_t gfp); + +/* pad version 2 tag with extra 128 bytes */ +void CDECL id3tag_pad_v2 (lame_t gfp); + +/* pad version 2 tag with extra n bytes */ +void CDECL id3tag_set_pad (lame_t gfp, size_t n); + +void CDECL id3tag_set_title(lame_t gfp, const char* title); +void CDECL id3tag_set_artist(lame_t gfp, const char* artist); +void CDECL id3tag_set_album(lame_t gfp, const char* album); +void CDECL id3tag_set_year(lame_t gfp, const char* year); +void CDECL id3tag_set_comment(lame_t gfp, const char* comment); + +/* return -1 result if track number is out of ID3v1 range + and ignored for ID3v1 */ +int CDECL id3tag_set_track(lame_t gfp, const char* track); + +/* return non-zero result if genre name or number is invalid + result 0: OK + result -1: genre number out of range + result -2: no valid ID3v1 genre name, mapped to ID3v1 'Other' + but taken as-is for ID3v2 genre tag */ +int CDECL id3tag_set_genre(lame_t gfp, const char* genre); + +/* return non-zero result if field name is invalid */ +int CDECL id3tag_set_fieldvalue(lame_t gfp, const char* fieldvalue); + +/* return non-zero result if image type is invalid */ +int CDECL id3tag_set_albumart(lame_t gfp, const char* image, size_t size); + +/* lame_get_id3v1_tag copies ID3v1 tag into buffer. + * Function returns number of bytes copied into buffer, or number + * of bytes rquired if buffer 'size' is too small. + * Function fails, if returned value is larger than 'size'. + * NOTE: + * This functions does nothing, if user/LAME disabled ID3v1 tag. + */ +size_t CDECL lame_get_id3v1_tag(lame_t gfp, unsigned char* buffer, size_t size); + +/* lame_get_id3v2_tag copies ID3v2 tag into buffer. + * Function returns number of bytes copied into buffer, or number + * of bytes rquired if buffer 'size' is too small. + * Function fails, if returned value is larger than 'size'. + * NOTE: + * This functions does nothing, if user/LAME disabled ID3v2 tag. + */ +size_t CDECL lame_get_id3v2_tag(lame_t gfp, unsigned char* buffer, size_t size); + +/* normaly lame_init_param writes ID3v2 tags into the audio stream + * Call lame_set_write_id3tag_automatic(gfp, 0) before lame_init_param + * to turn off this behaviour and get ID3v2 tag with above function + * write it yourself into your file. + */ +void CDECL lame_set_write_id3tag_automatic(lame_global_flags * gfp, int); +int CDECL lame_get_write_id3tag_automatic(lame_global_flags const* gfp); + +/* experimental */ +int CDECL id3tag_set_textinfo_latin1(lame_t gfp, char const *id, char const *text); + +/* experimental */ +int CDECL id3tag_set_comment_latin1(lame_t gfp, char const *lang, char const *desc, char const *text); + +#if DEPRECATED_OR_OBSOLETE_CODE_REMOVED +#else +/* experimental */ +int CDECL id3tag_set_textinfo_ucs2(lame_t gfp, char const *id, unsigned short const *text); + +/* experimental */ +int CDECL id3tag_set_comment_ucs2(lame_t gfp, char const *lang, + unsigned short const *desc, unsigned short const *text); + +/* experimental */ +int CDECL id3tag_set_fieldvalue_ucs2(lame_t gfp, const unsigned short *fieldvalue); +#endif + +/* experimental */ +int CDECL id3tag_set_fieldvalue_utf16(lame_t gfp, const unsigned short *fieldvalue); + +/* experimental */ +int CDECL id3tag_set_textinfo_utf16(lame_t gfp, char const *id, unsigned short const *text); + +/* experimental */ +int CDECL id3tag_set_comment_utf16(lame_t gfp, char const *lang, unsigned short const *desc, unsigned short const *text); + + +/*********************************************************************** +* +* list of valid bitrates [kbps] & sample frequencies [Hz]. +* first index: 0: MPEG-2 values (sample frequencies 16...24 kHz) +* 1: MPEG-1 values (sample frequencies 32...48 kHz) +* 2: MPEG-2.5 values (sample frequencies 8...12 kHz) +***********************************************************************/ + +extern const int bitrate_table [3][16]; +extern const int samplerate_table [3][ 4]; + +/* access functions for use in DLL, global vars are not exported */ +int CDECL lame_get_bitrate(int mpeg_version, int table_index); +int CDECL lame_get_samplerate(int mpeg_version, int table_index); + + +/* maximum size of albumart image (128KB), which affects LAME_MAXMP3BUFFER + as well since lame_encode_buffer() also returns ID3v2 tag data */ +#define LAME_MAXALBUMART (128 * 1024) + +/* maximum size of mp3buffer needed if you encode at most 1152 samples for + each call to lame_encode_buffer. see lame_encode_buffer() below + (LAME_MAXMP3BUFFER is now obsolete) */ +#define LAME_MAXMP3BUFFER (16384 + LAME_MAXALBUMART) + + +typedef enum { + LAME_OKAY = 0, + LAME_NOERROR = 0, + LAME_GENERICERROR = -1, + LAME_NOMEM = -10, + LAME_BADBITRATE = -11, + LAME_BADSAMPFREQ = -12, + LAME_INTERNALERROR = -13, + + FRONTEND_READERROR = -80, + FRONTEND_WRITEERROR = -81, + FRONTEND_FILETOOLARGE = -82 + +} lame_errorcodes_t; + +#if defined(__cplusplus) +} +#endif +#endif /* LAME_LAME_H */ + diff --git a/ios/Classes/FinAppletExt/Vendor/Lame/libmp3lame.a b/ios/Classes/FinAppletExt/Vendor/Lame/libmp3lame.a new file mode 100644 index 0000000..909a202 Binary files /dev/null and b/ios/Classes/FinAppletExt/Vendor/Lame/libmp3lame.a differ diff --git a/ios/Classes/FinAppletExt/Vendor/fincore/include/FinLicenseService.h b/ios/Classes/FinAppletExt/Vendor/fincore/include/FinLicenseService.h new file mode 100644 index 0000000..d6b60bc --- /dev/null +++ b/ios/Classes/FinAppletExt/Vendor/fincore/include/FinLicenseService.h @@ -0,0 +1,34 @@ +// +// FinLicenseService.h +// fincore +// +// Created by gordanyang on 2021/8/29. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface FinLicenseService : NSObject + +@property (nonatomic, strong) NSLock *checkLock; + +#pragma mark - encode/decode + +- (NSString *)fin_decodeAppKey:(NSString *)encryptText; +- (NSDictionary *)fin_getSDKKeyInfo; + +- (NSString *)fin_decodeAppKeyBySM4:(NSString *)appKey; +- (NSDictionary *)fin_getSDKKeyInfoBySM3; + +- (NSString *)fin_messageDigest:(NSString *)message; +- (NSString *)fin_messageDigest_sha256:(NSString *)message; + +- (NSData *)fin_encodeAESContent:(NSData *)content; +- (NSData *)fin_decodeAESContent:(NSData *)content; +- (NSData *)fin_encodeSMContent:(NSData *)content; +- (NSData *)fin_decodeSMContent:(NSData *)content; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/Vendor/fincore/include/SDKCoreClient.h b/ios/Classes/FinAppletExt/Vendor/fincore/include/SDKCoreClient.h new file mode 100644 index 0000000..570363e --- /dev/null +++ b/ios/Classes/FinAppletExt/Vendor/fincore/include/SDKCoreClient.h @@ -0,0 +1,19 @@ +// +// SDKCoreClient.h +// fincore +// +// Created by gordanyang on 2021/8/29. +// + +#import +#import "FinLicenseService.h" +NS_ASSUME_NONNULL_BEGIN + +@interface SDKCoreClient : NSObject ++ (instancetype)sharedInstance; + +@property (nonatomic, strong, readonly) FinLicenseService *finoLicenseService; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Classes/FinAppletExt/Vendor/fincore/include/fincore.h b/ios/Classes/FinAppletExt/Vendor/fincore/include/fincore.h new file mode 100644 index 0000000..ffb822a --- /dev/null +++ b/ios/Classes/FinAppletExt/Vendor/fincore/include/fincore.h @@ -0,0 +1,18 @@ +// +// fincore.h +// fincore +// +// Created by 杨涛 on 2018/4/26. +// Copyright © 2018年 finogeeks. All rights reserved. +// + + +//! Project version number for fincore. +FOUNDATION_EXPORT double fincoreVersionNumber; + +//! Project version string for fincore. +FOUNDATION_EXPORT const unsigned char fincoreVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import +#import "SDKCoreClient.h" +#import "FinLicenseService.h" diff --git a/ios/Classes/FinAppletExt/Vendor/fincore/libfincore.a b/ios/Classes/FinAppletExt/Vendor/fincore/libfincore.a new file mode 100644 index 0000000..ed4f44e Binary files /dev/null and b/ios/Classes/FinAppletExt/Vendor/fincore/libfincore.a differ diff --git a/ios/Classes/FinAppletExt/View/FATWebView.h b/ios/Classes/FinAppletExt/View/FATWebView.h new file mode 100644 index 0000000..a10f12c --- /dev/null +++ b/ios/Classes/FinAppletExt/View/FATWebView.h @@ -0,0 +1,15 @@ +// +// FATWebView.h +// FinApplet +// +// Created by Haley on 2019/12/9. +// Copyright © 2019 finogeeks. All rights reserved. +// + +#import + +@interface FATWebView : UIView + +- (instancetype)initWithFrame:(CGRect)frame URL:(NSURL *)URL appletId:(NSString *)appletId; + +@end diff --git a/ios/Classes/FinAppletExt/View/FATWebView.m b/ios/Classes/FinAppletExt/View/FATWebView.m new file mode 100644 index 0000000..8c2284e --- /dev/null +++ b/ios/Classes/FinAppletExt/View/FATWebView.m @@ -0,0 +1,260 @@ +// +// FATWebView.m +// FinApplet +// +// Created by Haley on 2019/12/9. +// Copyright © 2019 finogeeks. All rights reserved. +// + +#import "FATWebView.h" + +#import +#import +#import "FATExtUtil.h" + +@interface FATWebView () + +@property (nonatomic, strong) WKWebView *webView; + +@property (nonatomic, strong) UIProgressView *progressView; + +@property (nonatomic, copy) NSString *appletId; + +@end + +@implementation FATWebView + +- (instancetype)initWithFrame:(CGRect)frame URL:(NSURL *)URL appletId:(NSString *)appletId { + self = [super initWithFrame:frame]; + if (self) { + _appletId = appletId; + [self p_initSubViews:URL]; + } + return self; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + self.webView.frame = self.bounds; + + CGFloat y = fabs(self.webView.scrollView.contentOffset.y); + self.progressView.frame = CGRectMake(0, y, self.bounds.size.width, 4); +} + +- (void)dealloc { + [self.webView removeObserver:self forKeyPath:@"estimatedProgress"]; +} + +#pragma mark - private method + +- (void)p_initSubViews:(NSURL *)URL { + FATWeakScriptMessageDelegate *scriptMessageDelegate = [FATWeakScriptMessageDelegate new]; + scriptMessageDelegate.scriptDelegate = self; + + WKUserContentController *userContentController = [WKUserContentController new]; + NSString *souce = @"window.__fcjs_environment='miniprogram'"; + WKUserScript *script = [[WKUserScript alloc] initWithSource:souce injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:true]; + [userContentController addUserScript:script]; + [userContentController addScriptMessageHandler:scriptMessageDelegate name:@"webInvokeHandler"]; + [userContentController addScriptMessageHandler:scriptMessageDelegate name:@"webPublishHandler"]; + + WKWebViewConfiguration *wkWebViewConfiguration = [WKWebViewConfiguration new]; + wkWebViewConfiguration.allowsInlineMediaPlayback = YES; + wkWebViewConfiguration.userContentController = userContentController; + + self.webView = [[WKWebView alloc] initWithFrame:self.bounds configuration:wkWebViewConfiguration]; + self.webView.UIDelegate = self; + self.webView.navigationDelegate = self; + self.webView.clipsToBounds = YES; + self.webView.scrollView.delegate = self; + [self addSubview:self.webView]; + + NSURLRequest *request = [[NSURLRequest alloc] initWithURL:URL cachePolicy:NSURLRequestReloadRevalidatingCacheData timeoutInterval:60]; + [self.webView loadRequest:request]; + + self.progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.width, 4)]; + self.progressView.progressTintColor = [UIColor colorWithRed:44 / 255.0 green:127 / 255.0 blue:251 / 255.0 alpha:1]; + [self addSubview:self.progressView]; + + [self.webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil]; + + NSString *version = [FATClient sharedClient].version; + NSString *appendUserAgent; + NSString *model = [[UIDevice currentDevice] model]; + if ([FATExtUtil currentProductIdentificationIsEmpty]) { + appendUserAgent = [NSString stringWithFormat:@"Provider/finogeeks (%@; miniprogram; FinChat; runtimeSdkVersion/%@)", model, version]; + } else { + appendUserAgent = [NSString stringWithFormat:@"Provider/%@ (%@; miniprogram; %@; runtimeSdkVersion/%@)", [FATExtUtil currentProductIdentification], model, [FATExtUtil currentProductIdentification], version]; + } + NSString *customUA = [FATClient sharedClient].uiConfig.appendingCustomUserAgent; + if (customUA.length > 0) { + appendUserAgent = [appendUserAgent stringByAppendingString:@" "]; + appendUserAgent = [appendUserAgent stringByAppendingString:customUA]; + } + + __weak typeof(self) weakSelf = self; + [self.webView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id _Nullable result, NSError *_Nullable error) { + NSString *userAgent = result; + userAgent = [userAgent stringByAppendingFormat:@" %@", appendUserAgent]; + weakSelf.webView.customUserAgent = userAgent; + }]; +} + +- (void)callJS:(NSString *)js callback:(void (^)(id result, NSError *error))callback { + [self.webView evaluateJavaScript:js completionHandler:^(id _Nullable result, NSError *_Nullable error) { + if (callback) { + callback(result, error); + } + }]; +} + +- (void)webInvokeHandler:(NSDictionary *)data { + if (!data) { + return; + } + + NSString *command = data[@"C"]; + NSString *paramsString = data[@"paramsString"]; + + // 可能是字符串,也可能是number + id callbackId = data[@"callbackId"]; + + if (!command) { + return; + } + + if ([command isEqualToString:@"initPage"]) { + unsigned long long webPageId = self.webView.hash; + NSString *js = [NSString stringWithFormat:@"FinChatJSBridge.webInvokeCallbackHandler('%@',%@)", callbackId, @(webPageId)]; + [self callJS:js callback:nil]; + return; + } + + // 执行注入的事件 + NSDictionary *webExtensionApis = [FATWebExtension webExtensionApis]; + id handler = (__bridge id)(webExtensionApis[command].isOld ? webExtensionApis[command].deprecatedHandler : webExtensionApis[command].handler); + if (handler) { + FATExtensionApiCallback callbck = ^void(FATExtensionCode code, NSDictionary *result) { + NSString *successErrMsg = [NSString stringWithFormat:@"%@:ok", command]; + NSString *failErrMsg = [NSString stringWithFormat:@"%@:fail", command]; + NSString *cancelErrMsg = [NSString stringWithFormat:@"%@:cancel", command]; + + NSString *errMsg = (NSString *)result[@"errMsg"]; + if (errMsg && [errMsg isKindOfClass:[NSString class]] && errMsg.length > 0) { + successErrMsg = [successErrMsg stringByAppendingFormat:@" %@", errMsg]; + failErrMsg = [failErrMsg stringByAppendingFormat:@" %@", errMsg]; + cancelErrMsg = [cancelErrMsg stringByAppendingFormat:@" %@", errMsg]; + } + + switch (code) { + case FATExtensionCodeSuccess: { + NSMutableDictionary *successResult = [NSMutableDictionary dictionaryWithDictionary:result]; + [successResult setObject:successErrMsg forKey:@"errMsg"]; + NSString *resultJsonString = [self fat_jsonStringFromDict:successResult]; + + NSString *js = [NSString stringWithFormat:@"FinChatJSBridge.webInvokeCallbackHandler('%@',%@)", callbackId, resultJsonString]; + [self callJS:js callback:nil]; + break; + } + case FATExtensionCodeCancel: { + NSMutableDictionary *cancelResult = [NSMutableDictionary dictionaryWithDictionary:result]; + [cancelResult setObject:cancelErrMsg forKey:@"errMsg"]; + NSString *resultJsonString = [self fat_jsonStringFromDict:cancelResult]; + + NSString *js = [NSString stringWithFormat:@"FinChatJSBridge.webInvokeCallbackHandler('%@',%@)", callbackId, resultJsonString]; + [self callJS:js callback:nil]; + break; + } + case FATExtensionCodeFailure: { + NSMutableDictionary *failResult = [NSMutableDictionary dictionaryWithDictionary:result]; + [failResult setObject:failErrMsg forKey:@"errMsg"]; + NSString *resultJsonString = [self fat_jsonStringFromDict:failResult]; + + NSString *js = [NSString stringWithFormat:@"FinChatJSBridge.webInvokeCallbackHandler('%@',%@)", callbackId, resultJsonString]; + [self callJS:js callback:nil]; + break; + } + default: + break; + } + }; + NSDictionary *param = [self fat_jsonObjectFromString:paramsString]; + if (webExtensionApis[command].isOld) { + webExtensionApis[command].deprecatedHandler(param, callbck); + } else { + FATAppletInfo *appletInfo = [[FATClient sharedClient] getAppletInfo:self.appletId]; + webExtensionApis[command].handler(appletInfo, param, callbck); + } + return; + } + + NSMutableDictionary *failResult = [NSMutableDictionary dictionary]; + NSString *failErrMsg = [NSString stringWithFormat:@"%@:fail 该api未实现", command]; + [failResult setObject:failErrMsg forKey:@"errMsg"]; + NSString *resultJsonString = [self fat_jsonStringFromDict:failResult]; + + NSString *js = [NSString stringWithFormat:@"FinChatJSBridge.webInvokeCallbackHandler('%@',%@)", callbackId, resultJsonString]; + [self callJS:js callback:nil]; +} + +- (void)webPublishHandler:(NSDictionary *)data { +} + +#pragma mark - KVO +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if ([keyPath isEqual:@"estimatedProgress"] && object == self.webView) { + [self.progressView setAlpha:1.0f]; + [self.progressView setProgress:self.webView.estimatedProgress animated:YES]; + if (self.webView.estimatedProgress >= 1.0f) { + [UIView animateWithDuration:0.3 delay:0.3 options:UIViewAnimationOptionCurveEaseOut animations:^{ + [self.progressView setAlpha:0.0f]; + } completion:^(BOOL finished) { + [self.progressView setProgress:0.0f animated:YES]; + }]; + } + } else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } +} + +//MARK: - TOOL METHOD +- (NSString *)fat_jsonStringFromDict:(NSDictionary *)dict { + NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:nil]; + if (!data) { + return nil; + } + + NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + return jsonString; +} + +- (id)fat_jsonObjectFromString:(NSString *)string { + NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; + + if (!data) { + return nil; + } + + id object = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil]; + return object; +} + +//MARK: - WKScriptMessageHandler +- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { + NSString *name = message.name; + id body = message.body; + + if ([name isEqualToString:@"webInvokeHandler"]) { + [self webInvokeHandler:body]; + } else if ([name isEqualToString:@"webPublishHandler"]) { + [self webPublishHandler:body]; + } +} + +//MARK: - WKNavigationDelegate +- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { + decisionHandler(WKNavigationActionPolicyAllow); +} + +@end