+29
-6
@@ -4,9 +4,9 @@
|
||||
/// To regenerate, run: `dart run slang`
|
||||
///
|
||||
/// Locales: 6
|
||||
/// Strings: 2608 (434 per locale)
|
||||
/// Strings: 2614 (435 per locale)
|
||||
///
|
||||
/// Built on 2025-12-12 at 16:59 UTC
|
||||
/// Built on 2025-12-19 at 00:18 UTC
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
@@ -551,6 +551,7 @@ class _StringsVideoControlsEn {
|
||||
String get aspectRatioButton => 'Aspect ratio';
|
||||
String get fullscreenButton => 'Enter fullscreen';
|
||||
String get exitFullscreenButton => 'Exit fullscreen';
|
||||
String get alwaysOnTopButton => 'Always on top';
|
||||
String get rotationLockButton => 'Rotation lock';
|
||||
String get timelineSlider => 'Video timeline';
|
||||
String get volumeSlider => 'Volume level';
|
||||
@@ -1609,6 +1610,8 @@ class _StringsVideoControlsDe implements _StringsVideoControlsEn {
|
||||
@override
|
||||
String get exitFullscreenButton => 'Vollbild verlassen';
|
||||
@override
|
||||
String get alwaysOnTopButton => 'Immer im Vordergrund';
|
||||
@override
|
||||
String get rotationLockButton => 'Drehsperre';
|
||||
@override
|
||||
String get timelineSlider => 'Video-Zeitleiste';
|
||||
@@ -2906,6 +2909,8 @@ class _StringsVideoControlsIt implements _StringsVideoControlsEn {
|
||||
@override
|
||||
String get exitFullscreenButton => 'Esci da schermo intero';
|
||||
@override
|
||||
String get alwaysOnTopButton => 'Sempre in primo piano';
|
||||
@override
|
||||
String get rotationLockButton => 'Blocco rotazione';
|
||||
@override
|
||||
String get timelineSlider => 'Timeline video';
|
||||
@@ -4201,6 +4206,8 @@ class _StringsVideoControlsNl implements _StringsVideoControlsEn {
|
||||
@override
|
||||
String get exitFullscreenButton => 'Volledig scherm verlaten';
|
||||
@override
|
||||
String get alwaysOnTopButton => 'Altijd bovenop';
|
||||
@override
|
||||
String get rotationLockButton => 'Rotatievergrendeling';
|
||||
@override
|
||||
String get timelineSlider => 'Videotijdlijn';
|
||||
@@ -5496,6 +5503,8 @@ class _StringsVideoControlsSv implements _StringsVideoControlsEn {
|
||||
@override
|
||||
String get exitFullscreenButton => 'Avsluta helskärm';
|
||||
@override
|
||||
String get alwaysOnTopButton => 'Alltid överst';
|
||||
@override
|
||||
String get rotationLockButton => 'Rotationslås';
|
||||
@override
|
||||
String get timelineSlider => 'Videotidslinje';
|
||||
@@ -6315,7 +6324,7 @@ class _StringsScreensZh implements _StringsScreensEn {
|
||||
@override
|
||||
String get selectServer => '选择服务器';
|
||||
@override
|
||||
String get switchProfile => '切换配置文件';
|
||||
String get switchProfile => '切换用户';
|
||||
@override
|
||||
String get subtitleStyling => '字幕样式';
|
||||
@override
|
||||
@@ -6788,6 +6797,8 @@ class _StringsVideoControlsZh implements _StringsVideoControlsEn {
|
||||
@override
|
||||
String get exitFullscreenButton => '退出全屏';
|
||||
@override
|
||||
String get alwaysOnTopButton => '置顶窗口';
|
||||
@override
|
||||
String get rotationLockButton => '旋转锁定';
|
||||
@override
|
||||
String get timelineSlider => '视频时间轴';
|
||||
@@ -6940,7 +6951,7 @@ class _StringsDiscoverZh implements _StringsDiscoverEn {
|
||||
@override
|
||||
String get title => '发现';
|
||||
@override
|
||||
String get switchProfile => '切换配置文件';
|
||||
String get switchProfile => '切换用户';
|
||||
@override
|
||||
String get switchServer => '切换服务器';
|
||||
@override
|
||||
@@ -7850,6 +7861,8 @@ extension on Translations {
|
||||
return 'Enter fullscreen';
|
||||
case 'videoControls.exitFullscreenButton':
|
||||
return 'Exit fullscreen';
|
||||
case 'videoControls.alwaysOnTopButton':
|
||||
return 'Always on top';
|
||||
case 'videoControls.rotationLockButton':
|
||||
return 'Rotation lock';
|
||||
case 'videoControls.timelineSlider':
|
||||
@@ -8730,6 +8743,8 @@ extension on _StringsDe {
|
||||
return 'Vollbild aktivieren';
|
||||
case 'videoControls.exitFullscreenButton':
|
||||
return 'Vollbild verlassen';
|
||||
case 'videoControls.alwaysOnTopButton':
|
||||
return 'Immer im Vordergrund';
|
||||
case 'videoControls.rotationLockButton':
|
||||
return 'Drehsperre';
|
||||
case 'videoControls.timelineSlider':
|
||||
@@ -9614,6 +9629,8 @@ extension on _StringsIt {
|
||||
return 'Attiva schermo intero';
|
||||
case 'videoControls.exitFullscreenButton':
|
||||
return 'Esci da schermo intero';
|
||||
case 'videoControls.alwaysOnTopButton':
|
||||
return 'Sempre in primo piano';
|
||||
case 'videoControls.rotationLockButton':
|
||||
return 'Blocco rotazione';
|
||||
case 'videoControls.timelineSlider':
|
||||
@@ -10498,6 +10515,8 @@ extension on _StringsNl {
|
||||
return 'Volledig scherm activeren';
|
||||
case 'videoControls.exitFullscreenButton':
|
||||
return 'Volledig scherm verlaten';
|
||||
case 'videoControls.alwaysOnTopButton':
|
||||
return 'Altijd bovenop';
|
||||
case 'videoControls.rotationLockButton':
|
||||
return 'Rotatievergrendeling';
|
||||
case 'videoControls.timelineSlider':
|
||||
@@ -11382,6 +11401,8 @@ extension on _StringsSv {
|
||||
return 'Aktivera helskärm';
|
||||
case 'videoControls.exitFullscreenButton':
|
||||
return 'Avsluta helskärm';
|
||||
case 'videoControls.alwaysOnTopButton':
|
||||
return 'Alltid överst';
|
||||
case 'videoControls.rotationLockButton':
|
||||
return 'Rotationslås';
|
||||
case 'videoControls.timelineSlider':
|
||||
@@ -11893,7 +11914,7 @@ extension on _StringsZh {
|
||||
case 'screens.selectServer':
|
||||
return '选择服务器';
|
||||
case 'screens.switchProfile':
|
||||
return '切换配置文件';
|
||||
return '切换用户';
|
||||
case 'screens.subtitleStyling':
|
||||
return '字幕样式';
|
||||
case 'screens.search':
|
||||
@@ -12265,6 +12286,8 @@ extension on _StringsZh {
|
||||
return '进入全屏';
|
||||
case 'videoControls.exitFullscreenButton':
|
||||
return '退出全屏';
|
||||
case 'videoControls.alwaysOnTopButton':
|
||||
return '置顶窗口';
|
||||
case 'videoControls.rotationLockButton':
|
||||
return '旋转锁定';
|
||||
case 'videoControls.timelineSlider':
|
||||
@@ -12368,7 +12391,7 @@ extension on _StringsZh {
|
||||
case 'discover.title':
|
||||
return '发现';
|
||||
case 'discover.switchProfile':
|
||||
return '切换配置文件';
|
||||
return '切换用户';
|
||||
case 'discover.switchServer':
|
||||
return '切换服务器';
|
||||
case 'discover.logout':
|
||||
|
||||
@@ -243,6 +243,7 @@
|
||||
"aspectRatioButton": "Aspect ratio",
|
||||
"fullscreenButton": "Enter fullscreen",
|
||||
"exitFullscreenButton": "Exit fullscreen",
|
||||
"alwaysOnTopButton": "Always on top",
|
||||
"rotationLockButton": "Rotation lock",
|
||||
"timelineSlider": "Video timeline",
|
||||
"volumeSlider": "Volume level",
|
||||
|
||||
@@ -244,6 +244,7 @@
|
||||
"aspectRatioButton": "Seitenverhältnis",
|
||||
"fullscreenButton": "Vollbild aktivieren",
|
||||
"exitFullscreenButton": "Vollbild verlassen",
|
||||
"alwaysOnTopButton": "Immer im Vordergrund",
|
||||
"rotationLockButton": "Drehsperre",
|
||||
"timelineSlider": "Video-Zeitleiste",
|
||||
"volumeSlider": "Lautstärkepegel",
|
||||
|
||||
@@ -244,6 +244,7 @@
|
||||
"aspectRatioButton": "Proporzioni",
|
||||
"fullscreenButton": "Attiva schermo intero",
|
||||
"exitFullscreenButton": "Esci da schermo intero",
|
||||
"alwaysOnTopButton": "Sempre in primo piano",
|
||||
"rotationLockButton": "Blocco rotazione",
|
||||
"timelineSlider": "Timeline video",
|
||||
"volumeSlider": "Livello volume",
|
||||
|
||||
@@ -244,6 +244,7 @@
|
||||
"aspectRatioButton": "Beeldverhouding",
|
||||
"fullscreenButton": "Volledig scherm activeren",
|
||||
"exitFullscreenButton": "Volledig scherm verlaten",
|
||||
"alwaysOnTopButton": "Altijd bovenop",
|
||||
"rotationLockButton": "Rotatievergrendeling",
|
||||
"timelineSlider": "Videotijdlijn",
|
||||
"volumeSlider": "Volumeniveau",
|
||||
|
||||
@@ -244,6 +244,7 @@
|
||||
"aspectRatioButton": "Bildförhållande",
|
||||
"fullscreenButton": "Aktivera helskärm",
|
||||
"exitFullscreenButton": "Avsluta helskärm",
|
||||
"alwaysOnTopButton": "Alltid överst",
|
||||
"rotationLockButton": "Rotationslås",
|
||||
"timelineSlider": "Videotidslinje",
|
||||
"volumeSlider": "Volymnivå",
|
||||
|
||||
@@ -244,6 +244,7 @@
|
||||
"aspectRatioButton": "宽高比",
|
||||
"fullscreenButton": "进入全屏",
|
||||
"exitFullscreenButton": "退出全屏",
|
||||
"alwaysOnTopButton": "置顶窗口",
|
||||
"rotationLockButton": "旋转锁定",
|
||||
"timelineSlider": "视频时间轴",
|
||||
"volumeSlider": "音量调节",
|
||||
|
||||
@@ -51,8 +51,10 @@ class DesktopVideoControls extends StatefulWidget {
|
||||
final int audioSyncOffset;
|
||||
final int subtitleSyncOffset;
|
||||
final bool isFullscreen;
|
||||
final bool isAlwaysOnTop;
|
||||
final VoidCallback? onCycleBoxFitMode;
|
||||
final VoidCallback? onToggleFullscreen;
|
||||
final VoidCallback? onToggleAlwaysOnTop;
|
||||
final Function(int)? onSwitchVersion;
|
||||
final Function(AudioTrack)? onAudioTrackChanged;
|
||||
final Function(SubtitleTrack)? onSubtitleTrackChanged;
|
||||
@@ -89,8 +91,10 @@ class DesktopVideoControls extends StatefulWidget {
|
||||
this.audioSyncOffset = 0,
|
||||
this.subtitleSyncOffset = 0,
|
||||
this.isFullscreen = false,
|
||||
this.isAlwaysOnTop = false,
|
||||
this.onCycleBoxFitMode,
|
||||
this.onToggleFullscreen,
|
||||
this.onToggleAlwaysOnTop,
|
||||
this.onSwitchVersion,
|
||||
this.onAudioTrackChanged,
|
||||
this.onSubtitleTrackChanged,
|
||||
@@ -455,9 +459,11 @@ class DesktopVideoControlsState extends State<DesktopVideoControls> {
|
||||
subtitleSyncOffset: widget.subtitleSyncOffset,
|
||||
isRotationLocked: false, // Desktop doesn't have rotation lock
|
||||
isFullscreen: widget.isFullscreen,
|
||||
isAlwaysOnTop: widget.isAlwaysOnTop,
|
||||
serverId: widget.serverId,
|
||||
onCycleBoxFitMode: widget.onCycleBoxFitMode,
|
||||
onToggleFullscreen: widget.onToggleFullscreen,
|
||||
onToggleAlwaysOnTop: widget.onToggleAlwaysOnTop,
|
||||
onSwitchVersion: widget.onSwitchVersion,
|
||||
onAudioTrackChanged: widget.onAudioTrackChanged,
|
||||
onSubtitleTrackChanged: widget.onSubtitleTrackChanged,
|
||||
|
||||
@@ -124,6 +124,7 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
|
||||
bool _chaptersLoaded = false;
|
||||
Timer? _hideTimer;
|
||||
bool _isFullscreen = false;
|
||||
bool _isAlwaysOnTop = false;
|
||||
late final FocusNode _focusNode;
|
||||
KeyboardShortcutsService? _keyboardService;
|
||||
int _seekTimeSmall = 10; // Default, loaded from settings
|
||||
@@ -188,6 +189,7 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
|
||||
// Add window listener for tracking fullscreen state (for button icon)
|
||||
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
|
||||
windowManager.addListener(this);
|
||||
_initAlwaysOnTopState();
|
||||
}
|
||||
// Focus play/pause button on first frame if in keyboard mode
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
@@ -865,6 +867,25 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize always-on-top state from window manager (desktop only)
|
||||
Future<void> _initAlwaysOnTopState() async {
|
||||
final isOnTop = await windowManager.isAlwaysOnTop();
|
||||
if (mounted && isOnTop != _isAlwaysOnTop) {
|
||||
setState(() {
|
||||
_isAlwaysOnTop = isOnTop;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggle always-on-top window mode (desktop only)
|
||||
Future<void> _toggleAlwaysOnTop() async {
|
||||
if (!PlatformDetector.isMobile(context)) {
|
||||
_isAlwaysOnTop = !_isAlwaysOnTop;
|
||||
await windowManager.setAlwaysOnTop(_isAlwaysOnTop);
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a key is a directional key (arrow keys)
|
||||
bool _isDirectionalKey(LogicalKeyboardKey key) {
|
||||
return key == LogicalKeyboardKey.arrowUp ||
|
||||
@@ -1230,8 +1251,10 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
|
||||
audioSyncOffset: _audioSyncOffset,
|
||||
subtitleSyncOffset: _subtitleSyncOffset,
|
||||
isFullscreen: _isFullscreen,
|
||||
isAlwaysOnTop: _isAlwaysOnTop,
|
||||
onCycleBoxFitMode: widget.onCycleBoxFitMode,
|
||||
onToggleFullscreen: _toggleFullscreen,
|
||||
onToggleAlwaysOnTop: _toggleAlwaysOnTop,
|
||||
onSwitchVersion: _switchMediaVersion,
|
||||
onAudioTrackChanged: widget.onAudioTrackChanged,
|
||||
onSubtitleTrackChanged: widget.onSubtitleTrackChanged,
|
||||
|
||||
@@ -31,9 +31,11 @@ class TrackChapterControls extends StatelessWidget {
|
||||
final int subtitleSyncOffset;
|
||||
final bool isRotationLocked;
|
||||
final bool isFullscreen;
|
||||
final bool isAlwaysOnTop;
|
||||
final VoidCallback? onCycleBoxFitMode;
|
||||
final VoidCallback? onToggleRotationLock;
|
||||
final VoidCallback? onToggleFullscreen;
|
||||
final VoidCallback? onToggleAlwaysOnTop;
|
||||
final Function(int)? onSwitchVersion;
|
||||
final Function(AudioTrack)? onAudioTrackChanged;
|
||||
final Function(SubtitleTrack)? onSubtitleTrackChanged;
|
||||
@@ -67,9 +69,11 @@ class TrackChapterControls extends StatelessWidget {
|
||||
required this.isRotationLocked,
|
||||
required this.isFullscreen,
|
||||
required this.serverId,
|
||||
this.isAlwaysOnTop = false,
|
||||
this.onCycleBoxFitMode,
|
||||
this.onToggleRotationLock,
|
||||
this.onToggleFullscreen,
|
||||
this.onToggleAlwaysOnTop,
|
||||
this.onSwitchVersion,
|
||||
this.onAudioTrackChanged,
|
||||
this.onSubtitleTrackChanged,
|
||||
@@ -334,6 +338,24 @@ class TrackChapterControls extends StatelessWidget {
|
||||
onPressed: onToggleFullscreen,
|
||||
),
|
||||
);
|
||||
buttonIndex++;
|
||||
}
|
||||
|
||||
// Always on top button (desktop only, not TV)
|
||||
if (isDesktop && onToggleAlwaysOnTop != null) {
|
||||
final currentIndex = buttonIndex;
|
||||
buttons.add(
|
||||
_buildTrackButton(
|
||||
buttonIndex: currentIndex,
|
||||
icon: Symbols.layers_rounded,
|
||||
semanticLabel: t.videoControls.alwaysOnTopButton,
|
||||
isActive: isAlwaysOnTop,
|
||||
tracks: tracks,
|
||||
isMobile: isMobile,
|
||||
isDesktop: isDesktop,
|
||||
onPressed: onToggleAlwaysOnTop,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return IntrinsicHeight(
|
||||
@@ -352,7 +374,8 @@ class TrackChapterControls extends StatelessWidget {
|
||||
if (availableVersions.length > 1 && onSwitchVersion != null) count++;
|
||||
if (onCycleBoxFitMode != null) count++;
|
||||
if (isMobile && !PlatformDetector.isTV()) count++; // Rotation lock (not on TV)
|
||||
if (isDesktop) count++;
|
||||
if (isDesktop) count++; // Fullscreen
|
||||
if (isDesktop && onToggleAlwaysOnTop != null) count++; // Always on top
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user