Browse Source

feat(播放器): 添加可扩展的播放器视图和悬浮窗功能

实现播放器视图的扩展/折叠功能,根据内容自动调整高度
为iOS悬浮窗添加类似的可扩展功能,优化用户体验
weicu
lxm 9 months ago
parent
commit
c2c07b780f
  1. 43
      lib/modules/translation/controllers/translation_controller.dart
  2. 31
      local_plugins/native_plugin/ios/Classes/PlayerView.swift

43
lib/modules/translation/controllers/translation_controller.dart

@ -61,6 +61,10 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
late final Pip _pip; late final Pip _pip;
late final NativePlugin _nativePlugin; late final NativePlugin _nativePlugin;
int _pipContentView = 0; 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; // 悬浮窗状态 final isFloatingWindowEnabled = false.obs; // 悬浮窗状态
@ -174,7 +178,10 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
isFloatingWindowEnabled.value = state == PipState.pipStateStarted; isFloatingWindowEnabled.value = state == PipState.pipStateStarted;
if (state == PipState.pipStateFailed) { if (state == PipState.pipStateFailed) {
_isPipExpanded = false;
_pip.dispose(); _pip.dispose();
} else if (state == PipState.pipStateStopped) {
_isPipExpanded = false;
} }
}, },
), ),
@ -190,6 +197,7 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
if (Platform.isAndroid) { if (Platform.isAndroid) {
await _disableFloatingWindow(); await _disableFloatingWindow();
} else if (Platform.isIOS) { } else if (Platform.isIOS) {
_isPipExpanded = false;
await _pip.dispose(); await _pip.dispose();
} }
} else { } else {
@ -201,14 +209,11 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
} }
} }
Future<void> _startPip() async { PipOptions _buildIosPipOptions({required int preferredHeight}) {
if (_pipContentView == 0) { return PipOptions(
_pipContentView = await _nativePlugin.createPipContentView();
}
final options = PipOptions(
autoEnterEnabled: true, autoEnterEnabled: true,
aspectRatioX: 375, aspectRatioX: _pipPreferredWidth,
aspectRatioY: 400, aspectRatioY: preferredHeight,
sourceRectHintLeft: 0, sourceRectHintLeft: 0,
sourceRectHintTop: 0, sourceRectHintTop: 0,
sourceRectHintRight: 0, sourceRectHintRight: 0,
@ -217,12 +222,19 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
useExternalStateMonitor: true, useExternalStateMonitor: true,
externalStateMonitorInterval: 100, externalStateMonitorInterval: 100,
contentView: _pipContentView, contentView: _pipContentView,
preferredContentWidth: 375, preferredContentWidth: _pipPreferredWidth,
preferredContentHeight: 400, preferredContentHeight: preferredHeight,
controlStyle: 2, controlStyle: 2,
); );
}
await _pip.setup(options); Future<void> _startPip() async {
if (_pipContentView == 0) {
_pipContentView = await _nativePlugin.createPipContentView();
}
_isPipExpanded = false;
await _pip.setup(
_buildIosPipOptions(preferredHeight: _pipCompactPreferredHeight));
await _pip.start(); await _pip.start();
} }
@ -297,6 +309,17 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
); );
await _floatingWindowManager.updateFloatingWindowContent(data); await _floatingWindowManager.updateFloatingWindowContent(data);
} else if (Platform.isIOS) { } 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({ await _nativePlugin.updatePipContentView({
'viewId': _pipContentView, 'viewId': _pipContentView,
'cn': item.sourceText, 'cn': item.sourceText,

31
local_plugins/native_plugin/ios/Classes/PlayerView.swift

@ -11,6 +11,9 @@ public class PlayerView: UIView {
private let langRow: UIStackView = UIStackView() private let langRow: UIStackView = UIStackView()
private var lastBoundsSize: CGSize = .zero private var lastBoundsSize: CGSize = .zero
private var lastLangText: String? = nil 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) { public override init(frame: CGRect) {
stackView = UIStackView() stackView = UIStackView()
@ -73,12 +76,14 @@ public class PlayerView: UIView {
cnLabel.textAlignment = .left cnLabel.textAlignment = .left
cnLabel.numberOfLines = 0 cnLabel.numberOfLines = 0
cnLabel.lineBreakMode = .byTruncatingTail cnLabel.lineBreakMode = .byTruncatingTail
cnLabel.isHidden = true
enLabel.font = .systemFont(ofSize: 15) 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.textColor = UIColor(red: 144.0 / 255.0, green: 202.0 / 255.0, blue: 249.0 / 255.0, alpha: 1.0)
enLabel.textAlignment = .left enLabel.textAlignment = .left
enLabel.numberOfLines = 0 enLabel.numberOfLines = 0
enLabel.lineBreakMode = .byTruncatingTail enLabel.lineBreakMode = .byTruncatingTail
enLabel.isHidden = true
let para = NSMutableParagraphStyle() let para = NSMutableParagraphStyle()
para.lineSpacing = 2 para.lineSpacing = 2
@ -110,7 +115,7 @@ public class PlayerView: UIView {
stackView.addArrangedSubview(cnLabel) stackView.addArrangedSubview(cnLabel)
stackView.addArrangedSubview(enLabel) 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) addSubview(stackView)
let guide = layoutMarginsGuide let guide = layoutMarginsGuide
@ -141,11 +146,22 @@ public class PlayerView: UIView {
if let en = en { if let en = en {
enLabel.attributedText = NSAttributedString(string: en, attributes: [.paragraphStyle: para]) 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() setNeedsLayout()
} }
public override var intrinsicContentSize: CGSize {
CGSize(width: UIView.noIntrinsicMetric, height: isExpanded ? expandedPreferredHeight : compactPreferredHeight)
}
public override func layoutSubviews() { public override func layoutSubviews() {
super.layoutSubviews() super.layoutSubviews()
layer.cornerRadius = isExpanded ? 16 : 18
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius).cgPath layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius).cgPath
let currentLangText = langLabel.text let currentLangText = langLabel.text
if bounds.size != lastBoundsSize || currentLangText != lastLangText { 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() { private func updateDynamicTextLineLimit() {
let fittingWidth = max(0, bounds.width - layoutMargins.left - layoutMargins.right) let fittingWidth = max(0, bounds.width - layoutMargins.left - layoutMargins.right)
if fittingWidth <= 0 { return } if fittingWidth <= 0 { return }
@ -182,7 +209,7 @@ public class PlayerView: UIView {
let enLineHeight = enLabel.font.lineHeight * 1.1 + 2 let enLineHeight = enLabel.font.lineHeight * 1.1 + 2
let combinedLineHeight = cnLineHeight + enLineHeight 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 { if perLabelLines != lastDynamicTextLines {
lastDynamicTextLines = perLabelLines lastDynamicTextLines = perLabelLines

Loading…
Cancel
Save