diff --git a/lib/modules/translation/controllers/translation_controller.dart b/lib/modules/translation/controllers/translation_controller.dart index 56d64f1d2..994d1c109 100644 --- a/lib/modules/translation/controllers/translation_controller.dart +++ b/lib/modules/translation/controllers/translation_controller.dart @@ -61,6 +61,10 @@ class TranslationController extends GetxController with WidgetsBindingObserver { late final Pip _pip; late final NativePlugin _nativePlugin; int _pipContentView = 0; + static const int _pipPreferredWidth = 375; + static const int _pipCompactPreferredHeight = 72; + static const int _pipExpandedPreferredHeight = 220; + bool _isPipExpanded = false; // ==================== 悬浮窗相关 ==================== final isFloatingWindowEnabled = false.obs; // 悬浮窗状态 @@ -174,7 +178,10 @@ class TranslationController extends GetxController with WidgetsBindingObserver { isFloatingWindowEnabled.value = state == PipState.pipStateStarted; if (state == PipState.pipStateFailed) { + _isPipExpanded = false; _pip.dispose(); + } else if (state == PipState.pipStateStopped) { + _isPipExpanded = false; } }, ), @@ -190,6 +197,7 @@ class TranslationController extends GetxController with WidgetsBindingObserver { if (Platform.isAndroid) { await _disableFloatingWindow(); } else if (Platform.isIOS) { + _isPipExpanded = false; await _pip.dispose(); } } else { @@ -201,14 +209,11 @@ class TranslationController extends GetxController with WidgetsBindingObserver { } } - Future _startPip() async { - if (_pipContentView == 0) { - _pipContentView = await _nativePlugin.createPipContentView(); - } - final options = PipOptions( + PipOptions _buildIosPipOptions({required int preferredHeight}) { + return PipOptions( autoEnterEnabled: true, - aspectRatioX: 375, - aspectRatioY: 400, + aspectRatioX: _pipPreferredWidth, + aspectRatioY: preferredHeight, sourceRectHintLeft: 0, sourceRectHintTop: 0, sourceRectHintRight: 0, @@ -217,12 +222,19 @@ class TranslationController extends GetxController with WidgetsBindingObserver { useExternalStateMonitor: true, externalStateMonitorInterval: 100, contentView: _pipContentView, - preferredContentWidth: 375, - preferredContentHeight: 400, + preferredContentWidth: _pipPreferredWidth, + preferredContentHeight: preferredHeight, controlStyle: 2, ); + } - await _pip.setup(options); + Future _startPip() async { + if (_pipContentView == 0) { + _pipContentView = await _nativePlugin.createPipContentView(); + } + _isPipExpanded = false; + await _pip.setup( + _buildIosPipOptions(preferredHeight: _pipCompactPreferredHeight)); await _pip.start(); } @@ -297,6 +309,17 @@ class TranslationController extends GetxController with WidgetsBindingObserver { ); await _floatingWindowManager.updateFloatingWindowContent(data); } else if (Platform.isIOS) { + final hasContent = item.sourceText.trim().isNotEmpty || + item.translatedText.trim().isNotEmpty; + if (hasContent && !_isPipExpanded) { + final ok = await _pip.setup( + _buildIosPipOptions(preferredHeight: _pipExpandedPreferredHeight)); + if (ok) _isPipExpanded = true; + } else if (!hasContent && _isPipExpanded) { + final ok = await _pip.setup( + _buildIosPipOptions(preferredHeight: _pipCompactPreferredHeight)); + if (ok) _isPipExpanded = false; + } await _nativePlugin.updatePipContentView({ 'viewId': _pipContentView, 'cn': item.sourceText, diff --git a/local_plugins/native_plugin/ios/Classes/PlayerView.swift b/local_plugins/native_plugin/ios/Classes/PlayerView.swift index 98e624768..a0d690191 100644 --- a/local_plugins/native_plugin/ios/Classes/PlayerView.swift +++ b/local_plugins/native_plugin/ios/Classes/PlayerView.swift @@ -11,6 +11,9 @@ public class PlayerView: UIView { private let langRow: UIStackView = UIStackView() private var lastBoundsSize: CGSize = .zero private var lastLangText: String? = nil + private var isExpanded: Bool = false + private let compactPreferredHeight: CGFloat = 72 + private let expandedPreferredHeight: CGFloat = 220 public override init(frame: CGRect) { stackView = UIStackView() @@ -73,12 +76,14 @@ public class PlayerView: UIView { cnLabel.textAlignment = .left cnLabel.numberOfLines = 0 cnLabel.lineBreakMode = .byTruncatingTail + cnLabel.isHidden = true enLabel.font = .systemFont(ofSize: 15) enLabel.textColor = UIColor(red: 144.0 / 255.0, green: 202.0 / 255.0, blue: 249.0 / 255.0, alpha: 1.0) enLabel.textAlignment = .left enLabel.numberOfLines = 0 enLabel.lineBreakMode = .byTruncatingTail + enLabel.isHidden = true let para = NSMutableParagraphStyle() para.lineSpacing = 2 @@ -110,7 +115,7 @@ public class PlayerView: UIView { stackView.addArrangedSubview(cnLabel) stackView.addArrangedSubview(enLabel) - layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16) + layoutMargins = UIEdgeInsets(top: 10, left: 16, bottom: 10, right: 16) addSubview(stackView) let guide = layoutMarginsGuide @@ -141,11 +146,22 @@ public class PlayerView: UIView { if let en = en { enLabel.attributedText = NSAttributedString(string: en, attributes: [.paragraphStyle: para]) } + let currentCn = cnLabel.attributedText?.string ?? "" + let currentEn = enLabel.attributedText?.string ?? "" + let hasContent = + !currentCn.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + || !currentEn.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + setExpanded(hasContent) setNeedsLayout() } + public override var intrinsicContentSize: CGSize { + CGSize(width: UIView.noIntrinsicMetric, height: isExpanded ? expandedPreferredHeight : compactPreferredHeight) + } + public override func layoutSubviews() { super.layoutSubviews() + layer.cornerRadius = isExpanded ? 16 : 18 layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius).cgPath let currentLangText = langLabel.text if bounds.size != lastBoundsSize || currentLangText != lastLangText { @@ -155,6 +171,17 @@ public class PlayerView: UIView { } } + private func setExpanded(_ expanded: Bool) { + if expanded == isExpanded { return } + isExpanded = expanded + cnLabel.isHidden = !expanded + enLabel.isHidden = !expanded + layoutMargins = expanded + ? UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16) + : UIEdgeInsets(top: 10, left: 16, bottom: 10, right: 16) + invalidateIntrinsicContentSize() + } + private func updateDynamicTextLineLimit() { let fittingWidth = max(0, bounds.width - layoutMargins.left - layoutMargins.right) if fittingWidth <= 0 { return } @@ -182,7 +209,7 @@ public class PlayerView: UIView { let enLineHeight = enLabel.font.lineHeight * 1.1 + 2 let combinedLineHeight = cnLineHeight + enLineHeight - let perLabelLines = max(1, min(Int(availableHeight / combinedLineHeight), 6)) + let perLabelLines = max(1, min(Int(availableHeight / combinedLineHeight), 10)) if perLabelLines != lastDynamicTextLines { lastDynamicTextLines = perLabelLines