fix: add const constructors and remove unused imports
This commit is contained in:
+14
-27
@@ -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)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user