refactor: fix warnings
This commit is contained in:
@@ -56,7 +56,6 @@ class _InputModeTrackerState extends State<InputModeTracker> {
|
|||||||
_updateFocusHighlightStrategy(_mode);
|
_updateFocusHighlightStrategy(_mode);
|
||||||
// Listen to hardware keyboard events globally
|
// Listen to hardware keyboard events globally
|
||||||
HardwareKeyboard.instance.addHandler(_handleKeyEvent);
|
HardwareKeyboard.instance.addHandler(_handleKeyEvent);
|
||||||
RawKeyboard.instance.addListener(_handleRawKeyEvent);
|
|
||||||
|
|
||||||
// Register callback for gamepad input to switch to keyboard mode
|
// Register callback for gamepad input to switch to keyboard mode
|
||||||
GamepadService.onGamepadInput = () => _setMode(InputMode.keyboard);
|
GamepadService.onGamepadInput = () => _setMode(InputMode.keyboard);
|
||||||
@@ -65,7 +64,6 @@ class _InputModeTrackerState extends State<InputModeTracker> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
HardwareKeyboard.instance.removeHandler(_handleKeyEvent);
|
HardwareKeyboard.instance.removeHandler(_handleKeyEvent);
|
||||||
RawKeyboard.instance.removeListener(_handleRawKeyEvent);
|
|
||||||
GamepadService.onGamepadInput = null;
|
GamepadService.onGamepadInput = null;
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@@ -79,12 +77,6 @@ class _InputModeTrackerState extends State<InputModeTracker> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleRawKeyEvent(RawKeyEvent event) {
|
|
||||||
if (event is RawKeyDownEvent) {
|
|
||||||
_setMode(InputMode.keyboard);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _setMode(InputMode mode) {
|
void _setMode(InputMode mode) {
|
||||||
if (_mode != mode) {
|
if (_mode != mode) {
|
||||||
setState(() => _mode = mode);
|
setState(() => _mode = mode);
|
||||||
|
|||||||
@@ -40,8 +40,9 @@ class DownloadProgress {
|
|||||||
String _formatBytes(int bytes) {
|
String _formatBytes(int bytes) {
|
||||||
if (bytes < 1024) return '$bytes B';
|
if (bytes < 1024) return '$bytes B';
|
||||||
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
|
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
|
||||||
if (bytes < 1024 * 1024 * 1024)
|
if (bytes < 1024 * 1024 * 1024) {
|
||||||
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
|
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
|
||||||
|
}
|
||||||
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB';
|
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ class OfflineModeProvider extends ChangeNotifier {
|
|||||||
StreamSubscription<Map<String, bool>>? _serverStatusSubscription;
|
StreamSubscription<Map<String, bool>>? _serverStatusSubscription;
|
||||||
|
|
||||||
bool _hasNetworkConnection = true;
|
bool _hasNetworkConnection = true;
|
||||||
bool _hasServerConnection = false;
|
late bool _hasServerConnection;
|
||||||
bool _isInitialized = false;
|
bool _isInitialized = false;
|
||||||
|
|
||||||
OfflineModeProvider(this._serverManager);
|
OfflineModeProvider(this._serverManager)
|
||||||
|
: _hasServerConnection = _serverManager.onlineServerIds.isNotEmpty;
|
||||||
|
|
||||||
/// Whether the app is currently in offline mode
|
/// Whether the app is currently in offline mode
|
||||||
/// Offline = no network OR no servers reachable
|
/// Offline = no network OR no servers reachable
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import '../utils/app_logger.dart';
|
|||||||
import '../utils/provider_extensions.dart';
|
import '../utils/provider_extensions.dart';
|
||||||
import '../utils/video_player_navigation.dart';
|
import '../utils/video_player_navigation.dart';
|
||||||
import '../utils/content_rating_formatter.dart';
|
import '../utils/content_rating_formatter.dart';
|
||||||
|
import '../utils/layout_constants.dart';
|
||||||
import '../focus/dpad_navigator.dart';
|
import '../focus/dpad_navigator.dart';
|
||||||
import 'auth_screen.dart';
|
import 'auth_screen.dart';
|
||||||
|
|
||||||
@@ -467,6 +468,8 @@ class _DiscoverScreenState extends State<DiscoverScreen>
|
|||||||
// Wait for libraries and then fetch hubs
|
// Wait for libraries and then fetch hubs
|
||||||
final librariesByServer = await librariesFuture;
|
final librariesByServer = await librariesFuture;
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
// Get hidden libraries to filter from hubs
|
// Get hidden libraries to filter from hubs
|
||||||
final hiddenLibrariesProvider = Provider.of<HiddenLibrariesProvider>(
|
final hiddenLibrariesProvider = Provider.of<HiddenLibrariesProvider>(
|
||||||
context,
|
context,
|
||||||
@@ -494,6 +497,7 @@ class _DiscoverScreenState extends State<DiscoverScreen>
|
|||||||
appLogger.d(
|
appLogger.d(
|
||||||
'Received ${onDeck.length} on deck items and ${filteredHubs.length} hubs from all servers',
|
'Received ${onDeck.length} on deck items and ${filteredHubs.length} hubs from all servers',
|
||||||
);
|
);
|
||||||
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_hubs = filteredHubs;
|
_hubs = filteredHubs;
|
||||||
_areHubsLoading = false;
|
_areHubsLoading = false;
|
||||||
@@ -554,6 +558,7 @@ class _DiscoverScreenState extends State<DiscoverScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Public method to fully reload all content (for profile switches)
|
// Public method to fully reload all content (for profile switches)
|
||||||
|
@override
|
||||||
void fullRefresh() {
|
void fullRefresh() {
|
||||||
appLogger.d('DiscoverScreen.fullRefresh() called - reloading all content');
|
appLogger.d('DiscoverScreen.fullRefresh() called - reloading all content');
|
||||||
// Reload all content including On Deck and content hubs
|
// Reload all content including On Deck and content hubs
|
||||||
@@ -782,6 +787,7 @@ class _DiscoverScreenState extends State<DiscoverScreen>
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
).then((value) {
|
).then((value) {
|
||||||
|
if (!context.mounted) return;
|
||||||
if (value == 'switch_profile') {
|
if (value == 'switch_profile') {
|
||||||
_handleSwitchProfile(context);
|
_handleSwitchProfile(context);
|
||||||
} else if (value == 'logout') {
|
} else if (value == 'logout') {
|
||||||
|
|||||||
@@ -244,6 +244,7 @@ class _LibrariesScreenState extends State<LibrariesScreen>
|
|||||||
/// If the active tab has already loaded data (often the case after preloading
|
/// If the active tab has already loaded data (often the case after preloading
|
||||||
/// while on another main tab), re-request focus so the first item is focused
|
/// while on another main tab), re-request focus so the first item is focused
|
||||||
/// once the screen is actually shown.
|
/// once the screen is actually shown.
|
||||||
|
@override
|
||||||
void focusActiveTabIfReady() {
|
void focusActiveTabIfReady() {
|
||||||
if (_selectedLibraryGlobalKey == null) return;
|
if (_selectedLibraryGlobalKey == null) return;
|
||||||
_focusCurrentTab();
|
_focusCurrentTab();
|
||||||
@@ -452,6 +453,7 @@ class _LibrariesScreenState extends State<LibrariesScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Public method to load a library by key (called from MainScreen side nav)
|
/// Public method to load a library by key (called from MainScreen side nav)
|
||||||
|
@override
|
||||||
void loadLibraryByKey(String libraryGlobalKey) {
|
void loadLibraryByKey(String libraryGlobalKey) {
|
||||||
_loadLibraryContent(libraryGlobalKey);
|
_loadLibraryContent(libraryGlobalKey);
|
||||||
}
|
}
|
||||||
@@ -716,6 +718,7 @@ class _LibrariesScreenState extends State<LibrariesScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Public method to fully reload all content (for profile switches)
|
// Public method to fully reload all content (for profile switches)
|
||||||
|
@override
|
||||||
void fullRefresh() {
|
void fullRefresh() {
|
||||||
appLogger.d('LibrariesScreen.fullRefresh() called - reloading all content');
|
appLogger.d('LibrariesScreen.fullRefresh() called - reloading all content');
|
||||||
// Reload libraries and clear any selected library/filters
|
// Reload libraries and clear any selected library/filters
|
||||||
|
|||||||
@@ -86,53 +86,55 @@ class _SortBottomSheetState extends State<SortBottomSheet> {
|
|||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: RadioGroup<PlexSort>(
|
||||||
controller: scrollController,
|
groupValue: _currentSort,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
onChanged: (value) {
|
||||||
itemCount: widget.sortOptions.length,
|
if (value != null) {
|
||||||
itemBuilder: (context, index) {
|
_handleSortChange(value, value.isDefaultDescending);
|
||||||
final sort = widget.sortOptions[index];
|
}
|
||||||
final isSelected = _currentSort?.key == sort.key;
|
|
||||||
|
|
||||||
return FocusableRadioListTile<PlexSort>(
|
|
||||||
focusNode: index == 0 ? _initialFocusNode : null,
|
|
||||||
title: Text(sort.title),
|
|
||||||
value: sort,
|
|
||||||
groupValue: _currentSort,
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value != null) {
|
|
||||||
_handleSortChange(value, value.isDefaultDescending);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
secondary: isSelected
|
|
||||||
? SegmentedButton<bool>(
|
|
||||||
showSelectedIcon: false,
|
|
||||||
segments: const [
|
|
||||||
ButtonSegment(
|
|
||||||
value: false,
|
|
||||||
icon: AppIcon(
|
|
||||||
Symbols.arrow_upward_rounded,
|
|
||||||
fill: 1,
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ButtonSegment(
|
|
||||||
value: true,
|
|
||||||
icon: AppIcon(
|
|
||||||
Symbols.arrow_downward_rounded,
|
|
||||||
fill: 1,
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
selected: {_currentDescending},
|
|
||||||
onSelectionChanged: (Set<bool> newSelection) {
|
|
||||||
_handleSortChange(sort, newSelection.first);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
child: ListView.builder(
|
||||||
|
controller: scrollController,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
itemCount: widget.sortOptions.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final sort = widget.sortOptions[index];
|
||||||
|
final isSelected = _currentSort?.key == sort.key;
|
||||||
|
|
||||||
|
return FocusableRadioListTile<PlexSort>(
|
||||||
|
focusNode: index == 0 ? _initialFocusNode : null,
|
||||||
|
title: Text(sort.title),
|
||||||
|
value: sort,
|
||||||
|
secondary: isSelected
|
||||||
|
? SegmentedButton<bool>(
|
||||||
|
showSelectedIcon: false,
|
||||||
|
segments: const [
|
||||||
|
ButtonSegment(
|
||||||
|
value: false,
|
||||||
|
icon: AppIcon(
|
||||||
|
Symbols.arrow_upward_rounded,
|
||||||
|
fill: 1,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ButtonSegment(
|
||||||
|
value: true,
|
||||||
|
icon: AppIcon(
|
||||||
|
Symbols.arrow_downward_rounded,
|
||||||
|
fill: 1,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
selected: {_currentDescending},
|
||||||
|
onSelectionChanged: (Set<bool> newSelection) {
|
||||||
|
_handleSortChange(sort, newSelection.first);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -365,35 +365,36 @@ class _LibraryBrowseTabState
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (sheetContext) {
|
builder: (sheetContext) {
|
||||||
final options = _getGroupingOptions();
|
final options = _getGroupingOptions();
|
||||||
return ListView.builder(
|
return RadioGroup<String>(
|
||||||
shrinkWrap: true,
|
groupValue: _selectedGrouping,
|
||||||
itemCount: options.length,
|
onChanged: (value) async {
|
||||||
itemBuilder: (context, index) {
|
if (value == null) return;
|
||||||
final grouping = options[index];
|
setState(() {
|
||||||
return RadioListTile<String>(
|
_selectedGrouping = value;
|
||||||
title: Text(_getGroupingLabel(grouping)),
|
});
|
||||||
value: grouping,
|
|
||||||
groupValue: _selectedGrouping,
|
|
||||||
onChanged: (value) async {
|
|
||||||
if (value != null) {
|
|
||||||
setState(() {
|
|
||||||
_selectedGrouping = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
final storage = await StorageService.getInstance();
|
final storage = await StorageService.getInstance();
|
||||||
await storage.saveLibraryGrouping(
|
await storage.saveLibraryGrouping(
|
||||||
widget.library.globalKey,
|
widget.library.globalKey,
|
||||||
value,
|
value,
|
||||||
);
|
|
||||||
|
|
||||||
if (!sheetContext.mounted) return;
|
|
||||||
|
|
||||||
Navigator.pop(sheetContext);
|
|
||||||
_loadItems();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!sheetContext.mounted || !mounted) return;
|
||||||
|
|
||||||
|
Navigator.pop(sheetContext);
|
||||||
|
_loadItems();
|
||||||
},
|
},
|
||||||
|
child: ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: options.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final grouping = options[index];
|
||||||
|
return RadioListTile<String>(
|
||||||
|
title: Text(_getGroupingLabel(grouping)),
|
||||||
|
value: grouping,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
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:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../../services/plex_client.dart';
|
import '../../services/plex_client.dart';
|
||||||
import '../i18n/strings.g.dart';
|
|
||||||
import '../utils/app_logger.dart';
|
import '../utils/app_logger.dart';
|
||||||
import '../utils/provider_extensions.dart';
|
import '../utils/provider_extensions.dart';
|
||||||
import '../utils/platform_detector.dart';
|
import '../utils/platform_detector.dart';
|
||||||
@@ -313,7 +310,8 @@ class _MainScreenState extends State<MainScreen> with RouteAware {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Trigger watch state sync now that servers are connected
|
// Trigger watch state sync now that servers are connected
|
||||||
if (connectedCount > 0 && context.mounted) {
|
if (connectedCount > 0) {
|
||||||
|
if (!mounted) return;
|
||||||
context.read<OfflineWatchSyncService>().onServersConnected();
|
context.read<OfflineWatchSyncService>().onServersConnected();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -460,42 +458,3 @@ class _MainScreenState extends State<MainScreen> with RouteAware {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Placeholder widget shown for network-dependent screens when offline
|
|
||||||
class _OfflinePlaceholder extends StatelessWidget {
|
|
||||||
const _OfflinePlaceholder();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(32),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
AppIcon(
|
|
||||||
Symbols.cloud_off_rounded,
|
|
||||||
fill: 1,
|
|
||||||
size: 64,
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
t.messages.youAreOffline,
|
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
t.messages.offlineFeatureUnavailable,
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -99,7 +99,10 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
|
|||||||
value: 1.0,
|
value: 1.0,
|
||||||
strokeWidth: 2.0,
|
strokeWidth: 2.0,
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(
|
valueColor: AlwaysStoppedAnimation<Color>(
|
||||||
Theme.of(context).colorScheme.primary.withOpacity(0.2),
|
Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary
|
||||||
|
.withValues(alpha: 0.2),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Progress circle (indeterminate if no progress, determinate otherwise)
|
// Progress circle (indeterminate if no progress, determinate otherwise)
|
||||||
@@ -368,6 +371,8 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
|
|||||||
await Future.delayed(const Duration(milliseconds: 100));
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
if (_seasons.isEmpty) {
|
if (_seasons.isEmpty) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
@@ -382,6 +387,7 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
|
|||||||
|
|
||||||
// Get episodes of the first season
|
// Get episodes of the first season
|
||||||
List<PlexMetadata> episodes;
|
List<PlexMetadata> episodes;
|
||||||
|
if (!mounted) return;
|
||||||
if (widget.isOffline) {
|
if (widget.isOffline) {
|
||||||
// In offline mode, get episodes from downloads
|
// In offline mode, get episodes from downloads
|
||||||
final downloadProvider = context.read<DownloadProvider>();
|
final downloadProvider = context.read<DownloadProvider>();
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ class _SearchScreenState extends State<SearchScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Focus the search input field
|
/// Focus the search input field
|
||||||
|
@override
|
||||||
void focusSearchInput() {
|
void focusSearchInput() {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
_searchFocusNode.requestFocus();
|
_searchFocusNode.requestFocus();
|
||||||
@@ -138,6 +139,7 @@ class _SearchScreenState extends State<SearchScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Public method to fully reload all content (for profile switches)
|
// Public method to fully reload all content (for profile switches)
|
||||||
|
@override
|
||||||
void fullRefresh() {
|
void fullRefresh() {
|
||||||
appLogger.d(
|
appLogger.d(
|
||||||
'SearchScreen.fullRefresh() called - clearing search and reloading',
|
'SearchScreen.fullRefresh() called - clearing search and reloading',
|
||||||
|
|||||||
@@ -479,14 +479,14 @@ class _EpisodeCard extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
// Background circle
|
// Background circle
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
value: 1.0,
|
value: 1.0,
|
||||||
strokeWidth: 1.5,
|
strokeWidth: 1.5,
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(
|
valueColor: AlwaysStoppedAnimation<Color>(
|
||||||
getMutedColor(
|
getMutedColor(
|
||||||
Colors.blue,
|
Colors.blue,
|
||||||
).withOpacity(0.3),
|
).withValues(alpha: 0.3),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
// Progress circle
|
// Progress circle
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
value: progress?.progressPercent,
|
value: progress?.progressPercent,
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'dart:collection';
|
|
||||||
|
|
||||||
class LogRedactionManager {
|
class LogRedactionManager {
|
||||||
// Size limits for bounded sets (FIFO eviction when exceeded)
|
// Size limits for bounded sets (FIFO eviction when exceeded)
|
||||||
static const int _maxTokens = 50;
|
static const int _maxTokens = 50;
|
||||||
@@ -7,9 +5,9 @@ class LogRedactionManager {
|
|||||||
static const int _maxCustomValues = 50;
|
static const int _maxCustomValues = 50;
|
||||||
|
|
||||||
// Use LinkedHashSet for FIFO ordering
|
// Use LinkedHashSet for FIFO ordering
|
||||||
static final Set<String> _tokens = LinkedHashSet<String>();
|
static final Set<String> _tokens = <String>{};
|
||||||
static final Set<String> _urls = LinkedHashSet<String>();
|
static final Set<String> _urls = <String>{};
|
||||||
static final Set<String> _customValues = LinkedHashSet<String>();
|
static final Set<String> _customValues = <String>{};
|
||||||
|
|
||||||
static final RegExp _ipv4Pattern = RegExp(
|
static final RegExp _ipv4Pattern = RegExp(
|
||||||
r'\b(\d{1,3})([.-])(\d{1,3})\2(\d{1,3})\2(\d{1,3})\b',
|
r'\b(\d{1,3})([.-])(\d{1,3})\2(\d{1,3})\2(\d{1,3})\b',
|
||||||
|
|||||||
@@ -65,10 +65,12 @@ class DeletionProgressDialog extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
progress.currentOperation!,
|
progress.currentOperation!,
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
color: Theme.of(
|
color: Theme.of(context)
|
||||||
context,
|
.textTheme
|
||||||
).textTheme.bodySmall?.color?.withOpacity(0.7),
|
.bodySmall
|
||||||
),
|
?.color
|
||||||
|
?.withValues(alpha: 0.7),
|
||||||
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -48,13 +48,17 @@ class DownloadTreeNode {
|
|||||||
|
|
||||||
// Aggregate status from children
|
// Aggregate status from children
|
||||||
final statuses = children.map((c) => c.status).toSet();
|
final statuses = children.map((c) => c.status).toSet();
|
||||||
if (statuses.contains(DownloadStatus.downloading))
|
if (statuses.contains(DownloadStatus.downloading)) {
|
||||||
return DownloadStatus.downloading;
|
return DownloadStatus.downloading;
|
||||||
|
}
|
||||||
if (statuses.contains(DownloadStatus.queued)) return DownloadStatus.queued;
|
if (statuses.contains(DownloadStatus.queued)) return DownloadStatus.queued;
|
||||||
if (statuses.contains(DownloadStatus.paused)) return DownloadStatus.paused;
|
if (statuses.contains(DownloadStatus.paused)) return DownloadStatus.paused;
|
||||||
if (statuses.contains(DownloadStatus.failed)) return DownloadStatus.failed;
|
if (statuses.contains(DownloadStatus.failed)) {
|
||||||
if (statuses.every((s) => s == DownloadStatus.completed))
|
return DownloadStatus.failed;
|
||||||
|
}
|
||||||
|
if (statuses.every((s) => s == DownloadStatus.completed)) {
|
||||||
return DownloadStatus.completed;
|
return DownloadStatus.completed;
|
||||||
|
}
|
||||||
return DownloadStatus.queued;
|
return DownloadStatus.queued;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,7 +150,8 @@ class DownloadTreeItem extends StatelessWidget {
|
|||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
value: 1.0,
|
value: 1.0,
|
||||||
strokeWidth: 3.0,
|
strokeWidth: 3.0,
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(color.withOpacity(0.2)),
|
valueColor:
|
||||||
|
AlwaysStoppedAnimation<Color>(color.withValues(alpha: 0.2)),
|
||||||
),
|
),
|
||||||
// Progress circle
|
// Progress circle
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
|
|||||||
@@ -397,13 +397,14 @@ class _DownloadTreeViewState extends State<DownloadTreeView> {
|
|||||||
|
|
||||||
if (canExpand) ...[
|
if (canExpand) ...[
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
_getNodeSummary(node),
|
_getNodeSummary(node),
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
color: theme.colorScheme.onSurface.withOpacity(0.6),
|
color:
|
||||||
),
|
theme.colorScheme.onSurface.withValues(alpha: 0.6),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
|
],
|
||||||
|
|
||||||
// Progress bar
|
// Progress bar
|
||||||
if (node.status == DownloadStatus.downloading ||
|
if (node.status == DownloadStatus.downloading ||
|
||||||
@@ -418,7 +419,8 @@ class _DownloadTreeViewState extends State<DownloadTreeView> {
|
|||||||
Text(
|
Text(
|
||||||
'${(node.progress * 100).toStringAsFixed(1)}% - ${node.downloadProgress!.speedFormatted}',
|
'${(node.progress * 100).toStringAsFixed(1)}% - ${node.downloadProgress!.speedFormatted}',
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
color: theme.colorScheme.onSurface.withOpacity(0.6),
|
color:
|
||||||
|
theme.colorScheme.onSurface.withValues(alpha: 0.6),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:plezy/widgets/app_icon.dart';
|
import 'package:plezy/widgets/app_icon.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
|
|
||||||
import '../focus/focusable_chip_mixin.dart';
|
import '../focus/focusable_chip_mixin.dart';
|
||||||
import '../focus/input_mode_tracker.dart';
|
import '../focus/input_mode_tracker.dart';
|
||||||
|
|||||||
@@ -92,12 +92,6 @@ class FocusableRadioListTile<T> extends StatelessWidget {
|
|||||||
/// The value represented by this radio button.
|
/// The value represented by this radio button.
|
||||||
final T value;
|
final T value;
|
||||||
|
|
||||||
/// The currently selected value for this group of radio buttons.
|
|
||||||
final T? groupValue;
|
|
||||||
|
|
||||||
/// Called when the user selects this radio button.
|
|
||||||
final ValueChanged<T?>? onChanged;
|
|
||||||
|
|
||||||
/// Whether this radio button is part of a vertically dense list.
|
/// Whether this radio button is part of a vertically dense list.
|
||||||
final bool dense;
|
final bool dense;
|
||||||
|
|
||||||
@@ -107,17 +101,19 @@ class FocusableRadioListTile<T> extends StatelessWidget {
|
|||||||
/// Whether this tile should autofocus when first built.
|
/// Whether this tile should autofocus when first built.
|
||||||
final bool autofocus;
|
final bool autofocus;
|
||||||
|
|
||||||
|
/// Whether the radio tile is interactive.
|
||||||
|
final bool? enabled;
|
||||||
|
|
||||||
const FocusableRadioListTile({
|
const FocusableRadioListTile({
|
||||||
super.key,
|
super.key,
|
||||||
this.title,
|
this.title,
|
||||||
this.subtitle,
|
this.subtitle,
|
||||||
this.secondary,
|
this.secondary,
|
||||||
required this.value,
|
required this.value,
|
||||||
required this.groupValue,
|
|
||||||
required this.onChanged,
|
|
||||||
this.dense = false,
|
this.dense = false,
|
||||||
this.focusNode,
|
this.focusNode,
|
||||||
this.autofocus = false,
|
this.autofocus = false,
|
||||||
|
this.enabled,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -127,11 +123,10 @@ class FocusableRadioListTile<T> extends StatelessWidget {
|
|||||||
subtitle: subtitle,
|
subtitle: subtitle,
|
||||||
secondary: secondary,
|
secondary: secondary,
|
||||||
value: value,
|
value: value,
|
||||||
groupValue: groupValue,
|
|
||||||
onChanged: onChanged,
|
|
||||||
dense: dense,
|
dense: dense,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
autofocus: autofocus,
|
autofocus: autofocus,
|
||||||
|
enabled: enabled,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
|
|
||||||
import '../focus/focusable_chip_mixin.dart';
|
import '../focus/focusable_chip_mixin.dart';
|
||||||
import '../focus/input_mode_tracker.dart';
|
import '../focus/input_mode_tracker.dart';
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ class MediaCardState extends State<MediaCard> {
|
|||||||
isOffline: widget.isOffline,
|
isOffline: widget.isOffline,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!context.mounted) return;
|
||||||
|
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case MediaNavigationResult.unsupported:
|
case MediaNavigationResult.unsupported:
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
|
|||||||
trailing: Switch(
|
trailing: Switch(
|
||||||
value: _enableHDR,
|
value: _enableHDR,
|
||||||
onChanged: (_) => _toggleHDR(),
|
onChanged: (_) => _toggleHDR(),
|
||||||
activeColor: Colors.amber,
|
activeThumbColor: Colors.amber,
|
||||||
),
|
),
|
||||||
onTap: _toggleHDR,
|
onTap: _toggleHDR,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:plezy/widgets/app_icon.dart';
|
import 'package:plezy/widgets/app_icon.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
|
|
||||||
import '../../focus/focusable_wrapper.dart';
|
import '../../focus/focusable_wrapper.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
|
|
||||||
import '../../../models/plex_media_info.dart';
|
import '../../../models/plex_media_info.dart';
|
||||||
import '../../../i18n/strings.g.dart';
|
import '../../../i18n/strings.g.dart';
|
||||||
import '../../../focus/focusable_wrapper.dart';
|
import '../../../focus/focusable_wrapper.dart';
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
|
|
||||||
import '../../../mpv/mpv.dart';
|
import '../../../mpv/mpv.dart';
|
||||||
import '../../../models/plex_media_info.dart';
|
import '../../../models/plex_media_info.dart';
|
||||||
|
|||||||
Reference in New Issue
Block a user