fix: add const constructors and remove unused imports

This commit is contained in:
edde746
2026-02-27 15:43:34 +01:00
parent ab2f8cf6ff
commit 00667fe8b8
21 changed files with 206 additions and 220 deletions
+14 -27
View File
@@ -137,7 +137,7 @@ void main() async {
void _registerShaderLicenses() { void _registerShaderLicenses() {
LicenseRegistry.addLicense(() async* { LicenseRegistry.addLicense(() async* {
yield LicenseEntryWithLineBreaks( yield const LicenseEntryWithLineBreaks(
['Anime4K'], ['Anime4K'],
'MIT License\n' 'MIT License\n'
'\n' '\n'
@@ -162,7 +162,7 @@ void _registerShaderLicenses() {
'OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE ' 'OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE '
'SOFTWARE.', 'SOFTWARE.',
); );
yield LicenseEntryWithLineBreaks( yield const LicenseEntryWithLineBreaks(
['NVIDIA Image Scaling (NVScaler)'], ['NVIDIA Image Scaling (NVScaler)'],
'The MIT License (MIT)\n' 'The MIT License (MIT)\n'
'\n' '\n'
@@ -390,9 +390,10 @@ class _SetupScreenState extends State<SetupScreen> {
// Check network connectivity early to fast-path airplane mode. // Check network connectivity early to fast-path airplane mode.
// Timeout guards against connectivity_plus hanging on some Android TV devices after force-close. // Timeout guards against connectivity_plus hanging on some Android TV devices after force-close.
final connectivityResult = await Connectivity() final connectivityResult = await Connectivity().checkConnectivity().timeout(
.checkConnectivity() const Duration(seconds: 3),
.timeout(const Duration(seconds: 3), onTimeout: () => [ConnectivityResult.other]); onTimeout: () => [ConnectivityResult.other],
);
final hasNetwork = !connectivityResult.contains(ConnectivityResult.none); final hasNetwork = !connectivityResult.contains(ConnectivityResult.none);
if (hasNetwork) { if (hasNetwork) {
@@ -430,10 +431,7 @@ class _SetupScreenState extends State<SetupScreen> {
_setStatus(t.common.startingOfflineMode); _setStatus(t.common.startingOfflineMode);
await context.read<DownloadProvider>().ensureInitialized(); await context.read<DownloadProvider>().ensureInitialized();
if (!mounted) return; if (!mounted) return;
Navigator.pushReplacement( Navigator.pushReplacement(context, fadeRoute(const MainScreen(isOfflineMode: true)));
context,
fadeRoute(const MainScreen(isOfflineMode: true)),
);
return; return;
} }
@@ -457,18 +455,12 @@ class _SetupScreenState extends State<SetupScreen> {
downloadProvider.resumeQueuedDownloads(result.firstClient!); downloadProvider.resumeQueuedDownloads(result.firstClient!);
}); });
Navigator.pushReplacement( Navigator.pushReplacement(context, fadeRoute(MainScreen(client: result.firstClient!)));
context,
fadeRoute(MainScreen(client: result.firstClient!)),
);
} else { } else {
_setStatus(t.common.startingOfflineMode); _setStatus(t.common.startingOfflineMode);
await context.read<DownloadProvider>().ensureInitialized(); await context.read<DownloadProvider>().ensureInitialized();
if (!mounted) return; if (!mounted) return;
Navigator.pushReplacement( Navigator.pushReplacement(context, fadeRoute(const MainScreen(isOfflineMode: true)));
context,
fadeRoute(const MainScreen(isOfflineMode: true)),
);
} }
} catch (e, stackTrace) { } catch (e, stackTrace) {
appLogger.e('Error during multi-server connection', error: e, stackTrace: stackTrace); appLogger.e('Error during multi-server connection', error: e, stackTrace: stackTrace);
@@ -477,10 +469,7 @@ class _SetupScreenState extends State<SetupScreen> {
_setStatus(t.common.startingOfflineMode); _setStatus(t.common.startingOfflineMode);
await context.read<DownloadProvider>().ensureInitialized(); await context.read<DownloadProvider>().ensureInitialized();
if (!mounted) return; if (!mounted) return;
Navigator.pushReplacement( Navigator.pushReplacement(context, fadeRoute(const MainScreen(isOfflineMode: true)));
context,
fadeRoute(const MainScreen(isOfflineMode: true)),
);
} }
} }
} }
@@ -493,9 +482,7 @@ class _SetupScreenState extends State<SetupScreen> {
children: [ children: [
// Icon dead-center, matching Android 12+ splash position. // Icon dead-center, matching Android 12+ splash position.
// 192dp accounts for the 16% inset in ic_launcher.xml. // 192dp accounts for the 16% inset in ic_launcher.xml.
Center( Center(child: SvgPicture.asset('assets/plezy_adaptive_foreground.svg', width: 288, height: 288)),
child: SvgPicture.asset('assets/plezy_adaptive_foreground.svg', width: 288, height: 288),
),
// Status text below center, independent of icon position. // Status text below center, independent of icon position.
Positioned( Positioned(
left: 0, left: 0,
@@ -507,9 +494,9 @@ class _SetupScreenState extends State<SetupScreen> {
_statusMessage, _statusMessage,
key: ValueKey(_statusMessage), key: ValueKey(_statusMessage),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6), context,
), ).textTheme.bodyMedium?.copyWith(color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6)),
), ),
), ),
), ),
+2 -5
View File
@@ -113,10 +113,7 @@ class _AuthScreenState extends State<AuthScreen> {
await profileFuture; await profileFuture;
if (!mounted) return; if (!mounted) return;
Navigator.pushReplacement( Navigator.pushReplacement(context, fadeRoute(MainScreen(client: result.firstClient!)));
context,
fadeRoute(MainScreen(client: result.firstClient!)),
);
} catch (e) { } catch (e) {
appLogger.e('Failed to connect to servers', error: e); appLogger.e('Failed to connect to servers', error: e);
setState(() { setState(() {
@@ -455,7 +452,7 @@ class _AuthScreenState extends State<AuthScreen> {
padding: const EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(vertical: 12),
side: BorderSide(color: Theme.of(context).colorScheme.outline.withValues(alpha: 0.5)), side: BorderSide(color: Theme.of(context).colorScheme.outline.withValues(alpha: 0.5)),
), ),
child: Text(t.auth.debugEnterToken, style: TextStyle(fontSize: 12)), child: Text(t.auth.debugEnterToken, style: const TextStyle(fontSize: 12)),
), ),
], ],
if (_errorMessage != null) ...[ if (_errorMessage != null) ...[
@@ -201,7 +201,7 @@ class _RemoteControlContentState extends State<_RemoteControlContent> {
Container( Container(
width: 8, width: 8,
height: 8, height: 8,
decoration: BoxDecoration(color: Colors.green, shape: BoxShape.circle), decoration: const BoxDecoration(color: Colors.green, shape: BoxShape.circle),
), ),
], ],
), ),
@@ -648,7 +648,7 @@ class _SearchBottomSheetState extends State<_SearchBottomSheet> {
hintText: t.companionRemote.remote.searchHint, hintText: t.companionRemote.remote.searchHint,
prefixIcon: const Icon(Icons.search), prefixIcon: const Icon(Icons.search),
suffixIcon: IconButton(icon: const Icon(Icons.send), onPressed: () => _submit(_controller.text)), suffixIcon: IconButton(icon: const Icon(Icons.send), onPressed: () => _submit(_controller.text)),
border: OutlineInputBorder(borderRadius: const BorderRadius.all(Radius.circular(100))), border: const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(100))),
), ),
onSubmitted: _submit, onSubmitted: _submit,
), ),
+35 -23
View File
@@ -258,7 +258,6 @@ class _DiscoverScreenState extends State<DiscoverScreen>
_loadContent(); _loadContent();
} }
/// Handle key events for the hero section /// Handle key events for the hero section
late final _handleHeroKeyEvent = dpadKeyHandler( late final _handleHeroKeyEvent = dpadKeyHandler(
onDown: () { onDown: () {
@@ -285,7 +284,6 @@ class _DiscoverScreenState extends State<DiscoverScreen>
}, },
); );
@override @override
void dispose() { void dispose() {
_hiddenLibrariesProvider?.removeListener(_onHiddenLibrariesChanged); _hiddenLibrariesProvider?.removeListener(_onHiddenLibrariesChanged);
@@ -781,12 +779,18 @@ class _DiscoverScreenState extends State<DiscoverScreen>
PopupMenuItem( PopupMenuItem(
value: 'switch_profile', value: 'switch_profile',
child: Row( child: Row(
children: [AppIcon(Symbols.people_rounded, fill: 1), SizedBox(width: 8), Text(t.discover.switchProfile)], children: [
AppIcon(Symbols.people_rounded, fill: 1),
const SizedBox(width: 8),
Text(t.discover.switchProfile),
],
), ),
), ),
PopupMenuItem( PopupMenuItem(
value: 'logout', value: 'logout',
child: Row(children: [AppIcon(Symbols.logout_rounded, fill: 1), SizedBox(width: 8), Text(t.common.logout)]), child: Row(
children: [AppIcon(Symbols.logout_rounded, fill: 1), const SizedBox(width: 8), Text(t.common.logout)],
),
), ),
], ],
).then((value) { ).then((value) {
@@ -838,14 +842,11 @@ class _DiscoverScreenState extends State<DiscoverScreen>
onNavigateLeft: _navigateToSidebar, onNavigateLeft: _navigateToSidebar,
onNavigateDown: _focusContentFromAppBar, onNavigateDown: _focusContentFromAppBar,
actions: [ actions: [
FocusableAction( FocusableAction(icon: Symbols.refresh_rounded, iconColor: Colors.white, onPressed: _loadContent),
icon: Symbols.refresh_rounded,
iconColor: Colors.white,
onPressed: _loadContent,
),
// Watch Together // Watch Together
FocusableAction( FocusableAction(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const WatchTogetherScreen())), onPressed: () =>
Navigator.push(context, MaterialPageRoute(builder: (_) => const WatchTogetherScreen())),
child: Stack( child: Stack(
children: [ children: [
IconButton( IconButton(
@@ -854,7 +855,10 @@ class _DiscoverScreenState extends State<DiscoverScreen>
fill: watchTogether.isInSession ? 1 : 0, fill: watchTogether.isInSession ? 1 : 0,
color: watchTogether.isInSession ? Theme.of(context).colorScheme.primary : Colors.white, color: watchTogether.isInSession ? Theme.of(context).colorScheme.primary : Colors.white,
), ),
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const WatchTogetherScreen())), onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const WatchTogetherScreen()),
),
tooltip: 'Watch Together', tooltip: 'Watch Together',
), ),
if (watchTogether.isInSession && watchTogether.participantCount > 1) if (watchTogether.isInSession && watchTogether.participantCount > 1)
@@ -886,7 +890,10 @@ class _DiscoverScreenState extends State<DiscoverScreen>
if (isDesktop) { if (isDesktop) {
RemoteSessionDialog.show(context); RemoteSessionDialog.show(context);
} else { } else {
Navigator.push(context, MaterialPageRoute(builder: (context) => MobileRemoteScreen())); Navigator.push(
context,
MaterialPageRoute(builder: (context) => const MobileRemoteScreen()),
);
} }
}, },
child: Stack( child: Stack(
@@ -895,13 +902,18 @@ class _DiscoverScreenState extends State<DiscoverScreen>
icon: AppIcon( icon: AppIcon(
Symbols.phone_android_rounded, Symbols.phone_android_rounded,
fill: companionRemote.isConnected ? 1 : 0, fill: companionRemote.isConnected ? 1 : 0,
color: companionRemote.isConnected ? Theme.of(context).colorScheme.primary : Colors.white, color: companionRemote.isConnected
? Theme.of(context).colorScheme.primary
: Colors.white,
), ),
onPressed: () { onPressed: () {
if (isDesktop) { if (isDesktop) {
RemoteSessionDialog.show(context); RemoteSessionDialog.show(context);
} else { } else {
Navigator.push(context, MaterialPageRoute(builder: (context) => MobileRemoteScreen())); Navigator.push(
context,
MaterialPageRoute(builder: (context) => const MobileRemoteScreen()),
);
} }
}, },
tooltip: t.companionRemote.title, tooltip: t.companionRemote.title,
@@ -913,10 +925,10 @@ class _DiscoverScreenState extends State<DiscoverScreen>
child: Container( child: Container(
width: 8, width: 8,
height: 8, height: 8,
decoration: BoxDecoration( decoration: const BoxDecoration(
color: Colors.green, color: Colors.green,
shape: BoxShape.circle, shape: BoxShape.circle,
border: const Border.fromBorderSide(BorderSide(color: Colors.white, width: 1)), border: Border.fromBorderSide(BorderSide(color: Colors.white, width: 1)),
), ),
), ),
), ),
@@ -944,7 +956,7 @@ class _DiscoverScreenState extends State<DiscoverScreen>
child: Row( child: Row(
children: [ children: [
AppIcon(Symbols.people_rounded, fill: 1), AppIcon(Symbols.people_rounded, fill: 1),
SizedBox(width: 8), const SizedBox(width: 8),
Text(t.discover.switchProfile), Text(t.discover.switchProfile),
], ],
), ),
@@ -954,7 +966,7 @@ class _DiscoverScreenState extends State<DiscoverScreen>
child: Row( child: Row(
children: [ children: [
AppIcon(Symbols.logout_rounded, fill: 1), AppIcon(Symbols.logout_rounded, fill: 1),
SizedBox(width: 8), const SizedBox(width: 8),
Text(t.common.logout), Text(t.common.logout),
], ],
), ),
@@ -1097,11 +1109,11 @@ class _DiscoverScreenState extends State<DiscoverScreen>
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
AppIcon(Symbols.movie_rounded, fill: 1, size: 64, color: Colors.grey), const AppIcon(Symbols.movie_rounded, fill: 1, size: 64, color: Colors.grey),
SizedBox(height: 16), const SizedBox(height: 16),
Text(t.discover.noContentAvailable), Text(t.discover.noContentAvailable),
SizedBox(height: 8), const SizedBox(height: 8),
Text(t.discover.addMediaToLibraries, style: TextStyle(color: Colors.grey)), Text(t.discover.addMediaToLibraries, style: const TextStyle(color: Colors.grey)),
], ],
), ),
), ),
@@ -1575,7 +1587,7 @@ class _DiscoverScreenState extends State<DiscoverScreen>
] else ] else
Text( Text(
t.common.play, t.common.play,
style: TextStyle(color: Colors.black, fontSize: 14, fontWeight: FontWeight.w600), style: const TextStyle(color: Colors.black, fontSize: 14, fontWeight: FontWeight.w600),
), ),
], ],
), ),
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../focus/focusable_action_bar.dart'; import '../focus/focusable_action_bar.dart';
import '../focus/input_mode_tracker.dart'; import '../focus/input_mode_tracker.dart';
import '../focus/key_event_utils.dart';
import '../mixins/grid_focus_node_mixin.dart'; import '../mixins/grid_focus_node_mixin.dart';
import '../providers/settings_provider.dart'; import '../providers/settings_provider.dart';
import '../utils/grid_size_calculator.dart'; import '../utils/grid_size_calculator.dart';
-4
View File
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:plezy/widgets/app_icon.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../services/plex_client.dart'; import '../../services/plex_client.dart';
@@ -15,11 +14,8 @@ import '../widgets/focusable_media_card.dart';
import '../widgets/media_grid_delegate.dart'; import '../widgets/media_grid_delegate.dart';
import '../widgets/desktop_app_bar.dart'; import '../widgets/desktop_app_bar.dart';
import '../widgets/overlay_sheet.dart'; import '../widgets/overlay_sheet.dart';
import 'package:flutter/services.dart';
import '../focus/dpad_navigator.dart';
import '../focus/focusable_action_bar.dart'; import '../focus/focusable_action_bar.dart';
import '../focus/input_mode_tracker.dart'; import '../focus/input_mode_tracker.dart';
import '../focus/key_event_utils.dart';
import '../mixins/grid_focus_node_mixin.dart'; import '../mixins/grid_focus_node_mixin.dart';
import 'libraries/sort_bottom_sheet.dart'; import 'libraries/sort_bottom_sheet.dart';
import 'libraries/state_messages.dart'; import 'libraries/state_messages.dart';
-1
View File
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../focus/dpad_navigator.dart';
import '../../focus/focusable_action_bar.dart'; import '../../focus/focusable_action_bar.dart';
import '../../i18n/strings.g.dart'; import '../../i18n/strings.g.dart';
import '../../models/livetv_channel.dart'; import '../../models/livetv_channel.dart';
+1 -1
View File
@@ -533,7 +533,7 @@ class GuideTabState extends State<GuideTab> {
children: [ children: [
Row( Row(
children: [ children: [
SizedBox(width: _channelColumnWidth, height: _timeHeaderHeight), const SizedBox(width: _channelColumnWidth, height: _timeHeaderHeight),
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
controller: _headerHorizontalController, controller: _headerHorizontalController,
+4 -4
View File
@@ -365,7 +365,7 @@ class _TvPinInputState extends State<_TvPinInput> {
_digits[index] = digit; _digits[index] = digit;
_activeIndex = index; _activeIndex = index;
_mobileControllers[index].text = digit.toString(); _mobileControllers[index].text = digit.toString();
_mobileControllers[index].selection = TextSelection.collapsed(offset: 1); _mobileControllers[index].selection = const TextSelection.collapsed(offset: 1);
}); });
if (index < 3) { if (index < 3) {
@@ -436,12 +436,12 @@ class _TvPinInputState extends State<_TvPinInput> {
maxLength: 2, // allow overwrite maxLength: 2, // allow overwrite
obscureText: true, obscureText: true,
style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold), style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold),
decoration: InputDecoration( decoration: const InputDecoration(
counterText: '', counterText: '',
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: const BorderRadius.all(Radius.circular(FocusTheme.defaultBorderRadius)), borderRadius: BorderRadius.all(Radius.circular(FocusTheme.defaultBorderRadius)),
), ),
contentPadding: const EdgeInsets.symmetric(vertical: 14), contentPadding: EdgeInsets.symmetric(vertical: 14),
), ),
inputFormatters: [FilteringTextInputFormatter.digitsOnly], inputFormatters: [FilteringTextInputFormatter.digitsOnly],
onChanged: (value) => _onMobileDigitChanged(i, value), onChanged: (value) => _onMobileDigitChanged(i, value),
+6 -6
View File
@@ -235,16 +235,16 @@ class _SearchScreenState extends State<SearchScreen> with Refreshable, FullRefre
: null, : null,
filled: true, filled: true,
fillColor: Theme.of(context).colorScheme.surfaceContainerHighest, fillColor: Theme.of(context).colorScheme.surfaceContainerHighest,
border: OutlineInputBorder( border: const OutlineInputBorder(
borderRadius: const BorderRadius.all(Radius.circular(100)), borderRadius: BorderRadius.all(Radius.circular(100)),
borderSide: BorderSide.none, borderSide: BorderSide.none,
), ),
enabledBorder: OutlineInputBorder( enabledBorder: const OutlineInputBorder(
borderRadius: const BorderRadius.all(Radius.circular(100)), borderRadius: BorderRadius.all(Radius.circular(100)),
borderSide: BorderSide.none, borderSide: BorderSide.none,
), ),
focusedBorder: OutlineInputBorder( focusedBorder: const OutlineInputBorder(
borderRadius: const BorderRadius.all(Radius.circular(100)), borderRadius: BorderRadius.all(Radius.circular(100)),
borderSide: BorderSide.none, borderSide: BorderSide.none,
), ),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
+8 -3
View File
@@ -59,7 +59,8 @@ class _SeasonDetailScreenState extends State<SeasonDetailScreen>
bool _suppressNextBackKeyUp = false; bool _suppressNextBackKeyUp = false;
bool _routeSubscribed = false; bool _routeSubscribed = false;
String _toGlobalKey(String ratingKey, {String? serverId}) => buildGlobalKey(serverId ?? widget.season.serverId ?? '', ratingKey); String _toGlobalKey(String ratingKey, {String? serverId}) =>
buildGlobalKey(serverId ?? widget.season.serverId ?? '', ratingKey);
// WatchStateAware: watch all episode ratingKeys // WatchStateAware: watch all episode ratingKeys
@override @override
@@ -364,14 +365,18 @@ class _EpisodeCardState extends State<_EpisodeCard> {
); );
return Row( return Row(
children: [ children: [
if (widget.episode.duration != null) Text(formatDurationTimestamp(Duration(milliseconds: widget.episode.duration!)), style: mutedStyle), if (widget.episode.duration != null)
Text(formatDurationTimestamp(Duration(milliseconds: widget.episode.duration!)), style: mutedStyle),
if (widget.episode.originallyAvailableAt != null) ...[ if (widget.episode.originallyAvailableAt != null) ...[
dot, dot,
Text(formatFullDate(widget.episode.originallyAvailableAt!), style: mutedStyle), Text(formatFullDate(widget.episode.originallyAvailableAt!), style: mutedStyle),
], ],
if (widget.episode.userRating != null && widget.episode.userRating! > 0) ...[ if (widget.episode.userRating != null && widget.episode.userRating! > 0) ...[
dot, dot,
Padding(padding: const EdgeInsets.only(top: 2), child: Icon(Symbols.star_rounded, size: 12, fill: 1, color: Colors.amber)), const Padding(
padding: EdgeInsets.only(top: 2),
child: Icon(Symbols.star_rounded, size: 12, fill: 1, color: Colors.amber),
),
const SizedBox(width: 2), const SizedBox(width: 2),
Text( Text(
(widget.episode.userRating! / 2) == (widget.episode.userRating! / 2).truncateToDouble() (widget.episode.userRating! / 2) == (widget.episode.userRating! / 2).truncateToDouble()
-1
View File
@@ -2,7 +2,6 @@ import 'dart:convert';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:plezy/widgets/app_icon.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:logger/logger.dart'; import 'package:logger/logger.dart';
+6 -7
View File
@@ -51,10 +51,9 @@ class PlexAuthService {
static Future<PlexAuthService> create() async { static Future<PlexAuthService> create() async {
final storage = await StorageService.getInstance(); final storage = await StorageService.getInstance();
final dio = Dio(BaseOptions( final dio = Dio(
connectTimeout: ConnectionTimeouts.plexTvConnect, BaseOptions(connectTimeout: ConnectionTimeouts.plexTvConnect, receiveTimeout: ConnectionTimeouts.plexTvReceive),
receiveTimeout: ConnectionTimeouts.plexTvReceive, );
));
// Get or create client identifier // Get or create client identifier
String? clientIdentifier = storage.getClientIdentifier(); String? clientIdentifier = storage.getClientIdentifier();
@@ -276,7 +275,7 @@ class PlexServer {
factory PlexServer.fromJson(Map<String, dynamic> json) { factory PlexServer.fromJson(Map<String, dynamic> json) {
// Validate required fields first // Validate required fields first
if (!_isValidServerJson(json)) { if (!_isValidServerJson(json)) {
throw FormatException( throw const FormatException(
'Invalid server data: missing required fields (name, clientIdentifier, accessToken, or connections)', 'Invalid server data: missing required fields (name, clientIdentifier, accessToken, or connections)',
); );
} }
@@ -302,7 +301,7 @@ class PlexServer {
// If no valid connections were parsed, this server is unusable // If no valid connections were parsed, this server is unusable
if (connections.isEmpty) { if (connections.isEmpty) {
throw FormatException('Server has no valid connections'); throw const FormatException('Server has no valid connections');
} }
DateTime? lastSeenAt; DateTime? lastSeenAt;
@@ -863,7 +862,7 @@ class PlexConnection {
factory PlexConnection.fromJson(Map<String, dynamic> json) { factory PlexConnection.fromJson(Map<String, dynamic> json) {
// Validate required fields // Validate required fields
if (!_isValidConnectionJson(json)) { if (!_isValidConnectionJson(json)) {
throw FormatException('Invalid connection data: missing required fields (protocol, address, port, or uri)'); throw const FormatException('Invalid connection data: missing required fields (protocol, address, port, or uri)');
} }
return PlexConnection( return PlexConnection(
+21 -21
View File
@@ -399,27 +399,27 @@ class SettingsService extends BaseSharedPreferencesService {
// HotKey Objects (New implementation) // HotKey Objects (New implementation)
Map<String, HotKey> getDefaultKeyboardHotkeys() { Map<String, HotKey> getDefaultKeyboardHotkeys() {
return { return {
'play_pause': HotKey(key: PhysicalKeyboardKey.space), 'play_pause': const HotKey(key: PhysicalKeyboardKey.space),
'volume_up': HotKey(key: PhysicalKeyboardKey.arrowUp), 'volume_up': const HotKey(key: PhysicalKeyboardKey.arrowUp),
'volume_down': HotKey(key: PhysicalKeyboardKey.arrowDown), 'volume_down': const HotKey(key: PhysicalKeyboardKey.arrowDown),
'seek_forward': HotKey(key: PhysicalKeyboardKey.arrowRight), 'seek_forward': const HotKey(key: PhysicalKeyboardKey.arrowRight),
'seek_backward': HotKey(key: PhysicalKeyboardKey.arrowLeft), 'seek_backward': const HotKey(key: PhysicalKeyboardKey.arrowLeft),
'seek_forward_large': HotKey(key: PhysicalKeyboardKey.arrowRight, modifiers: [HotKeyModifier.shift]), 'seek_forward_large': const HotKey(key: PhysicalKeyboardKey.arrowRight, modifiers: [HotKeyModifier.shift]),
'seek_backward_large': HotKey(key: PhysicalKeyboardKey.arrowLeft, modifiers: [HotKeyModifier.shift]), 'seek_backward_large': const HotKey(key: PhysicalKeyboardKey.arrowLeft, modifiers: [HotKeyModifier.shift]),
'fullscreen_toggle': HotKey(key: PhysicalKeyboardKey.keyF), 'fullscreen_toggle': const HotKey(key: PhysicalKeyboardKey.keyF),
'mute_toggle': HotKey(key: PhysicalKeyboardKey.keyM), 'mute_toggle': const HotKey(key: PhysicalKeyboardKey.keyM),
'subtitle_toggle': HotKey(key: PhysicalKeyboardKey.keyS), 'subtitle_toggle': const HotKey(key: PhysicalKeyboardKey.keyS),
'audio_track_next': HotKey(key: PhysicalKeyboardKey.keyA), 'audio_track_next': const HotKey(key: PhysicalKeyboardKey.keyA),
'subtitle_track_next': HotKey(key: PhysicalKeyboardKey.keyS, modifiers: [HotKeyModifier.shift]), 'subtitle_track_next': const HotKey(key: PhysicalKeyboardKey.keyS, modifiers: [HotKeyModifier.shift]),
'chapter_next': HotKey(key: PhysicalKeyboardKey.keyN), 'chapter_next': const HotKey(key: PhysicalKeyboardKey.keyN),
'chapter_previous': HotKey(key: PhysicalKeyboardKey.keyP), 'chapter_previous': const HotKey(key: PhysicalKeyboardKey.keyP),
'speed_increase': HotKey(key: PhysicalKeyboardKey.equal), 'speed_increase': const HotKey(key: PhysicalKeyboardKey.equal),
'speed_decrease': HotKey(key: PhysicalKeyboardKey.minus), 'speed_decrease': const HotKey(key: PhysicalKeyboardKey.minus),
'speed_reset': HotKey(key: PhysicalKeyboardKey.keyR), 'speed_reset': const HotKey(key: PhysicalKeyboardKey.keyR),
'sub_seek_next': HotKey(key: PhysicalKeyboardKey.arrowRight, modifiers: [HotKeyModifier.control]), 'sub_seek_next': const HotKey(key: PhysicalKeyboardKey.arrowRight, modifiers: [HotKeyModifier.control]),
'sub_seek_prev': HotKey(key: PhysicalKeyboardKey.arrowLeft, modifiers: [HotKeyModifier.control]), 'sub_seek_prev': const HotKey(key: PhysicalKeyboardKey.arrowLeft, modifiers: [HotKeyModifier.control]),
'shader_toggle': HotKey(key: PhysicalKeyboardKey.keyG), 'shader_toggle': const HotKey(key: PhysicalKeyboardKey.keyG),
'skip_marker': HotKey(key: PhysicalKeyboardKey.enter), 'skip_marker': const HotKey(key: PhysicalKeyboardKey.enter),
}; };
} }
+1 -1
View File
@@ -95,7 +95,7 @@ ThemeData monoTheme({required bool dark, bool oled = false}) {
color: c.surface, color: c.surface,
elevation: 0, elevation: 0,
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(borderRadius: const BorderRadius.all(Radius.circular(14))), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(14))),
), ),
inputDecorationTheme: InputDecorationTheme( inputDecorationTheme: InputDecorationTheme(
filled: true, filled: true,
@@ -453,7 +453,7 @@ class DesktopVideoControlsState extends State<DesktopVideoControls> {
const SizedBox(width: 8), const SizedBox(width: 8),
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(color: Colors.red, borderRadius: const BorderRadius.all(Radius.circular(4))), decoration: const BoxDecoration(color: Colors.red, borderRadius: BorderRadius.all(Radius.circular(4))),
child: Text( child: Text(
t.liveTv.live, t.liveTv.live,
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 12), style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 12),
@@ -199,7 +199,7 @@ class MobileVideoControls extends StatelessWidget {
children: [ children: [
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(color: Colors.red, borderRadius: const BorderRadius.all(Radius.circular(4))), decoration: const BoxDecoration(color: Colors.red, borderRadius: BorderRadius.all(Radius.circular(4))),
child: Text( child: Text(
t.liveTv.live, t.liveTv.live,
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 12), style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 12),
@@ -35,7 +35,6 @@ class ChapterSheet extends StatefulWidget {
} }
class _ChapterSheetState extends State<ChapterSheet> { class _ChapterSheetState extends State<ChapterSheet> {
/// Get the PlexClient for chapters, or null if unavailable (offline mode) /// Get the PlexClient for chapters, or null if unavailable (offline mode)
PlexClient? _tryGetClientForChapters(BuildContext context) { PlexClient? _tryGetClientForChapters(BuildContext context) {
if (widget.serverId == null) return null; if (widget.serverId == null) return null;
@@ -49,55 +48,53 @@ class _ChapterSheetState extends State<ChapterSheet> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return StreamBuilder<Duration>( return StreamBuilder<Duration>(
stream: widget.player.streams.position, stream: widget.player.streams.position,
initialData: widget.player.state.position, initialData: widget.player.state.position,
builder: (context, positionSnapshot) { builder: (context, positionSnapshot) {
final currentPosition = positionSnapshot.data ?? Duration.zero; final currentPosition = positionSnapshot.data ?? Duration.zero;
final currentPositionMs = currentPosition.inMilliseconds; final currentPositionMs = currentPosition.inMilliseconds;
// Find the current chapter based on position // Find the current chapter based on position
int? currentChapterIndex; int? currentChapterIndex;
for (int i = 0; i < widget.chapters.length; i++) { for (int i = 0; i < widget.chapters.length; i++) {
final chapter = widget.chapters[i]; final chapter = widget.chapters[i];
final startMs = chapter.startTimeOffset ?? 0; final startMs = chapter.startTimeOffset ?? 0;
final endMs = final endMs =
chapter.endTimeOffset ?? chapter.endTimeOffset ??
(i < widget.chapters.length - 1 (i < widget.chapters.length - 1 ? widget.chapters[i + 1].startTimeOffset ?? 0 : double.maxFinite.toInt());
? widget.chapters[i + 1].startTimeOffset ?? 0
: double.maxFinite.toInt());
if (currentPositionMs >= startMs && currentPositionMs < endMs) { if (currentPositionMs >= startMs && currentPositionMs < endMs) {
currentChapterIndex = i; currentChapterIndex = i;
break; break;
}
} }
}
Widget content; Widget content;
if (!widget.chaptersLoaded) { if (!widget.chaptersLoaded) {
content = const Center(child: CircularProgressIndicator()); content = const Center(child: CircularProgressIndicator());
} else if (widget.chapters.isEmpty) { } else if (widget.chapters.isEmpty) {
content = Center( content = Center(
child: Text(t.videoControls.noChaptersAvailable, style: TextStyle(color: tokens(context).textMuted)), child: Text(t.videoControls.noChaptersAvailable, style: TextStyle(color: tokens(context).textMuted)),
); );
} else { } else {
content = ListView.builder( content = ListView.builder(
itemCount: widget.chapters.length, itemCount: widget.chapters.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final chapter = widget.chapters[index]; final chapter = widget.chapters[index];
final isCurrentChapter = currentChapterIndex == index; final isCurrentChapter = currentChapterIndex == index;
// Get local file path for offline chapter thumbnails // Get local file path for offline chapter thumbnails
final localThumbPath = widget.serverId != null && chapter.thumb != null final localThumbPath = widget.serverId != null && chapter.thumb != null
? DownloadStorageService.instance.getArtworkPathSync(widget.serverId!, chapter.thumb!) ? DownloadStorageService.instance.getArtworkPathSync(widget.serverId!, chapter.thumb!)
: null; : null;
return FocusableListTile( return FocusableListTile(
leading: chapter.thumb != null leading: chapter.thumb != null
? SizedBox( ? SizedBox(
width: 60, width: 60,
height: 34, height: 34,
child: Stack( child: Stack(
children: [ children: [
ClipRRect( ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(4)), borderRadius: const BorderRadius.all(Radius.circular(4)),
child: PlexOptimizedImage.thumb( child: PlexOptimizedImage.thumb(
@@ -114,48 +111,48 @@ class _ChapterSheetState extends State<ChapterSheet> {
if (isCurrentChapter) if (isCurrentChapter)
Positioned.fill( Positioned.fill(
child: Container( child: Container(
decoration: BoxDecoration( decoration: const BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(4)), borderRadius: BorderRadius.all(Radius.circular(4)),
border: const Border.fromBorderSide(BorderSide(color: Colors.blue, width: 2)), border: Border.fromBorderSide(BorderSide(color: Colors.blue, width: 2)),
), ),
), ),
), ),
], ],
), ),
) )
: null, : null,
title: Text( title: Text(
chapter.label, chapter.label,
style: TextStyle( style: TextStyle(
color: isCurrentChapter ? Colors.blue : null, color: isCurrentChapter ? Colors.blue : null,
fontWeight: isCurrentChapter ? FontWeight.bold : FontWeight.normal, fontWeight: isCurrentChapter ? FontWeight.bold : FontWeight.normal,
),
), ),
subtitle: Text( ),
formatDurationTimestamp(chapter.startTime), subtitle: Text(
style: TextStyle( formatDurationTimestamp(chapter.startTime),
color: isCurrentChapter ? Colors.blue.withValues(alpha: 0.7) : tokens(context).textMuted, style: TextStyle(
fontSize: 12, color: isCurrentChapter ? Colors.blue.withValues(alpha: 0.7) : tokens(context).textMuted,
), fontSize: 12,
), ),
trailing: isCurrentChapter ),
? const AppIcon(Symbols.play_circle_rounded, fill: 1, color: Colors.blue) trailing: isCurrentChapter
: null, ? const AppIcon(Symbols.play_circle_rounded, fill: 1, color: Colors.blue)
onTap: () { : null,
widget.player.seek(chapter.startTime); onTap: () {
OverlaySheetController.of(context).close(); widget.player.seek(chapter.startTime);
}, OverlaySheetController.of(context).close();
); },
}, );
); },
}
return BaseVideoControlSheet(
title: t.videoControls.chapters,
icon: Symbols.video_library_rounded,
child: content,
); );
}, }
);
return BaseVideoControlSheet(
title: t.videoControls.chapters,
icon: Symbols.video_library_rounded,
child: content,
);
},
);
} }
} }
@@ -62,9 +62,7 @@ class QueueSheet extends StatelessWidget {
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
trailing: isCurrent trailing: isCurrent ? const AppIcon(Symbols.play_circle_rounded, fill: 1, color: Colors.blue) : null,
? const AppIcon(Symbols.play_circle_rounded, fill: 1, color: Colors.blue)
: null,
onTap: () { onTap: () {
onItemSelected(item); onItemSelected(item);
OverlaySheetController.of(context).close(); OverlaySheetController.of(context).close();
@@ -74,11 +72,7 @@ class QueueSheet extends StatelessWidget {
); );
} }
return BaseVideoControlSheet( return BaseVideoControlSheet(title: t.videoControls.queue, icon: Symbols.queue_music_rounded, child: content);
title: t.videoControls.queue,
icon: Symbols.queue_music_rounded,
child: content,
);
}, },
); );
} }
@@ -109,9 +103,9 @@ class QueueSheet extends StatelessWidget {
if (isCurrent) if (isCurrent)
Positioned.fill( Positioned.fill(
child: Container( child: Container(
decoration: BoxDecoration( decoration: const BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(4)), borderRadius: BorderRadius.all(Radius.circular(4)),
border: const Border.fromBorderSide(BorderSide(color: Colors.blue, width: 2)), border: Border.fromBorderSide(BorderSide(color: Colors.blue, width: 2)),
), ),
), ),
), ),
@@ -1288,7 +1288,7 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
AppIcon(Symbols.fast_forward_rounded, fill: 1, color: Colors.white, size: 16), const AppIcon(Symbols.fast_forward_rounded, fill: 1, color: Colors.white, size: 16),
const SizedBox(width: 4), const SizedBox(width: 4),
const Text( const Text(
'2x', '2x',
@@ -1565,7 +1565,9 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
// On Windows/Linux with navigation off, ESC only exits fullscreen — // On Windows/Linux with navigation off, ESC only exits fullscreen —
// never exits the player. Consume all back key events and check // never exits the player. Consume all back key events and check
// actual window state asynchronously. // actual window state asynchronously.
if (!_videoPlayerNavigationEnabled && (Platform.isWindows || Platform.isLinux) && event.logicalKey.isBackKey) { if (!_videoPlayerNavigationEnabled &&
(Platform.isWindows || Platform.isLinux) &&
event.logicalKey.isBackKey) {
if (event is KeyUpEvent) { if (event is KeyUpEvent) {
_exitFullscreenIfNeeded(); _exitFullscreenIfNeeded();
} }
@@ -242,25 +242,25 @@ class _TimelineSliderState extends State<TimelineSlider> {
overlayShape: const RoundSliderOverlayShape(overlayRadius: 12), overlayShape: const RoundSliderOverlayShape(overlayRadius: 12),
), ),
child: Semantics( child: Semantics(
label: t.videoControls.timelineSlider, label: t.videoControls.timelineSlider,
slider: true, slider: true,
child: Slider( child: Slider(
value: widget.duration.inMilliseconds > 0 ? widget.position.inMilliseconds.toDouble() : 0.0, value: widget.duration.inMilliseconds > 0 ? widget.position.inMilliseconds.toDouble() : 0.0,
min: 0.0, min: 0.0,
max: widget.duration.inMilliseconds.toDouble(), max: widget.duration.inMilliseconds.toDouble(),
onChanged: (value) { onChanged: (value) {
setState(() => _dragValue = value); setState(() => _dragValue = value);
widget.onSeek(Duration(milliseconds: value.toInt())); widget.onSeek(Duration(milliseconds: value.toInt()));
}, },
onChangeEnd: (value) { onChangeEnd: (value) {
setState(() => _dragValue = null); setState(() => _dragValue = null);
widget.onSeekEnd(Duration(milliseconds: value.toInt())); widget.onSeekEnd(Duration(milliseconds: value.toInt()));
}, },
activeColor: Colors.white, activeColor: Colors.white,
inactiveColor: Colors.transparent, inactiveColor: Colors.transparent,
),
), ),
), ),
),
), ),
// Chapter marker indicators // Chapter marker indicators
if (widget.chaptersLoaded && widget.chapters.isNotEmpty && widget.duration.inMilliseconds > 0) if (widget.chaptersLoaded && widget.chapters.isNotEmpty && widget.duration.inMilliseconds > 0)