From 5ec4db151a8d824e8f442b647f063da309bc0667 Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Sat, 31 Oct 2026 13:29:45 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(ios):=20=E4=BF=AE=E5=A4=8D=E5=88=86?= =?UTF-8?q?=E4=BA=AB=E9=9D=A2=E6=9D=BF=E5=B9=B6=E5=90=8C=E6=AD=A5=E4=BA=92?= =?UTF-8?q?=E5=8A=A8=E6=A0=8F=E5=B8=83=E5=B1=80=E6=96=AD=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Runner/AppDelegate.swift | 54 ++++++++++++++++--- .../player/widgets/home_feed_overlay.dart | 6 ++- test/home/home_preview_layout_test.dart | 2 +- 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 998004a..ce641d0 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -386,15 +386,29 @@ final class PipShadowCoordinator: NSObject, AVPictureInPictureControllerDelegate guard call.method == "shareText", let arguments = call.arguments as? [String: Any], let text = arguments["text"] as? String, - !text.isEmpty, - let controller = self?.window?.rootViewController else { + !text.isEmpty else { result(FlutterError(code: "share_unavailable", message: "Share sheet is unavailable", details: nil)) return } - let activity = UIActivityViewController(activityItems: [text], applicationActivities: nil) - activity.popoverPresentationController?.sourceView = controller.view - activity.completionWithItemsHandler = { _, _, _, _ in result(nil) } - controller.present(activity, animated: true) + DispatchQueue.main.async { + guard let controller = self?.sharePresenter() else { + result(FlutterError(code: "share_unavailable", message: "Share sheet is unavailable", details: nil)) + return + } + let activity = UIActivityViewController(activityItems: [text], applicationActivities: nil) + if let popover = activity.popoverPresentationController { + popover.sourceView = controller.view + popover.sourceRect = CGRect( + x: controller.view.bounds.midX, + y: controller.view.bounds.midY, + width: 0, + height: 0 + ) + popover.permittedArrowDirections = [] + } + activity.completionWithItemsHandler = { _, _, _, _ in result(nil) } + controller.present(activity, animated: true) + } } let thumbnailChannel = FlutterMethodChannel( @@ -459,6 +473,34 @@ final class PipShadowCoordinator: NSObject, AVPictureInPictureControllerDelegate } } + /// 当前前台 Scene 的根控制器之上,最适合展示系统分享面板的控制器。 + private func sharePresenter() -> UIViewController? { + let keyWindow = UIApplication.shared.connectedScenes + .compactMap { $0 as? UIWindowScene } + .first { $0.activationState == .foregroundActive }? + .windows.first { $0.isKeyWindow } + guard let rootController = keyWindow?.rootViewController else { return nil } + let presenter = topPresentedController(from: rootController) + guard presenter.viewIfLoaded?.window != nil else { return nil } + return presenter + } + + /// 解析已展示页面、导航栈和标签页中的当前可见控制器。 + private func topPresentedController(from controller: UIViewController) -> UIViewController { + if let presented = controller.presentedViewController { + return topPresentedController(from: presented) + } + if let navigation = controller as? UINavigationController, + let visible = navigation.visibleViewController { + return topPresentedController(from: visible) + } + if let tabs = controller as? UITabBarController, + let selected = tabs.selectedViewController { + return topPresentedController(from: selected) + } + return controller + } + /// iOS 画中画:影子播放器通道,与 Android 共用同一 channel 名。 private func registerPipChannel(_ engineBridge: FlutterImplicitEngineBridge) { let channel = FlutterMethodChannel( diff --git a/lib/pages/player/widgets/home_feed_overlay.dart b/lib/pages/player/widgets/home_feed_overlay.dart index 4331df2..a85f3b4 100644 --- a/lib/pages/player/widgets/home_feed_overlay.dart +++ b/lib/pages/player/widgets/home_feed_overlay.dart @@ -27,6 +27,7 @@ const double _progressHeight = 2; const double _progressHitHeight = 16; const double _pageHorizontal = 20; const double _railRight = 14; +const double _railShiftRight = 8; const double _railGap = 12; const double _railWidth = 56; const double _infoWatchAllGap = 2; @@ -234,7 +235,10 @@ class _HomeFeedOverlayState extends State { padding: const EdgeInsets.only( bottom: _railBottomOffset, ), - child: _rail(), + child: Transform.translate( + offset: const Offset(_railShiftRight, 0), + child: _rail(), + ), ), ), ], diff --git a/test/home/home_preview_layout_test.dart b/test/home/home_preview_layout_test.dart index 6e94a94..a0429f6 100644 --- a/test/home/home_preview_layout_test.dart +++ b/test/home/home_preview_layout_test.dart @@ -373,7 +373,7 @@ void main() { expect( tester.getRect(find.byKey(const Key('home-feed-action-rail'))).right, closeTo( - tester.getRect(find.byKey(const Key('home-feed-gesture'))).right - 14, + tester.getRect(find.byKey(const Key('home-feed-gesture'))).right - 6, 0.1, ), ); From a3ac007759acba6275877238bfe793a5d79f752f Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Sat, 31 Oct 2026 14:00:33 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(config):=20release=20=E6=9E=84=E5=BB=BA?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E4=BD=BF=E7=94=A8=E7=94=9F=E4=BA=A7=E7=8E=AF?= =?UTF-8?q?=E5=A2=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/config/app_config.dart | 10 +++++----- test/core/app_config_environment_test.dart | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 test/core/app_config_environment_test.dart diff --git a/lib/core/config/app_config.dart b/lib/core/config/app_config.dart index 0727c39..9fc3f53 100644 --- a/lib/core/config/app_config.dart +++ b/lib/core/config/app_config.dart @@ -1,5 +1,7 @@ // Author: fruit.com.cn +import 'package:flutter/foundation.dart'; + import 'dev_config.dart'; import 'prod_config.dart'; @@ -9,12 +11,10 @@ import 'prod_config.dart'; /// - `dev_config.dart`:[DevConfig](本机联调) /// - `prod_config.dart`:[ProdConfig](生产) /// -/// 切换环境只改下面这一个布尔值,不用传任何构建参数、也不做网络探测。 +/// Debug 使用开发环境,Release 自动使用生产环境,不做网络探测。 abstract final class AppConfig { - /// **环境开关**:true 用开发环境,false 用生产环境。 - /// - /// 打生产包前必须改成 false;release 构建若仍为 true 会在启动时直接报错。 - static const bool useDevConfig = true; + /// **环境开关**:Debug 使用开发环境,Release 使用生产环境。 + static const bool useDevConfig = !kReleaseMode; /// 当前选中的环境地址。 static String get baseUrl => diff --git a/test/core/app_config_environment_test.dart b/test/core/app_config_environment_test.dart new file mode 100644 index 0000000..2cce7e2 --- /dev/null +++ b/test/core/app_config_environment_test.dart @@ -0,0 +1,18 @@ +// Author: fruit.com.cn + +// 应用环境选择测试。 +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:fruitshorts/core/config/app_config.dart'; +import 'package:fruitshorts/core/config/dev_config.dart'; +import 'package:fruitshorts/core/config/prod_config.dart'; + +void main() { + test('should_use_environment_matching_build_mode', () { + expect(AppConfig.useDevConfig, !kReleaseMode); + expect( + AppConfig.baseUrl, + kReleaseMode ? ProdConfig.baseUrl : DevConfig.baseUrl, + ); + }); +}