Commit da6639bf by ludi

初步接入flutter,主要观察一下FUSFuSiWebViewEventHelper有没有问题

parent 38a4d1e3
Showing with 444 additions and 4 deletions
...@@ -26,6 +26,10 @@ Pod::Spec.new do |s| ...@@ -26,6 +26,10 @@ Pod::Spec.new do |s|
s.static_framework = true s.static_framework = true
s.source_files = ['FUSCommon/Classes/**/*.{m,h,swift}','FUSCommon/FUSRouter/**/*.{m,h,swift}', 'FUSCommon/FUSCommon.h'] s.source_files = ['FUSCommon/Classes/**/*.{m,h,swift}','FUSCommon/FUSRouter/**/*.{m,h,swift}', 'FUSCommon/FUSCommon.h']
s.vendored_frameworks = [
'FUSCommon/Vendor/Flutter/Flutter.xcframework',
'FUSCommon/Vendor/Flutter/App.xcframework'
]
s.resource_bundles = { s.resource_bundles = {
'FUSCommonBundle' => ['FUSCommon/Assets/*.xcassets', 'FUSCommonBundle' => ['FUSCommon/Assets/*.xcassets',
'FUSCommon/Assets/*.plist', 'FUSCommon/Assets/*.plist',
......
import Flutter
import UIKit
/// Flutter 页面容器控制器。
///
/// 用于承接 Flutter 页面展示、路由进入和页面退出时的引擎清理。
@objcMembers
open class FUSFlutterViewController: FlutterViewController {
/// Flutter 页面将要出现通知。
public static let flutterPageWillAppearNotification = Notification.Name("FUSFlutterPageWillAppear")
/// Flutter 页面退出通知。
public static let flutterPageDidExitNotification = Notification.Name("FUSFlutterPageDidExit")
/// 商城常驻页专用引擎标识。
public static let storeDedicatedEngineIdentifier = "store_tab_dedicated"
/// 页面关闭后的回调。
public var closeHandler: (() -> Void)?
/// 当前页面是否在 getUserData 中携带 showHead=1。
public var showHeadInUserData: Bool = false
/// 当前页面点击来源链接。
public var clickUrl: String = ""
/// 是否隐藏导航栏。
public var hideWebTitleBar: Bool = false {
didSet {
navigationController?.setNavigationBarHidden(hideWebTitleBar, animated: true)
}
}
/// 是否允许系统侧滑返回。
public var isSideSwipeEnabled: Bool = true
/// 当前页面关联的专用引擎标识。
private let dedicatedEngineIdentifier: String?
/// 页面退出时是否销毁关联引擎。
private let resetEngineOnExit: Bool
/// 使用共享引擎初始化 Flutter 页面。
///
/// - Parameter route: 目标路由。
public init(route: String? = nil) {
dedicatedEngineIdentifier = nil
resetEngineOnExit = true
let routeUrl = route ?? ""
let isReuseEngine = FUSFlutterEngineFactory.shared.engine != nil
FUSFlutterEngineFactory.shared.startEngine(initialRoute: isReuseEngine ? nil : route)
guard let engine = FUSFlutterEngineFactory.shared.engine else {
fatalError("Flutter engine failed to start")
}
super.init(engine: engine, nibName: nil, bundle: nil)
clickUrl = routeUrl
if isReuseEngine, let route = route {
engine.navigationChannel.invokeMethod("pushRoute", arguments: route)
}
modalPresentationStyle = .fullScreen
}
/// 使用专用引擎初始化 Flutter 页面。
///
/// - Parameters:
/// - route: 目标路由。
/// - dedicatedEngineIdentifier: 专用引擎标识。
/// - keepEngineAlive: 页面退出后是否保活引擎。
public init(route: String? = nil, dedicatedEngineIdentifier: String, keepEngineAlive: Bool) {
self.dedicatedEngineIdentifier = dedicatedEngineIdentifier
resetEngineOnExit = !keepEngineAlive
let routeUrl = route ?? ""
let existedEngine = FUSFlutterEngineFactory.shared.dedicatedEngine(identifier: dedicatedEngineIdentifier)
let engine = FUSFlutterEngineFactory.shared.startDedicatedEngine(identifier: dedicatedEngineIdentifier,
initialRoute: existedEngine == nil ? route : nil)
super.init(engine: engine, nibName: nil, bundle: nil)
clickUrl = routeUrl
if existedEngine != nil, keepEngineAlive == false, let route = route {
engine.navigationChannel.invokeMethod("pushRoute", arguments: route)
}
modalPresentationStyle = .fullScreen
}
/// Storyboard 初始化入口。
///
/// 当前不支持从 storyboard/xib 初始化。
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
open override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.post(name: Self.flutterPageWillAppearNotification,
object: self,
userInfo: ["showHeadInUserData": showHeadInUserData,
"clickUrl": clickUrl])
navigationController?.setNavigationBarHidden(hideWebTitleBar, animated: animated)
}
open override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
applySideSwipeGestureState()
}
/// 更新当前页面的侧滑返回开关。
///
/// - Parameter enabled: 是否允许侧滑返回。
public func updateSideSwipeEnabled(_ enabled: Bool) {
isSideSwipeEnabled = enabled
applySideSwipeGestureState()
}
/// 应用当前页面的侧滑返回状态。
private func applySideSwipeGestureState() {
guard let navigationController = navigationController,
let popGesture = navigationController.interactivePopGestureRecognizer else {
return
}
popGesture.delegate = nil
popGesture.isEnabled = isSideSwipeEnabled && navigationController.viewControllers.count > 1
}
open override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
if isMovingFromParent || isBeingDismissed {
NotificationCenter.default.post(name: Self.flutterPageDidExitNotification, object: self)
if resetEngineOnExit {
if let dedicatedEngineIdentifier = dedicatedEngineIdentifier {
FUSFlutterEngineFactory.shared.resetDedicatedEngine(identifier: dedicatedEngineIdentifier)
} else {
FUSFlutterEngineFactory.shared.resetEngine()
}
}
closeHandler?()
}
}
}
import Flutter
import UIKit
/// FlutterEngine 工厂。
///
/// 用于统一管理 FlutterEngine 的创建、复用与销毁。
@objcMembers
public class FUSFlutterEngineFactory: NSObject {
/// 全局单例实例。
public static let shared = FUSFlutterEngineFactory()
/// 当前全局复用的 FlutterEngine。
public var engine: FlutterEngine?
/// 引擎启动后的外部配置回调。
///
/// 主工程可通过该回调安装 MethodChannel 或其他业务插件。
public var engineConfigurator: ((FlutterEngine) -> Void)?
/// 按标识复用的专用引擎容器。
private var dedicatedEngineMap: [String: FlutterEngine] = [:]
/// 私有初始化方法。
private override init() {
super.init()
}
/// 启动或复用全局 FlutterEngine。
///
/// - Parameter initialRoute: 首次创建引擎时使用的初始路由。
public func startEngine(initialRoute: String? = nil) {
if let engine = engine {
engineConfigurator?(engine)
return
}
let newEngine = FlutterEngine(name: "fus_flutter_engine")
if let initialRoute = initialRoute, initialRoute.isEmpty == false {
newEngine.run(withEntrypoint: nil, initialRoute: initialRoute)
} else {
newEngine.run()
}
engine = newEngine
engineConfigurator?(newEngine)
}
/// 销毁并重置全局 FlutterEngine。
public func resetEngine() {
if let engine = engine {
engine.destroyContext()
}
engine = nil
}
/// 启动或复用指定标识的专用 FlutterEngine。
///
/// - Parameters:
/// - identifier: 专用引擎标识。
/// - initialRoute: 首次创建引擎时使用的初始路由。
/// - Returns: 可用的 FlutterEngine。
@discardableResult
public func startDedicatedEngine(identifier: String, initialRoute: String? = nil) -> FlutterEngine {
if let engine = dedicatedEngineMap[identifier] {
engineConfigurator?(engine)
return engine
}
let newEngine = FlutterEngine(name: "fus_flutter_engine_\(identifier)")
if let initialRoute = initialRoute, initialRoute.isEmpty == false {
newEngine.run(withEntrypoint: nil, initialRoute: initialRoute)
} else {
newEngine.run()
}
dedicatedEngineMap[identifier] = newEngine
engineConfigurator?(newEngine)
return newEngine
}
/// 获取指定标识的专用 FlutterEngine。
///
/// - Parameter identifier: 专用引擎标识。
/// - Returns: 已创建的专用引擎,没有则返回 `nil`。
public func dedicatedEngine(identifier: String) -> FlutterEngine? {
return dedicatedEngineMap[identifier]
}
/// 销毁并重置指定标识的专用 FlutterEngine。
///
/// - Parameter identifier: 专用引擎标识。
public func resetDedicatedEngine(identifier: String) {
if let engine = dedicatedEngineMap[identifier] {
engine.destroyContext()
dedicatedEngineMap.removeValue(forKey: identifier)
}
}
}
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#import "FUSCacheDataShare.h" #import "FUSCacheDataShare.h"
#import "FUSRouter.h" #import "FUSRouter.h"
#import <FUSCommon/FUSCommon-Swift.h>
static const NSString *FUSCidUDKey = @"FUSCidUDKey"; static const NSString *FUSCidUDKey = @"FUSCidUDKey";
...@@ -568,6 +569,7 @@ static const NSString *FUSCidUDKey = @"FUSCidUDKey"; ...@@ -568,6 +569,7 @@ static const NSString *FUSCidUDKey = @"FUSCidUDKey";
FUSConfig.sharedInstanced.liveConfigs = [[FUSFuSiLiveConfig alloc] init]; FUSConfig.sharedInstanced.liveConfigs = [[FUSFuSiLiveConfig alloc] init];
FUSConfig.sharedInstanced.webLoginConfig = [[FUSFuSiWebThirdLoginConfig alloc] init]; FUSConfig.sharedInstanced.webLoginConfig = [[FUSFuSiWebThirdLoginConfig alloc] init];
FUSConfig.sharedInstanced.webConfig = [[FUSFuSiWebConfig alloc] init]; FUSConfig.sharedInstanced.webConfig = [[FUSFuSiWebConfig alloc] init];
[FUSFlutterBridge install];
} }
@end @end
...@@ -109,9 +109,33 @@ typedef NS_ENUM(NSUInteger,FUSJsWebCidType) { ...@@ -109,9 +109,33 @@ typedef NS_ENUM(NSUInteger,FUSJsWebCidType) {
FUSJsWebCidTypeReceiveWebServiceChatMessage = 45, FUSJsWebCidTypeReceiveWebServiceChatMessage = 45,
/// 保存照片到本地 /// 保存照片到本地
FUSJsWebCidTypeSaveWebImageToLocal = 46, FUSJsWebCidTypeSaveWebImageToLocal = 46,
/// 无论在哪都是打开全屏的任务中心
FUSJsWebCidTypeOpenFullScreenTaskCenter = 53,
/// 返回上一级
FUSJsWebCidTypePopToInfo = 56,
/// 控制 Flutter 页面是否允许系统侧滑返回
FUSJsWebCidTypeControlFlutterSideSwipe = 58,
}; };
@interface FUSFuSiWebViewEventHelper : FUSWebViewEventHelper @interface FUSFuSiWebViewEventHelper : FUSWebViewEventHelper
/// Flutter 场景下的异步结果回调处理器。
@property (nonatomic, copy, nullable) void(^flutterCallbackHandler)(NSString *reqCode, NSDictionary *payload);
/// 将 Flutter 发起的 CID 事件转发到现有 Web 事件处理链。
/// @param cid 事件编号
/// @param data 事件附带数据
/// @param reqCode 请求编号,用于异步回调 Flutter
/// @param fallbackWebView 用于复用现有 Web 事件处理链的占位 WebView
- (BOOL)fus_forwardFlutterCid:(NSInteger)cid
data:(NSDictionary *)data
reqCode:(NSString * _Nullable)reqCode
fallbackWebView:(FUSWKWebView *)fallbackWebView;
/// 处理 JumpCollection 类型的通用跳转。
- (void)fus_dealTypeJumpCollectionWithWebViewEventWithInfo:(NSDictionary *)dataDict;
/// 调起微信登录。
- (void)fus_doWechatLoginWithWkVC:(UIViewController *)wkVC;
@end @end
...@@ -31,6 +31,8 @@ ...@@ -31,6 +31,8 @@
#import <FirebaseAnalytics/FirebaseAnalytics.h> #import <FirebaseAnalytics/FirebaseAnalytics.h>
#import "FUSLivePublicDefine.h" #import "FUSLivePublicDefine.h"
#import "FUSDataStatisticsManager.h" #import "FUSDataStatisticsManager.h"
#import <FUSCommon/FUSCommon-Swift.h>
#import <objc/runtime.h>
static NSString *FUSWebRightBtnExtraInfoKey = @"FUSWebRightBtnExtraInfoKey"; //官方认证的key static NSString *FUSWebRightBtnExtraInfoKey = @"FUSWebRightBtnExtraInfoKey"; //官方认证的key
...@@ -78,7 +80,15 @@ NSString * const kEVENT_RECHARGE_FIRST_RECHARGE_WINDOW_RECEIVE_OFFICIAL_RECAHARG ...@@ -78,7 +80,15 @@ NSString * const kEVENT_RECHARGE_FIRST_RECHARGE_WINDOW_RECEIVE_OFFICIAL_RECAHARG
// 与网页交互 // 与网页交互
if ([message.name isEqualToString:@"appCollaboration"]) { if ([message.name isEqualToString:@"appCollaboration"]) {
NSDictionary *dict = [[message.body description] converToDictionary]; NSDictionary *dict = [[message.body description] converToDictionary];
[self fus_dealWithWebViewEventWithWkController:[UIViewController fus_topViewController] webView:self.webview info:dict]; NSInteger cid = [[dict[@"cid"] description] integerValue];
NSDictionary *dataDict = [dict[@"data"] isKindOfClass:NSDictionary.class] ? dict[@"data"] : @{};
NSString *reqCode = [dict[@"reqCode"] description];
NSDictionary *eventInfo = @{
@"cid": @(cid),
@"data": dataDict ?: @{},
@"reqCode": reqCode ?: @""
};
[self fus_dealWithWebViewEventWithWkController:self.wkVC webView:self.webview info:eventInfo];
} }
} }
...@@ -210,8 +220,44 @@ NSString * const kEVENT_RECHARGE_FIRST_RECHARGE_WINDOW_RECEIVE_OFFICIAL_RECAHARG ...@@ -210,8 +220,44 @@ NSString * const kEVENT_RECHARGE_FIRST_RECHARGE_WINDOW_RECEIVE_OFFICIAL_RECAHARG
[self.shareView fus_dismissSelf]; [self.shareView fus_dismissSelf];
} }
- (BOOL)fus_forwardFlutterCid:(NSInteger)cid
data:(NSDictionary *)data
reqCode:(NSString * _Nullable)reqCode
fallbackWebView:(FUSWKWebView *)fallbackWebView {
if (cid <= 0) {
return NO;
}
UIViewController *topViewController = [UIViewController fus_topViewController];
if (fallbackWebView == nil || topViewController == nil) {
return NO;
}
NSDictionary *eventInfo = @{
@"cid": @(cid),
@"data": [data isKindOfClass:NSDictionary.class] ? data : @{},
@"reqCode": reqCode ?: @""
};
[self fus_dealWithWebViewEventWithWkController:topViewController webView:fallbackWebView info:eventInfo];
return YES;
}
#pragma mark - Private #pragma mark - Private
- (void)fus_callbackToWebOrFlutterWithReqCode:(NSString *)reqCode
payload:(NSDictionary *)payload
webView:(FUSWKWebView *)webView {
if (self.flutterCallbackHandler && reqCode.length > 0) {
self.flutterCallbackHandler(reqCode, payload ?: @{});
return;
}
if (webView == nil) {
return;
}
NSDictionary *webDict = @{@"reqCode":reqCode ?: @"",
@"result":payload ?: @{}};
[webView.wkWebView evaluateJavaScript:[NSString stringWithFormat:@"appCallback(%@)",[webDict converToString]]
completionHandler:nil];
}
/** /**
处理 webView 里面的点击事件 处理 webView 里面的点击事件
...@@ -426,8 +472,9 @@ NSString * const kEVENT_RECHARGE_FIRST_RECHARGE_WINDOW_RECEIVE_OFFICIAL_RECAHARG ...@@ -426,8 +472,9 @@ NSString * const kEVENT_RECHARGE_FIRST_RECHARGE_WINDOW_RECEIVE_OFFICIAL_RECAHARG
break; break;
} }
// 调用网页的一个js方法 // 调用网页的一个js方法
NSDictionary *webDict = @{@"reqCode":timeStamp,@"result":isAppInstall}; [self fus_callbackToWebOrFlutterWithReqCode:timeStamp
[webView.wkWebView evaluateJavaScript:[NSString stringWithFormat:@"appCallback(%@)",[webDict converToString]] completionHandler:^(id someThing, NSError *error) {}]; payload:@{@"result":isAppInstall}
webView:webView];
break; break;
} }
case FUSJsWebCidChangeTiltle: { case FUSJsWebCidChangeTiltle: {
...@@ -597,6 +644,10 @@ NSString * const kEVENT_RECHARGE_FIRST_RECHARGE_WINDOW_RECEIVE_OFFICIAL_RECAHARG ...@@ -597,6 +644,10 @@ NSString * const kEVENT_RECHARGE_FIRST_RECHARGE_WINDOW_RECEIVE_OFFICIAL_RECAHARG
if (webView.rechargeHandler) { if (webView.rechargeHandler) {
webView.rechargeHandler(YES, @""); webView.rechargeHandler(YES, @"");
} }
[self fus_callbackToWebOrFlutterWithReqCode:timeStamp
payload:@{@"success":@(1),
@"msg":@""}
webView:webView];
NSDictionary *succeedAlert = dataDict[@"succeedAlert"]; NSDictionary *succeedAlert = dataDict[@"succeedAlert"];
if (![NSDictionary isNull:succeedAlert]) { if (![NSDictionary isNull:succeedAlert]) {
...@@ -635,6 +686,10 @@ NSString * const kEVENT_RECHARGE_FIRST_RECHARGE_WINDOW_RECEIVE_OFFICIAL_RECAHARG ...@@ -635,6 +686,10 @@ NSString * const kEVENT_RECHARGE_FIRST_RECHARGE_WINDOW_RECEIVE_OFFICIAL_RECAHARG
if (webView.rechargeHandler) { if (webView.rechargeHandler) {
webView.rechargeHandler(NO, msg); webView.rechargeHandler(NO, msg);
} }
[self fus_callbackToWebOrFlutterWithReqCode:timeStamp
payload:@{@"success":@(0),
@"msg":msg ?: @""}
webView:webView];
if ([[UIViewController fus_topViewController] isKindOfClass:NSClassFromString(@"FUSLiveMainViewController")]) { if ([[UIViewController fus_topViewController] isKindOfClass:NSClassFromString(@"FUSLiveMainViewController")]) {
[trackEventDict setObject:@"rechargefail" forKey:@"result"]; [trackEventDict setObject:@"rechargefail" forKey:@"result"];
[FUSTalkingData fus_trackEvent:kEVENT_RECHARGE_FIRST_RECHARGE_WINDOW_RECEIVE_OFFICIAL_RECAHARGE label:@"" parameters:trackEventDict]; [FUSTalkingData fus_trackEvent:kEVENT_RECHARGE_FIRST_RECHARGE_WINDOW_RECEIVE_OFFICIAL_RECAHARGE label:@"" parameters:trackEventDict];
...@@ -968,7 +1023,11 @@ NSString * const kEVENT_RECHARGE_FIRST_RECHARGE_WINDOW_RECEIVE_OFFICIAL_RECAHARG ...@@ -968,7 +1023,11 @@ NSString * const kEVENT_RECHARGE_FIRST_RECHARGE_WINDOW_RECEIVE_OFFICIAL_RECAHARG
NSString *openId = [infoDict objectForKey:FUSSocializedFetchOpenIdKey]; NSString *openId = [infoDict objectForKey:FUSSocializedFetchOpenIdKey];
NSString *appCallback = [dataDict[@"callBackName"] description]; NSString *appCallback = [dataDict[@"callBackName"] description];
// [webView.wkWebView evaluateJavaScript:[NSString stringWithFormat:@"appCallback(%@)",[webDict converToString]] // [webView.wkWebView evaluateJavaScript:[NSString stringWithFormat:@"appCallback(%@)",[webDict converToString]]
if (weakSelf.flutterCallbackHandler && timeStamp.length > 0) {
weakSelf.flutterCallbackHandler(timeStamp, @{@"openId":openId ?: @""});
} else {
[webView.wkWebView evaluateJavaScript:[NSString stringWithFormat:@"%@(\"%@\")",appCallback, openId] completionHandler:nil]; [webView.wkWebView evaluateJavaScript:[NSString stringWithFormat:@"%@(\"%@\")",appCallback, openId] completionHandler:nil];
}
} failure:^(NSError *error) { } failure:^(NSError *error) {
...@@ -1439,4 +1498,3 @@ NSString * const kEVENT_RECHARGE_FIRST_RECHARGE_WINDOW_RECEIVE_OFFICIAL_RECAHARG ...@@ -1439,4 +1498,3 @@ NSString * const kEVENT_RECHARGE_FIRST_RECHARGE_WINDOW_RECEIVE_OFFICIAL_RECAHARG
return FUSConfig.sharedInstanced.liveConfigs.isInRoom; return FUSConfig.sharedInstanced.liveConfigs.isInRoom;
} }
@end @end
...@@ -89,6 +89,15 @@ NS_ASSUME_NONNULL_BEGIN ...@@ -89,6 +89,15 @@ NS_ASSUME_NONNULL_BEGIN
allowScroll:(BOOL)allowScroll allowScroll:(BOOL)allowScroll
userInteractionEnabled:(BOOL)userInteractionEnabled; userInteractionEnabled:(BOOL)userInteractionEnabled;
/// 显示半屏网页,并在显示后异步判断是否切换为 Flutter 承接。
//- (void)fus_showHalfWebViewWithModel:(FUSHalfWebViewModel *)webModel
// needMouted:(BOOL)needMouted
// allowScroll:(BOOL)allowScroll
// userInteractionEnabled:(BOOL)userInteractionEnabled
// flutterRoute:(NSString * _Nullable)flutterRoute
// showHeadInUserData:(BOOL)showHeadInUserData
// completion:(void (^ _Nullable)(BOOL useFlutter, UIView * _Nullable halfWebView))completion;
-(void)fus_showConfigHalfWebView:(NSDictionary *)configDict -(void)fus_showConfigHalfWebView:(NSDictionary *)configDict
needMouted:(BOOL)needMouted needMouted:(BOOL)needMouted
allowScroll:(BOOL)allowScroll allowScroll:(BOOL)allowScroll
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>BinaryPath</key>
<string>App.framework/App</string>
<key>DebugSymbolsPath</key>
<string>dSYMs</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>App.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
<dict>
<key>BinaryPath</key>
<string>App.framework/App</string>
<key>LibraryIdentifier</key>
<string>ios-arm64_x86_64-simulator</string>
<key>LibraryPath</key>
<string>App.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>13.0</string>
</dict>
</plist>
[{"family":"MaterialIcons","fonts":[{"asset":"fonts/MaterialIcons-Regular.otf"}]},{"family":"Microsoft YaHei","fonts":[{"asset":"lib/core/font/msyh.ttf"}]},{"family":"packages/cupertino_icons/CupertinoIcons","fonts":[{"asset":"packages/cupertino_icons/assets/CupertinoIcons.ttf"}]}]
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>13.0</string>
</dict>
</plist>
[{"family":"MaterialIcons","fonts":[{"asset":"fonts/MaterialIcons-Regular.otf"}]},{"family":"Microsoft YaHei","fonts":[{"asset":"lib/core/font/msyh.ttf"}]},{"family":"packages/cupertino_icons/CupertinoIcons","fonts":[{"asset":"packages/cupertino_icons/assets/CupertinoIcons.ttf"}]}]
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment