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 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<void> _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<void> _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,

31
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

Loading…
Cancel
Save