refactor: format
This commit is contained in:
@@ -191,11 +191,9 @@ class AppDatabase extends _$AppDatabase {
|
||||
|
||||
/// Get all downloaded media items (for syncing watch states)
|
||||
Future<List<DownloadedMediaItem>> getAllDownloadedMetadata() {
|
||||
return (select(downloadedMedia)
|
||||
..where(
|
||||
(t) => t.status.equals(DownloadStatus.completed.index),
|
||||
))
|
||||
.get();
|
||||
return (select(
|
||||
downloadedMedia,
|
||||
)..where((t) => t.status.equals(DownloadStatus.completed.index))).get();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -905,7 +905,8 @@ class DownloadProvider extends ChangeNotifier {
|
||||
|
||||
// Ensure show artwork is downloaded even if metadata already existed
|
||||
final thumbPath = showWithServer.thumb;
|
||||
final hasPoster = thumbPath != null &&
|
||||
final hasPoster =
|
||||
thumbPath != null &&
|
||||
await storageService.artworkExists(serverId, thumbPath);
|
||||
if (!hasPoster) {
|
||||
await _downloadManager.downloadArtworkForMetadata(
|
||||
@@ -916,9 +917,7 @@ class DownloadProvider extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// Store artwork reference in provider's map for offline display
|
||||
_artworkPaths[showGlobalKey] = DownloadedArtwork(
|
||||
thumbPath: thumbPath,
|
||||
);
|
||||
_artworkPaths[showGlobalKey] = DownloadedArtwork(thumbPath: thumbPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -948,7 +947,8 @@ class DownloadProvider extends ChangeNotifier {
|
||||
|
||||
// Ensure season artwork is downloaded even if metadata already existed
|
||||
final thumbPath = seasonWithServer.thumb;
|
||||
final hasPoster = thumbPath != null &&
|
||||
final hasPoster =
|
||||
thumbPath != null &&
|
||||
await storageService.artworkExists(serverId, thumbPath);
|
||||
if (!hasPoster) {
|
||||
await _downloadManager.downloadArtworkForMetadata(
|
||||
|
||||
@@ -133,7 +133,12 @@ abstract class BaseMediaListDetailScreen<T extends StatefulWidget>
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const AppIcon(Symbols.error_outline_rounded, fill: 1, size: 48, color: Colors.red),
|
||||
const AppIcon(
|
||||
Symbols.error_outline_rounded,
|
||||
fill: 1,
|
||||
size: 48,
|
||||
color: Colors.red,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(errorMessage!),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
@@ -845,7 +845,11 @@ class _DiscoverScreenState extends State<DiscoverScreen>
|
||||
size: 32,
|
||||
showIndicators: false,
|
||||
)
|
||||
: const AppIcon(Symbols.account_circle_rounded, fill: 1, size: 32),
|
||||
: const AppIcon(
|
||||
Symbols.account_circle_rounded,
|
||||
fill: 1,
|
||||
size: 32,
|
||||
),
|
||||
onSelected: (value) {
|
||||
if (value == 'switch_profile') {
|
||||
_handleSwitchProfile(context);
|
||||
@@ -894,7 +898,12 @@ class _DiscoverScreenState extends State<DiscoverScreen>
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const AppIcon(Symbols.error_outline_rounded, fill: 1, size: 48, color: Colors.red),
|
||||
const AppIcon(
|
||||
Symbols.error_outline_rounded,
|
||||
fill: 1,
|
||||
size: 48,
|
||||
color: Colors.red,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(_errorMessage!),
|
||||
const SizedBox(height: 16),
|
||||
@@ -1008,7 +1017,12 @@ class _DiscoverScreenState extends State<DiscoverScreen>
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
AppIcon(Symbols.movie_rounded, fill: 1, size: 64, color: Colors.grey),
|
||||
AppIcon(
|
||||
Symbols.movie_rounded,
|
||||
fill: 1,
|
||||
size: 64,
|
||||
color: Colors.grey,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(t.discover.noContentAvailable),
|
||||
SizedBox(height: 8),
|
||||
@@ -1072,7 +1086,16 @@ class _DiscoverScreenState extends State<DiscoverScreen>
|
||||
_pauseAutoScroll();
|
||||
}
|
||||
},
|
||||
child: AppIcon(_isAutoScrollPaused ? Symbols.play_arrow_rounded : Symbols.pause_rounded, fill: 1, color: Colors.white, size: 18, semanticLabel: '${_isAutoScrollPaused ? t.discover.play : t.discover.pause} auto-scroll'),
|
||||
child: AppIcon(
|
||||
_isAutoScrollPaused
|
||||
? Symbols.play_arrow_rounded
|
||||
: Symbols.pause_rounded,
|
||||
fill: 1,
|
||||
color: Colors.white,
|
||||
size: 18,
|
||||
semanticLabel:
|
||||
'${_isAutoScrollPaused ? t.discover.play : t.discover.pause} auto-scroll',
|
||||
),
|
||||
),
|
||||
// Spacer to separate indicators from button
|
||||
const SizedBox(width: 8),
|
||||
@@ -1525,7 +1548,12 @@ class _DiscoverScreenState extends State<DiscoverScreen>
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const AppIcon(Symbols.play_arrow_rounded, fill: 1, size: 20, color: Colors.black),
|
||||
const AppIcon(
|
||||
Symbols.play_arrow_rounded,
|
||||
fill: 1,
|
||||
size: 20,
|
||||
color: Colors.black,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
if (hasProgress) ...[
|
||||
// Progress bar
|
||||
|
||||
@@ -240,7 +240,9 @@ class DownloadsScreenState extends State<DownloadsScreen>
|
||||
// Helper to get client from globalKey (serverId:ratingKey)
|
||||
getClient(String globalKey) {
|
||||
final serverId = globalKey.split(':').first;
|
||||
return serverProvider.serverManager.getClient(serverId);
|
||||
return serverProvider.serverManager.getClient(
|
||||
serverId,
|
||||
);
|
||||
}
|
||||
|
||||
return DownloadTreeView(
|
||||
@@ -250,13 +252,19 @@ class DownloadsScreenState extends State<DownloadsScreen>
|
||||
onResume: (globalKey) {
|
||||
final client = getClient(globalKey);
|
||||
if (client != null) {
|
||||
downloadProvider.resumeDownload(globalKey, client);
|
||||
downloadProvider.resumeDownload(
|
||||
globalKey,
|
||||
client,
|
||||
);
|
||||
}
|
||||
},
|
||||
onRetry: (globalKey) {
|
||||
final client = getClient(globalKey);
|
||||
if (client != null) {
|
||||
downloadProvider.retryDownload(globalKey, client);
|
||||
downloadProvider.retryDownload(
|
||||
globalKey,
|
||||
client,
|
||||
);
|
||||
}
|
||||
},
|
||||
onCancel: downloadProvider.cancelDownload,
|
||||
@@ -339,7 +347,12 @@ class _DownloadsGridContent extends StatelessWidget {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
AppIcon(Symbols.download_rounded, fill: 1, size: 80, color: Theme.of(context).colorScheme.outline),
|
||||
AppIcon(
|
||||
Symbols.download_rounded,
|
||||
fill: 1,
|
||||
size: 80,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
t.downloads.noDownloads,
|
||||
|
||||
@@ -245,7 +245,11 @@ class _HubDetailScreenState extends State<HubDetailScreen> with Refreshable {
|
||||
title: Text(widget.hub.title),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: AppIcon(Symbols.swap_vert_rounded, fill: 1, semanticLabel: t.libraries.sort),
|
||||
icon: AppIcon(
|
||||
Symbols.swap_vert_rounded,
|
||||
fill: 1,
|
||||
semanticLabel: t.libraries.sort,
|
||||
),
|
||||
onPressed: _showSortBottomSheet,
|
||||
),
|
||||
],
|
||||
@@ -256,7 +260,12 @@ class _HubDetailScreenState extends State<HubDetailScreen> with Refreshable {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const AppIcon(Symbols.error_outline_rounded, fill: 1, size: 48, color: Colors.red),
|
||||
const AppIcon(
|
||||
Symbols.error_outline_rounded,
|
||||
fill: 1,
|
||||
size: 48,
|
||||
color: Colors.red,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(_errorMessage!),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
@@ -81,7 +81,13 @@ class FolderTreeItem extends StatelessWidget {
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: AppIcon(isExpanded ? Symbols.keyboard_arrow_down_rounded : Symbols.keyboard_arrow_right_rounded, fill: 1, size: 20),
|
||||
: AppIcon(
|
||||
isExpanded
|
||||
? Symbols.keyboard_arrow_down_rounded
|
||||
: Symbols.keyboard_arrow_right_rounded,
|
||||
fill: 1,
|
||||
size: 20,
|
||||
),
|
||||
)
|
||||
else
|
||||
const SizedBox(width: 24),
|
||||
@@ -89,7 +95,16 @@ class FolderTreeItem extends StatelessWidget {
|
||||
const SizedBox(width: 8),
|
||||
|
||||
// File/folder icon
|
||||
AppIcon(_getIcon(), fill: 1, size: 20, color: isFolder ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7)),
|
||||
AppIcon(
|
||||
_getIcon(),
|
||||
fill: 1,
|
||||
size: 20,
|
||||
color: isFolder
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurface.withValues(alpha: 0.7),
|
||||
),
|
||||
|
||||
const SizedBox(width: 12),
|
||||
|
||||
|
||||
@@ -963,7 +963,12 @@ class _LibrariesScreenState extends State<LibrariesScreen>
|
||||
value: library.globalKey,
|
||||
child: Row(
|
||||
children: [
|
||||
AppIcon(_getLibraryIcon(library.type), fill: 1, size: 20, color: isSelected ? Theme.of(context).colorScheme.primary : null),
|
||||
AppIcon(
|
||||
_getLibraryIcon(library.type),
|
||||
fill: 1,
|
||||
size: 20,
|
||||
color: isSelected ? Theme.of(context).colorScheme.primary : null,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
@@ -1170,7 +1175,12 @@ class _LibrariesScreenState extends State<LibrariesScreen>
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const AppIcon(Symbols.error_outline_rounded, fill: 1, size: 48, color: Colors.red),
|
||||
const AppIcon(
|
||||
Symbols.error_outline_rounded,
|
||||
fill: 1,
|
||||
size: 48,
|
||||
color: Colors.red,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(_errorMessage!),
|
||||
const SizedBox(height: 16),
|
||||
@@ -1188,7 +1198,12 @@ class _LibrariesScreenState extends State<LibrariesScreen>
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const AppIcon(Symbols.video_library_rounded, fill: 1, size: 64, color: Colors.grey),
|
||||
const AppIcon(
|
||||
Symbols.video_library_rounded,
|
||||
fill: 1,
|
||||
size: 64,
|
||||
color: Colors.grey,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(t.libraries.noLibrariesFound),
|
||||
],
|
||||
@@ -1684,7 +1699,15 @@ class _LibraryManagementSheetState extends State<_LibraryManagementSheet> {
|
||||
children: [
|
||||
ReorderableDragStartListener(
|
||||
index: index,
|
||||
child: AppIcon(isMoving ? Symbols.swap_vert_rounded : Symbols.drag_indicator_rounded, fill: 1, color: isMoving ? colorScheme.primary : IconTheme.of(context).color?.withValues(alpha: 0.5)),
|
||||
child: AppIcon(
|
||||
isMoving
|
||||
? Symbols.swap_vert_rounded
|
||||
: Symbols.drag_indicator_rounded,
|
||||
fill: 1,
|
||||
color: isMoving
|
||||
? colorScheme.primary
|
||||
: IconTheme.of(context).color?.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
AppIcon(_getLibraryIcon(library.type), fill: 1),
|
||||
@@ -1713,7 +1736,12 @@ class _LibraryManagementSheetState extends State<_LibraryManagementSheet> {
|
||||
)
|
||||
: null,
|
||||
child: IconButton(
|
||||
icon: AppIcon(isHidden ? Symbols.visibility_off_rounded : Symbols.visibility_rounded, fill: 1),
|
||||
icon: AppIcon(
|
||||
isHidden
|
||||
? Symbols.visibility_off_rounded
|
||||
: Symbols.visibility_rounded,
|
||||
fill: 1,
|
||||
),
|
||||
tooltip: isHidden
|
||||
? t.libraries.showLibrary
|
||||
: t.libraries.hideLibrary,
|
||||
|
||||
@@ -110,11 +110,19 @@ class _SortBottomSheetState extends State<SortBottomSheet> {
|
||||
segments: const [
|
||||
ButtonSegment(
|
||||
value: false,
|
||||
icon: AppIcon(Symbols.arrow_upward_rounded, fill: 1, size: 16),
|
||||
icon: AppIcon(
|
||||
Symbols.arrow_upward_rounded,
|
||||
fill: 1,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: true,
|
||||
icon: AppIcon(Symbols.arrow_downward_rounded, fill: 1, size: 16),
|
||||
icon: AppIcon(
|
||||
Symbols.arrow_downward_rounded,
|
||||
fill: 1,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
selected: {_currentDescending},
|
||||
|
||||
@@ -46,11 +46,16 @@ class StateMessageWidget extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (icon != null) ...[
|
||||
AppIcon(icon, fill: 1, size: 64, color:
|
||||
AppIcon(
|
||||
icon,
|
||||
fill: 1,
|
||||
size: 64,
|
||||
color:
|
||||
iconColor ??
|
||||
Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurface.withValues(alpha: 0.4)),
|
||||
).colorScheme.onSurface.withValues(alpha: 0.4),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
Text(
|
||||
|
||||
@@ -183,16 +183,14 @@ class _MainScreenState extends State<MainScreen> with RouteAware {
|
||||
}
|
||||
|
||||
void _handleOfflineStatusChanged() {
|
||||
final newOffline =
|
||||
_offlineModeProvider?.isOffline ?? widget.isOfflineMode;
|
||||
final newOffline = _offlineModeProvider?.isOffline ?? widget.isOfflineMode;
|
||||
|
||||
if (newOffline == _isOffline) return;
|
||||
|
||||
setState(() {
|
||||
_isOffline = newOffline;
|
||||
_screens = _buildScreens(_isOffline);
|
||||
_selectedLibraryGlobalKey =
|
||||
_isOffline ? null : _selectedLibraryGlobalKey;
|
||||
_selectedLibraryGlobalKey = _isOffline ? null : _selectedLibraryGlobalKey;
|
||||
_currentIndex = _normalizeIndexForMode(_currentIndex, _isOffline);
|
||||
});
|
||||
|
||||
@@ -205,9 +203,7 @@ class _MainScreenState extends State<MainScreen> with RouteAware {
|
||||
if (!_isOffline) {
|
||||
final userProfileProvider = context.userProfile;
|
||||
userProfileProvider.initialize().then((_) {
|
||||
userProfileProvider.setDataInvalidationCallback(
|
||||
_invalidateAllScreens,
|
||||
);
|
||||
userProfileProvider.setDataInvalidationCallback(_invalidateAllScreens);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -467,7 +463,10 @@ class _MainScreenState extends State<MainScreen> with RouteAware {
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: const AppIcon(Symbols.video_library_rounded, fill: 1),
|
||||
selectedIcon: const AppIcon(Symbols.video_library_rounded, fill: 1),
|
||||
selectedIcon: const AppIcon(
|
||||
Symbols.video_library_rounded,
|
||||
fill: 1,
|
||||
),
|
||||
label: t.navigation.libraries,
|
||||
),
|
||||
NavigationDestination(
|
||||
@@ -510,7 +509,12 @@ class _OfflinePlaceholder extends StatelessWidget {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
AppIcon(Symbols.cloud_off_rounded, fill: 1, size: 64, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
AppIcon(
|
||||
Symbols.cloud_off_rounded,
|
||||
fill: 1,
|
||||
size: 64,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
t.messages.youAreOffline,
|
||||
|
||||
@@ -233,7 +233,12 @@ class _PlaylistDetailScreenState
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
AppIcon(Symbols.auto_awesome_rounded, fill: 1, size: 12, color: Colors.blue[300]),
|
||||
AppIcon(
|
||||
Symbols.auto_awesome_rounded,
|
||||
fill: 1,
|
||||
size: 12,
|
||||
color: Colors.blue[300],
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
t.playlists.smartPlaylist,
|
||||
|
||||
@@ -58,7 +58,11 @@ class _PlaylistItemCardState extends State<PlaylistItemCard> {
|
||||
index: widget.index,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(right: 12),
|
||||
child: AppIcon(Symbols.drag_indicator_rounded, fill: 1, color: Colors.grey),
|
||||
child: AppIcon(
|
||||
Symbols.drag_indicator_rounded,
|
||||
fill: 1,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -166,7 +170,12 @@ class _PlaylistItemCardState extends State<PlaylistItemCard> {
|
||||
color: Colors.grey[850],
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: const AppIcon(Symbols.movie_rounded, fill: 1, color: Colors.grey, size: 24),
|
||||
child: const AppIcon(
|
||||
Symbols.movie_rounded,
|
||||
fill: 1,
|
||||
color: Colors.grey,
|
||||
size: 24,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,12 @@ class _PinEntryDialogState extends State<PinEntryDialog>
|
||||
child: AlertDialog(
|
||||
title: Row(
|
||||
children: [
|
||||
AppIcon(Symbols.lock_outline_rounded, fill: 1, size: 24, color: theme.colorScheme.primary),
|
||||
AppIcon(
|
||||
Symbols.lock_outline_rounded,
|
||||
fill: 1,
|
||||
size: 24,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(widget.userName, overflow: TextOverflow.ellipsis),
|
||||
@@ -114,7 +119,13 @@ class _PinEntryDialogState extends State<PinEntryDialog>
|
||||
errorText: widget.errorMessage,
|
||||
errorMaxLines: 2,
|
||||
suffixIcon: IconButton(
|
||||
icon: AppIcon(_obscureText ? Symbols.visibility_off_rounded : Symbols.visibility_rounded, fill: 1, size: 20),
|
||||
icon: AppIcon(
|
||||
_obscureText
|
||||
? Symbols.visibility_off_rounded
|
||||
: Symbols.visibility_rounded,
|
||||
fill: 1,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_obscureText = !_obscureText;
|
||||
|
||||
@@ -48,7 +48,9 @@ class ProfileListTile extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
)
|
||||
: (showTrailingIcon ? const AppIcon(Symbols.chevron_right_rounded, fill: 1) : null),
|
||||
: (showTrailingIcon
|
||||
? const AppIcon(Symbols.chevron_right_rounded, fill: 1)
|
||||
: null),
|
||||
onTap: isCurrentUser ? null : onTap,
|
||||
enabled: !isCurrentUser,
|
||||
);
|
||||
|
||||
@@ -56,7 +56,12 @@ class ProfileSwitchScreen extends StatelessWidget {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
AppIcon(Symbols.person_off_rounded, fill: 1, size: 64, color: theme.colorScheme.onSurfaceVariant),
|
||||
AppIcon(
|
||||
Symbols.person_off_rounded,
|
||||
fill: 1,
|
||||
size: 64,
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'No profiles available',
|
||||
|
||||
@@ -29,7 +29,12 @@ class UserAvatarWidget extends StatelessWidget {
|
||||
color: theme.colorScheme.surfaceContainerHighest,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: AppIcon(Symbols.person_rounded, fill: 1, size: size * 0.6, color: theme.colorScheme.onSurfaceVariant),
|
||||
child: AppIcon(
|
||||
Symbols.person_rounded,
|
||||
fill: 1,
|
||||
size: size * 0.6,
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -217,7 +217,12 @@ class _SearchScreenState extends State<SearchScreen> with Refreshable {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
AppIcon(Symbols.search_rounded, fill: 1, size: 80, color: Colors.grey.shade400),
|
||||
AppIcon(
|
||||
Symbols.search_rounded,
|
||||
fill: 1,
|
||||
size: 80,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
t.search.searchYourMedia,
|
||||
@@ -240,7 +245,12 @@ class _SearchScreenState extends State<SearchScreen> with Refreshable {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
AppIcon(Symbols.search_off_rounded, fill: 1, size: 80, color: Colors.grey.shade400),
|
||||
AppIcon(
|
||||
Symbols.search_off_rounded,
|
||||
fill: 1,
|
||||
size: 80,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
t.messages.noResultsFound,
|
||||
|
||||
@@ -149,7 +149,12 @@ class _SeasonDetailScreenState extends State<SeasonDetailScreen>
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
AppIcon(Symbols.movie_rounded, fill: 1, size: 64, color: tokens(context).textMuted),
|
||||
AppIcon(
|
||||
Symbols.movie_rounded,
|
||||
fill: 1,
|
||||
size: 64,
|
||||
color: tokens(context).textMuted,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
t.messages.noEpisodesFoundGeneral,
|
||||
@@ -315,7 +320,11 @@ class _EpisodeCard extends StatelessWidget {
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.surfaceContainerHighest,
|
||||
child: const AppIcon(Symbols.movie_rounded, fill: 1, size: 32),
|
||||
child: const AppIcon(
|
||||
Symbols.movie_rounded,
|
||||
fill: 1,
|
||||
size: 32,
|
||||
),
|
||||
),
|
||||
)
|
||||
: episode.thumb != null
|
||||
@@ -333,14 +342,22 @@ class _EpisodeCard extends StatelessWidget {
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.surfaceContainerHighest,
|
||||
child: const AppIcon(Symbols.movie_rounded, fill: 1, size: 32),
|
||||
child: const AppIcon(
|
||||
Symbols.movie_rounded,
|
||||
fill: 1,
|
||||
size: 32,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.surfaceContainerHighest,
|
||||
child: const AppIcon(Symbols.movie_rounded, fill: 1, size: 32),
|
||||
child: const AppIcon(
|
||||
Symbols.movie_rounded,
|
||||
fill: 1,
|
||||
size: 32,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -366,7 +383,12 @@ class _EpisodeCard extends StatelessWidget {
|
||||
color: Colors.black.withValues(alpha: 0.6),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const AppIcon(Symbols.play_arrow_rounded, fill: 1, color: Colors.white, size: 20),
|
||||
child: const AppIcon(
|
||||
Symbols.play_arrow_rounded,
|
||||
fill: 1,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -440,7 +462,12 @@ class _EpisodeCard extends StatelessWidget {
|
||||
} else if (progress?.status ==
|
||||
DownloadStatus.queued) {
|
||||
// Queued state - waiting to download
|
||||
downloadStatusIcon = AppIcon(Symbols.schedule_rounded, fill: 1, size: 12, color: getMutedColor(Colors.orange));
|
||||
downloadStatusIcon = AppIcon(
|
||||
Symbols.schedule_rounded,
|
||||
fill: 1,
|
||||
size: 12,
|
||||
color: getMutedColor(Colors.orange),
|
||||
);
|
||||
} else if (progress?.status ==
|
||||
DownloadStatus.downloading) {
|
||||
// Downloading state - active download with radial progress
|
||||
@@ -474,19 +501,39 @@ class _EpisodeCard extends StatelessWidget {
|
||||
} else if (progress?.status ==
|
||||
DownloadStatus.paused) {
|
||||
// Paused state - download paused
|
||||
downloadStatusIcon = AppIcon(Symbols.pause_circle_outline_rounded, fill: 1, size: 12, color: getMutedColor(Colors.amber));
|
||||
downloadStatusIcon = AppIcon(
|
||||
Symbols.pause_circle_outline_rounded,
|
||||
fill: 1,
|
||||
size: 12,
|
||||
color: getMutedColor(Colors.amber),
|
||||
);
|
||||
} else if (progress?.status ==
|
||||
DownloadStatus.failed) {
|
||||
// Failed state - download failed
|
||||
downloadStatusIcon = AppIcon(Symbols.error_outline_rounded, fill: 1, size: 12, color: getMutedColor(Colors.red));
|
||||
downloadStatusIcon = AppIcon(
|
||||
Symbols.error_outline_rounded,
|
||||
fill: 1,
|
||||
size: 12,
|
||||
color: getMutedColor(Colors.red),
|
||||
);
|
||||
} else if (progress?.status ==
|
||||
DownloadStatus.cancelled) {
|
||||
// Cancelled state - download cancelled
|
||||
downloadStatusIcon = AppIcon(Symbols.cancel_rounded, fill: 1, size: 12, color: getMutedColor(Colors.grey));
|
||||
downloadStatusIcon = AppIcon(
|
||||
Symbols.cancel_rounded,
|
||||
fill: 1,
|
||||
size: 12,
|
||||
color: getMutedColor(Colors.grey),
|
||||
);
|
||||
} else if (progress?.status ==
|
||||
DownloadStatus.completed) {
|
||||
// Completed state - download complete
|
||||
downloadStatusIcon = AppIcon(Symbols.file_download_done_rounded, fill: 1, size: 12, color: getMutedColor(Colors.green));
|
||||
downloadStatusIcon = AppIcon(
|
||||
Symbols.file_download_done_rounded,
|
||||
fill: 1,
|
||||
size: 12,
|
||||
color: getMutedColor(Colors.green),
|
||||
);
|
||||
}
|
||||
// Note: No icon shown if not downloaded (null)
|
||||
}
|
||||
|
||||
@@ -80,7 +80,10 @@ class _AboutScreenState extends State<AboutScreen> {
|
||||
leading: const AppIcon(Symbols.description_rounded, fill: 1),
|
||||
title: Text(t.about.openSourceLicenses),
|
||||
subtitle: Text(t.about.viewLicensesDescription),
|
||||
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
|
||||
trailing: const AppIcon(
|
||||
Symbols.chevron_right_rounded,
|
||||
fill: 1,
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
|
||||
@@ -73,7 +73,11 @@ class _HotKeyRecorderWidgetState extends State<HotKeyRecorderWidget> {
|
||||
),
|
||||
if (_recordedHotKey != null)
|
||||
IconButton(
|
||||
icon: const AppIcon(Symbols.backspace_rounded, fill: 1, size: 18),
|
||||
icon: const AppIcon(
|
||||
Symbols.backspace_rounded,
|
||||
fill: 1,
|
||||
size: 18,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_recordedHotKey = null;
|
||||
|
||||
@@ -106,7 +106,10 @@ class _LicensesScreenState extends State<LicensesScreen> {
|
||||
),
|
||||
)
|
||||
: null,
|
||||
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
|
||||
trailing: const AppIcon(
|
||||
Symbols.chevron_right_rounded,
|
||||
fill: 1,
|
||||
),
|
||||
onTap: () => _showLicenseDetail(mergedLicense),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -196,7 +196,12 @@ class _LogEntryCardState extends State<_LogEntryCard> {
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AppIcon(widget.levelIcon, fill: 1, color: widget.levelColor, size: 20),
|
||||
AppIcon(
|
||||
widget.levelIcon,
|
||||
fill: 1,
|
||||
color: widget.levelColor,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
@@ -235,9 +240,15 @@ class _LogEntryCardState extends State<_LogEntryCard> {
|
||||
),
|
||||
),
|
||||
if (hasErrorOrStackTrace)
|
||||
AppIcon(_isExpanded ? Symbols.expand_less_rounded : Symbols.expand_more_rounded, fill: 1, color: Theme.of(
|
||||
AppIcon(
|
||||
_isExpanded
|
||||
? Symbols.expand_less_rounded
|
||||
: Symbols.expand_more_rounded,
|
||||
fill: 1,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).iconTheme.color?.withValues(alpha: 0.6)),
|
||||
).iconTheme.color?.withValues(alpha: 0.6),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_isExpanded && hasErrorOrStackTrace) ...[
|
||||
|
||||
@@ -73,8 +73,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
_autoSkipCredits = _settingsService.getAutoSkipCredits();
|
||||
_autoSkipDelay = _settingsService.getAutoSkipDelay();
|
||||
_downloadOnWifiOnly = _settingsService.getDownloadOnWifiOnly();
|
||||
_videoPlayerNavigationEnabled =
|
||||
_settingsService.getVideoPlayerNavigationEnabled();
|
||||
_videoPlayerNavigationEnabled = _settingsService
|
||||
.getVideoPlayerNavigationEnabled();
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
@@ -386,7 +386,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
|
||||
trailing: const AppIcon(
|
||||
Symbols.chevron_right_rounded,
|
||||
fill: 1,
|
||||
),
|
||||
onTap: () => _showDownloadLocationDialog(),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1375,7 +1375,12 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen>
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const AppIcon(Symbols.play_circle_rounded, fill: 1, size: 64, color: Colors.white),
|
||||
const AppIcon(
|
||||
Symbols.play_circle_rounded,
|
||||
fill: 1,
|
||||
size: 64,
|
||||
color: Colors.white,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Consumer<PlaybackStateProvider>(
|
||||
builder: (context, playbackState, child) {
|
||||
@@ -1394,7 +1399,12 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen>
|
||||
),
|
||||
if (isShuffleActive) ...[
|
||||
const SizedBox(width: 8),
|
||||
const AppIcon(Symbols.shuffle_rounded, fill: 1, size: 20, color: Colors.white70),
|
||||
const AppIcon(
|
||||
Symbols.shuffle_rounded,
|
||||
fill: 1,
|
||||
size: 20,
|
||||
color: Colors.white70,
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
@@ -153,11 +153,14 @@ extension DownloadDatabaseOperations on AppDatabase {
|
||||
|
||||
/// Clear download error and reset retry count (for retry)
|
||||
Future<void> clearDownloadError(String globalKey) async {
|
||||
await (update(downloadedMedia)..where((t) => t.globalKey.equals(globalKey)))
|
||||
.write(const DownloadedMediaCompanion(
|
||||
errorMessage: Value(null),
|
||||
retryCount: Value(0),
|
||||
));
|
||||
await (update(
|
||||
downloadedMedia,
|
||||
)..where((t) => t.globalKey.equals(globalKey))).write(
|
||||
const DownloadedMediaCompanion(
|
||||
errorMessage: Value(null),
|
||||
retryCount: Value(0),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Remove item from queue
|
||||
|
||||
@@ -76,9 +76,7 @@ class OfflineWatchSyncService extends ChangeNotifier {
|
||||
/// Push always happens immediately. Pull respects [minSyncInterval] unless [force] is true.
|
||||
Future<void> _performBidirectionalSync({bool force = false}) async {
|
||||
if (_serverManager.onlineClients.isEmpty) {
|
||||
appLogger.d(
|
||||
'Skipping watch sync - no connected servers available yet',
|
||||
);
|
||||
appLogger.d('Skipping watch sync - no connected servers available yet');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -154,7 +154,12 @@ class _AppBarBackButtonState extends State<AppBarBackButton>
|
||||
color: currentColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: AppIcon(Symbols.arrow_back_rounded, fill: 1, color: effectiveColor, size: 20),
|
||||
child: AppIcon(
|
||||
Symbols.arrow_back_rounded,
|
||||
fill: 1,
|
||||
color: effectiveColor,
|
||||
size: 20,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -121,7 +121,13 @@ class DownloadTreeItem extends StatelessWidget {
|
||||
Widget _buildExpandIcon() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: AppIcon(isExpanded ? Symbols.expand_more_rounded : Symbols.chevron_right_rounded, fill: 1, size: 24),
|
||||
child: AppIcon(
|
||||
isExpanded
|
||||
? Symbols.expand_more_rounded
|
||||
: Symbols.chevron_right_rounded,
|
||||
fill: 1,
|
||||
size: 24,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -364,7 +364,13 @@ class _DownloadTreeViewState extends State<DownloadTreeView> {
|
||||
children: [
|
||||
// Expand/collapse icon
|
||||
if (canExpand)
|
||||
AppIcon(isExpanded ? Symbols.expand_more_rounded : Symbols.chevron_right_rounded, fill: 1, size: 20)
|
||||
AppIcon(
|
||||
isExpanded
|
||||
? Symbols.expand_more_rounded
|
||||
: Symbols.chevron_right_rounded,
|
||||
fill: 1,
|
||||
size: 20,
|
||||
)
|
||||
else
|
||||
const SizedBox(width: 20),
|
||||
|
||||
@@ -498,7 +504,11 @@ class _DownloadTreeViewState extends State<DownloadTreeView> {
|
||||
// Resume button for paused items
|
||||
if (node.status == DownloadStatus.paused && widget.onResume != null)
|
||||
IconButton(
|
||||
icon: const AppIcon(Symbols.play_arrow_rounded, fill: 1, size: 20),
|
||||
icon: const AppIcon(
|
||||
Symbols.play_arrow_rounded,
|
||||
fill: 1,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () => widget.onResume!(globalKey),
|
||||
tooltip: 'Resume',
|
||||
),
|
||||
@@ -548,7 +558,11 @@ class _DownloadTreeViewState extends State<DownloadTreeView> {
|
||||
// Resume all button - show if container is paused
|
||||
if (node.status == DownloadStatus.paused && widget.onResume != null)
|
||||
IconButton(
|
||||
icon: const AppIcon(Symbols.play_arrow_rounded, fill: 1, size: 20),
|
||||
icon: const AppIcon(
|
||||
Symbols.play_arrow_rounded,
|
||||
fill: 1,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () => _resumeAllChildren(node),
|
||||
tooltip: 'Resume all',
|
||||
),
|
||||
@@ -556,7 +570,11 @@ class _DownloadTreeViewState extends State<DownloadTreeView> {
|
||||
// Delete all button
|
||||
if (widget.onDelete != null)
|
||||
IconButton(
|
||||
icon: const AppIcon(Symbols.delete_sweep_rounded, fill: 1, size: 20),
|
||||
icon: const AppIcon(
|
||||
Symbols.delete_sweep_rounded,
|
||||
fill: 1,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () => _deleteAllChildren(node),
|
||||
tooltip: 'Delete all',
|
||||
),
|
||||
|
||||
@@ -58,7 +58,12 @@ class _FileInfoBottomSheetState extends State<FileInfoBottomSheet> {
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
const AppIcon(Symbols.info_rounded, fill: 1, color: Colors.white, size: 24),
|
||||
const AppIcon(
|
||||
Symbols.info_rounded,
|
||||
fill: 1,
|
||||
color: Colors.white,
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
@@ -72,7 +77,11 @@ class _FileInfoBottomSheetState extends State<FileInfoBottomSheet> {
|
||||
),
|
||||
IconButton(
|
||||
focusNode: _initialFocusNode,
|
||||
icon: const AppIcon(Symbols.close_rounded, fill: 1, color: Colors.white),
|
||||
icon: const AppIcon(
|
||||
Symbols.close_rounded,
|
||||
fill: 1,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -287,7 +287,11 @@ class HubSectionState extends State<HubSection> {
|
||||
),
|
||||
if (widget.hub.more) ...[
|
||||
const SizedBox(width: 4),
|
||||
const AppIcon(Symbols.chevron_right_rounded, fill: 1, size: 20),
|
||||
const AppIcon(
|
||||
Symbols.chevron_right_rounded,
|
||||
fill: 1,
|
||||
size: 20,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
|
||||
@@ -717,7 +717,9 @@ Widget _buildPosterImage(
|
||||
}
|
||||
|
||||
return SkeletonLoader(
|
||||
child: Center(child: AppIcon(fallbackIcon, fill: 1, size: 40, color: Colors.white54)),
|
||||
child: Center(
|
||||
child: AppIcon(fallbackIcon, fill: 1, size: 40, color: Colors.white54),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -840,7 +842,12 @@ class _MediaCardHelpers {
|
||||
),
|
||||
],
|
||||
),
|
||||
child: AppIcon(Symbols.check_rounded, fill: 1, color: tokens(context).bg, size: 16),
|
||||
child: AppIcon(
|
||||
Symbols.check_rounded,
|
||||
fill: 1,
|
||||
color: tokens(context).bg,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
// Progress bar for partially watched content
|
||||
|
||||
@@ -309,7 +309,11 @@ class MediaContextMenuState extends State<MediaContextMenu> {
|
||||
itemType == 'show' ||
|
||||
itemType == 'season') {
|
||||
menuActions.add(
|
||||
_MenuAction(value: 'add_to', icon: Symbols.add_rounded, label: t.common.addTo),
|
||||
_MenuAction(
|
||||
value: 'add_to',
|
||||
icon: Symbols.add_rounded,
|
||||
label: t.common.addTo,
|
||||
),
|
||||
);
|
||||
}
|
||||
} // End of regular menu items else block
|
||||
|
||||
@@ -336,7 +336,14 @@ class PlexOptimizedImage extends StatelessWidget {
|
||||
Widget _buildPlaceholder(BuildContext context) {
|
||||
return SkeletonLoader(
|
||||
child: fallbackIcon != null
|
||||
? Center(child: AppIcon(fallbackIcon!, fill: 1, size: 40, color: Colors.white54))
|
||||
? Center(
|
||||
child: AppIcon(
|
||||
fallbackIcon!,
|
||||
fill: 1,
|
||||
size: 40,
|
||||
color: Colors.white54,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
@@ -345,7 +352,12 @@ class PlexOptimizedImage extends StatelessWidget {
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
child: Center(
|
||||
child: AppIcon(fallbackIcon ?? Symbols.broken_image_rounded, fill: 1, size: 40, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
child: AppIcon(
|
||||
fallbackIcon ?? Symbols.broken_image_rounded,
|
||||
fill: 1,
|
||||
size: 40,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -356,7 +368,12 @@ class PlexOptimizedImage extends StatelessWidget {
|
||||
height: height,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
child: Center(
|
||||
child: AppIcon(fallbackIcon ?? Symbols.image_not_supported_rounded, fill: 1, size: 40, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
child: AppIcon(
|
||||
fallbackIcon ?? Symbols.image_not_supported_rounded,
|
||||
fill: 1,
|
||||
size: 40,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -80,7 +80,12 @@ class NavigationRailItem extends StatelessWidget {
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
AppIcon(isSelected && selectedIcon != null ? selectedIcon! : icon, fill: 1, size: iconSize, color: isSelected ? t.text : t.textMuted),
|
||||
AppIcon(
|
||||
isSelected && selectedIcon != null ? selectedIcon! : icon,
|
||||
fill: 1,
|
||||
size: iconSize,
|
||||
color: isSelected ? t.text : t.textMuted,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: label),
|
||||
],
|
||||
@@ -523,7 +528,14 @@ class SideNavigationRailState extends State<SideNavigationRail> {
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
AppIcon(widget.selectedIndex == 1 ? Symbols.video_library_rounded : Symbols.video_library_rounded, fill: 1, size: 22, color: widget.selectedIndex == 1 ? t.text : t.textMuted),
|
||||
AppIcon(
|
||||
widget.selectedIndex == 1
|
||||
? Symbols.video_library_rounded
|
||||
: Symbols.video_library_rounded,
|
||||
fill: 1,
|
||||
size: 22,
|
||||
color: widget.selectedIndex == 1 ? t.text : t.textMuted,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
@@ -539,7 +551,14 @@ class SideNavigationRailState extends State<SideNavigationRail> {
|
||||
),
|
||||
),
|
||||
),
|
||||
AppIcon(_librariesExpanded ? Symbols.expand_less_rounded : Symbols.expand_more_rounded, fill: 1, size: 20, color: t.textMuted),
|
||||
AppIcon(
|
||||
_librariesExpanded
|
||||
? Symbols.expand_less_rounded
|
||||
: Symbols.expand_more_rounded,
|
||||
fill: 1,
|
||||
size: 20,
|
||||
color: t.textMuted,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -422,7 +422,9 @@ class DesktopVideoControlsState extends State<DesktopVideoControls> {
|
||||
return _buildFocusableButton(
|
||||
focusNode: _playPauseFocusNode,
|
||||
index: 2,
|
||||
icon: isPlaying ? Symbols.pause_rounded : Symbols.play_arrow_rounded,
|
||||
icon: isPlaying
|
||||
? Symbols.pause_rounded
|
||||
: Symbols.play_arrow_rounded,
|
||||
iconSize: 32,
|
||||
onPressed: () {
|
||||
if (isPlaying) {
|
||||
|
||||
@@ -87,7 +87,9 @@ class TrackSelectionHelper {
|
||||
label,
|
||||
style: TextStyle(color: isSelected ? Colors.blue : Colors.white),
|
||||
),
|
||||
trailing: isSelected ? const AppIcon(Symbols.check_rounded, fill: 1, color: Colors.blue) : null,
|
||||
trailing: isSelected
|
||||
? const AppIcon(Symbols.check_rounded, fill: 1, color: Colors.blue)
|
||||
: null,
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -103,7 +103,9 @@ class MobileVideoControls extends StatelessWidget {
|
||||
semanticLabel: isPlaying
|
||||
? t.videoControls.pauseButton
|
||||
: t.videoControls.playButton,
|
||||
icon: isPlaying ? Symbols.pause_rounded : Symbols.play_arrow_rounded,
|
||||
icon: isPlaying
|
||||
? Symbols.pause_rounded
|
||||
: Symbols.play_arrow_rounded,
|
||||
iconSize: 72,
|
||||
onPressed: () {
|
||||
if (isPlaying) {
|
||||
|
||||
@@ -149,7 +149,12 @@ class _ChapterSheetState extends State<ChapterSheet> {
|
||||
height: 34,
|
||||
fit: BoxFit.cover,
|
||||
errorWidget: (context, url, error) =>
|
||||
const AppIcon(Symbols.image_rounded, fill: 1, color: Colors.white54, size: 34),
|
||||
const AppIcon(
|
||||
Symbols.image_rounded,
|
||||
fill: 1,
|
||||
color: Colors.white54,
|
||||
size: 34,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isCurrentChapter)
|
||||
@@ -186,7 +191,11 @@ class _ChapterSheetState extends State<ChapterSheet> {
|
||||
),
|
||||
),
|
||||
trailing: isCurrentChapter
|
||||
? const AppIcon(Symbols.play_circle_rounded, fill: 1, color: Colors.blue)
|
||||
? const AppIcon(
|
||||
Symbols.play_circle_rounded,
|
||||
fill: 1,
|
||||
color: Colors.blue,
|
||||
)
|
||||
: null,
|
||||
onTap: () {
|
||||
widget.player.seek(chapter.startTime);
|
||||
|
||||
@@ -81,7 +81,11 @@ class _VersionSheetState extends State<VersionSheet> {
|
||||
),
|
||||
),
|
||||
trailing: isSelected
|
||||
? const AppIcon(Symbols.check_rounded, fill: 1, color: Colors.blue)
|
||||
? const AppIcon(
|
||||
Symbols.check_rounded,
|
||||
fill: 1,
|
||||
color: Colors.blue,
|
||||
)
|
||||
: null,
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
|
||||
@@ -51,14 +51,22 @@ class _SettingsMenuItem extends StatelessWidget {
|
||||
|
||||
return FocusableListTile(
|
||||
focusNode: focusNode,
|
||||
leading: AppIcon(icon, fill: 1, color: isHighlighted ? Colors.amber : Colors.white70),
|
||||
leading: AppIcon(
|
||||
icon,
|
||||
fill: 1,
|
||||
color: isHighlighted ? Colors.amber : Colors.white70,
|
||||
),
|
||||
title: Text(title, style: const TextStyle(color: Colors.white)),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (allowValueOverflow) Flexible(child: valueWidget) else valueWidget,
|
||||
const SizedBox(width: 8),
|
||||
const AppIcon(Symbols.chevron_right_rounded, fill: 1, color: Colors.white70),
|
||||
const AppIcon(
|
||||
Symbols.chevron_right_rounded,
|
||||
fill: 1,
|
||||
color: Colors.white70,
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: onTap,
|
||||
@@ -229,7 +237,9 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
|
||||
builder: (context, _) {
|
||||
final isActive = sleepTimer.isActive;
|
||||
return _SettingsMenuItem(
|
||||
icon: isActive ? Symbols.bedtime_rounded : Symbols.bedtime_rounded,
|
||||
icon: isActive
|
||||
? Symbols.bedtime_rounded
|
||||
: Symbols.bedtime_rounded,
|
||||
title: 'Sleep Timer',
|
||||
valueText: _formatSleepTimer(sleepTimer),
|
||||
isHighlighted: isActive,
|
||||
@@ -259,7 +269,11 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
|
||||
// HDR Toggle (iOS, macOS, and Windows)
|
||||
if (Platform.isIOS || Platform.isMacOS || Platform.isWindows)
|
||||
ListTile(
|
||||
leading: AppIcon(Symbols.hdr_strong_rounded, fill: 1, color: _enableHDR ? Colors.amber : Colors.white70),
|
||||
leading: AppIcon(
|
||||
Symbols.hdr_strong_rounded,
|
||||
fill: 1,
|
||||
color: _enableHDR ? Colors.amber : Colors.white70,
|
||||
),
|
||||
title: const Text('HDR', style: TextStyle(color: Colors.white)),
|
||||
trailing: Switch(
|
||||
value: _enableHDR,
|
||||
@@ -319,7 +333,11 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
|
||||
),
|
||||
),
|
||||
trailing: isSelected
|
||||
? const AppIcon(Symbols.check_rounded, fill: 1, color: Colors.blue)
|
||||
? const AppIcon(
|
||||
Symbols.check_rounded,
|
||||
fill: 1,
|
||||
color: Colors.blue,
|
||||
)
|
||||
: null,
|
||||
onTap: () {
|
||||
widget.player.setRate(speed);
|
||||
@@ -405,7 +423,11 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
|
||||
),
|
||||
),
|
||||
trailing: isSelected
|
||||
? const AppIcon(Symbols.check_rounded, fill: 1, color: Colors.blue)
|
||||
? const AppIcon(
|
||||
Symbols.check_rounded,
|
||||
fill: 1,
|
||||
color: Colors.blue,
|
||||
)
|
||||
: null,
|
||||
onTap: () {
|
||||
widget.player.setAudioDevice(device);
|
||||
|
||||
@@ -30,7 +30,11 @@ class VideoSheetHeader extends StatelessWidget {
|
||||
// Back button or icon
|
||||
if (onBack != null)
|
||||
IconButton(
|
||||
icon: const AppIcon(Symbols.arrow_back_rounded, fill: 1, color: Colors.white),
|
||||
icon: const AppIcon(
|
||||
Symbols.arrow_back_rounded,
|
||||
fill: 1,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: onBack,
|
||||
)
|
||||
else if (icon != null)
|
||||
@@ -46,7 +50,11 @@ class VideoSheetHeader extends StatelessWidget {
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
icon: const AppIcon(Symbols.close_rounded, fill: 1, color: Colors.white),
|
||||
icon: const AppIcon(
|
||||
Symbols.close_rounded,
|
||||
fill: 1,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: onClose ?? () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -334,8 +334,8 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
|
||||
_autoSkipIntro = settingsService.getAutoSkipIntro();
|
||||
_autoSkipCredits = settingsService.getAutoSkipCredits();
|
||||
_autoSkipDelay = settingsService.getAutoSkipDelay();
|
||||
_videoPlayerNavigationEnabled =
|
||||
settingsService.getVideoPlayerNavigationEnabled();
|
||||
_videoPlayerNavigationEnabled = settingsService
|
||||
.getVideoPlayerNavigationEnabled();
|
||||
});
|
||||
|
||||
// Apply rotation lock setting
|
||||
@@ -801,9 +801,14 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
|
||||
color: Colors.black.withValues(alpha: 0.6),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: AppIcon(_lastDoubleTapWasForward
|
||||
child: AppIcon(
|
||||
_lastDoubleTapWasForward
|
||||
? getForwardIcon(_seekTimeSmall)
|
||||
: getReplayIcon(_seekTimeSmall), fill: 1, color: Colors.white, size: 48),
|
||||
: getReplayIcon(_seekTimeSmall),
|
||||
fill: 1,
|
||||
color: Colors.white,
|
||||
size: 48,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -947,7 +952,9 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
|
||||
|
||||
// On desktop, show controls and focus play/pause on directional input
|
||||
// Only handle navigation if video player navigation is enabled
|
||||
if (!isMobile && _isDirectionalKey(key) && _videoPlayerNavigationEnabled) {
|
||||
if (!isMobile &&
|
||||
_isDirectionalKey(key) &&
|
||||
_videoPlayerNavigationEnabled) {
|
||||
// If controls are hidden, show them and focus play/pause
|
||||
if (!_showControls) {
|
||||
_showControlsWithFocus();
|
||||
|
||||
@@ -39,7 +39,11 @@ class SleepTimerDurationList extends StatelessWidget {
|
||||
);
|
||||
|
||||
return ListTile(
|
||||
leading: const AppIcon(Symbols.timer_rounded, fill: 1, color: Colors.white70),
|
||||
leading: const AppIcon(
|
||||
Symbols.timer_rounded,
|
||||
fill: 1,
|
||||
color: Colors.white70,
|
||||
),
|
||||
title: Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
|
||||
@@ -364,7 +364,9 @@ class TrackChapterControls extends StatelessWidget {
|
||||
final currentIndex = buttonIndex;
|
||||
buttons.add(
|
||||
VideoControlButton(
|
||||
icon: isFullscreen ? Symbols.fullscreen_exit_rounded : Symbols.fullscreen_rounded,
|
||||
icon: isFullscreen
|
||||
? Symbols.fullscreen_exit_rounded
|
||||
: Symbols.fullscreen_rounded,
|
||||
semanticLabel: isFullscreen
|
||||
? t.videoControls.exitFullscreenButton
|
||||
: t.videoControls.fullscreenButton,
|
||||
|
||||
@@ -149,7 +149,11 @@ class _VolumeControlState extends State<VolumeControl> {
|
||||
button: true,
|
||||
excludeSemantics: true,
|
||||
child: IconButton(
|
||||
icon: AppIcon(isMuted ? Symbols.volume_off_rounded : Symbols.volume_up_rounded, fill: 1, color: Colors.white),
|
||||
icon: AppIcon(
|
||||
isMuted ? Symbols.volume_off_rounded : Symbols.volume_up_rounded,
|
||||
fill: 1,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: () async {
|
||||
final newVolume = isMuted ? 100.0 : 0.0;
|
||||
widget.player.setVolume(newVolume);
|
||||
|
||||
Reference in New Issue
Block a user