From 263eddbdba3662d33fc3b1af0870832d5b6de784 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Fri, 12 Dec 2025 15:57:22 +0100 Subject: [PATCH] refactor: new icons --- lib/providers/theme_provider.dart | 7 +- .../base_media_list_detail_screen.dart | 12 +- lib/screens/discover_screen.dart | 86 +++---- lib/screens/downloads/downloads_screen.dart | 8 +- lib/screens/hub_detail_screen.dart | 6 +- .../libraries/content_state_builder.dart | 3 +- lib/screens/libraries/empty_state_widget.dart | 3 +- lib/screens/libraries/error_state_widget.dart | 3 +- .../libraries/filters_bottom_sheet.dart | 8 +- lib/screens/libraries/folder_tree_item.dart | 33 +-- lib/screens/libraries/folder_tree_view.dart | 5 +- lib/screens/libraries/libraries_screen.dart | 77 +++---- lib/screens/libraries/sort_bottom_sheet.dart | 6 +- .../libraries/state_message_widget.dart | 12 +- .../libraries/tabs/library_browse_tab.dart | 13 +- .../tabs/library_collections_tab.dart | 3 +- .../libraries/tabs/library_playlists_tab.dart | 3 +- .../tabs/library_recommended_tab.dart | 19 +- lib/screens/main_screen.dart | 36 ++- lib/screens/media_detail_screen.dart | 211 ++++++++++-------- .../playlist/playlist_detail_screen.dart | 6 +- lib/screens/playlist/playlist_item_card.dart | 8 +- lib/screens/profile/pin_entry_dialog.dart | 13 +- lib/screens/profile/profile_list_tile.dart | 4 +- .../profile/profile_switch_screen.dart | 8 +- lib/screens/profile/user_avatar_widget.dart | 16 +- lib/screens/search_screen.dart | 14 +- lib/screens/season_detail_screen.dart | 50 +---- lib/screens/settings/about_screen.dart | 6 +- .../settings/hotkey_recorder_widget.dart | 4 +- lib/screens/settings/licenses_screen.dart | 4 +- lib/screens/settings/logs_screen.dart | 27 ++- lib/screens/settings/settings_screen.dart | 164 ++++++++------ .../settings/subtitle_styling_screen.dart | 4 +- lib/screens/video_player_screen.dart | 14 +- lib/utils/video_control_icons.dart | 17 +- lib/widgets/app_bar_back_button.dart | 8 +- lib/widgets/app_icon.dart | 74 ++++++ lib/widgets/bottom_sheet_header.dart | 4 +- lib/widgets/desktop_app_bar.dart | 4 +- lib/widgets/download_tree_item.dart | 23 +- lib/widgets/download_tree_view.dart | 36 +-- lib/widgets/file_info_bottom_sheet.dart | 10 +- lib/widgets/focusable_filter_chip.dart | 3 +- .../horizontal_scroll_with_arrows.dart | 8 +- lib/widgets/hub_section.dart | 6 +- lib/widgets/media_card.dart | 10 +- lib/widgets/media_context_menu.dart | 52 ++--- lib/widgets/plex_optimized_image.dart | 22 +- lib/widgets/side_navigation_rail.dart | 60 ++--- .../desktop_video_controls.dart | 14 +- .../helpers/track_selection_helper.dart | 4 +- .../video_controls/mobile_video_controls.dart | 6 +- .../sheets/audio_track_sheet.dart | 3 +- .../video_controls/sheets/chapter_sheet.dart | 12 +- .../sheets/subtitle_track_sheet.dart | 3 +- .../video_controls/sheets/version_sheet.dart | 6 +- .../sheets/video_settings_sheet.dart | 37 ++- .../sheets/video_sheet_header.dart | 8 +- .../video_controls/video_control_button.dart | 3 +- .../video_controls/video_controls.dart | 16 +- .../widgets/sleep_timer_active_status.dart | 6 +- .../widgets/sleep_timer_duration_list.dart | 4 +- .../widgets/sync_offset_control.dart | 4 +- .../widgets/track_chapter_controls.dart | 25 ++- .../widgets/volume_control.dart | 7 +- pubspec.lock | 16 ++ pubspec.yaml | 1 + 68 files changed, 739 insertions(+), 669 deletions(-) create mode 100644 lib/widgets/app_icon.dart diff --git a/lib/providers/theme_provider.dart b/lib/providers/theme_provider.dart index b166daf4..34504ee5 100644 --- a/lib/providers/theme_provider.dart +++ b/lib/providers/theme_provider.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../services/settings_service.dart' as settings; import '../theme/mono_theme.dart'; @@ -78,11 +79,11 @@ class ThemeProvider extends ChangeNotifier { IconData get themeModeIcon { switch (_themeMode) { case settings.ThemeMode.light: - return Icons.light_mode; + return Symbols.light_mode_rounded; case settings.ThemeMode.dark: - return Icons.dark_mode; + return Symbols.dark_mode_rounded; case settings.ThemeMode.system: - return Icons.brightness_auto; + return Symbols.brightness_auto_rounded; } } diff --git a/lib/screens/base_media_list_detail_screen.dart b/lib/screens/base_media_list_detail_screen.dart index a0e7aaaf..29a2e868 100644 --- a/lib/screens/base_media_list_detail_screen.dart +++ b/lib/screens/base_media_list_detail_screen.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import '../../services/plex_client.dart'; import '../models/plex_metadata.dart'; @@ -131,7 +133,7 @@ abstract class BaseMediaListDetailScreen child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.error_outline, 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), @@ -163,7 +165,7 @@ abstract class BaseMediaListDetailScreen ? Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(icon, size: 64, color: Colors.grey), + AppIcon(icon, fill: 1, size: 64, color: Colors.grey), const SizedBox(height: 16), Text( emptyMessage, @@ -195,21 +197,21 @@ abstract class BaseMediaListDetailScreen // Play button if (items.isNotEmpty) IconButton( - icon: const Icon(Icons.play_arrow), + icon: const AppIcon(Symbols.play_arrow_rounded, fill: 1), tooltip: t.discover.play, onPressed: playItems, ), // Shuffle button if (items.isNotEmpty) IconButton( - icon: const Icon(Icons.shuffle), + icon: const AppIcon(Symbols.shuffle_rounded, fill: 1), tooltip: t.common.shuffle, onPressed: shufflePlayItems, ), // Delete button if (showDelete && onDelete != null) IconButton( - icon: const Icon(Icons.delete), + icon: const AppIcon(Symbols.delete_rounded, fill: 1), tooltip: deleteTooltip ?? t.common.delete, onPressed: onDelete, color: deleteColor ?? Colors.red, diff --git a/lib/screens/discover_screen.dart b/lib/screens/discover_screen.dart index db7ca444..2652c382 100644 --- a/lib/screens/discover_screen.dart +++ b/lib/screens/discover_screen.dart @@ -1,5 +1,7 @@ import 'dart:async'; 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:provider/provider.dart'; import 'package:cached_network_image/cached_network_image.dart'; @@ -564,98 +566,98 @@ class _DiscoverScreenState extends State // Trending/Popular content if (lowerTitle.contains('trending')) { - return Icons.trending_up; + return Symbols.trending_up_rounded; } if (lowerTitle.contains('popular') || lowerTitle.contains('imdb')) { - return Icons.whatshot; + return Symbols.whatshot_rounded; } // Seasonal/Time-based if (lowerTitle.contains('seasonal')) { - return Icons.calendar_month; + return Symbols.calendar_month_rounded; } if (lowerTitle.contains('newly') || lowerTitle.contains('new release')) { - return Icons.new_releases; + return Symbols.new_releases_rounded; } if (lowerTitle.contains('recently released') || lowerTitle.contains('recent')) { - return Icons.schedule; + return Symbols.schedule_rounded; } // Top/Rated content if (lowerTitle.contains('top rated') || lowerTitle.contains('highest rated')) { - return Icons.star; + return Symbols.star_rounded; } if (lowerTitle.contains('top ')) { - return Icons.military_tech; + return Symbols.military_tech_rounded; } // Genre-specific if (lowerTitle.contains('thriller')) { - return Icons.warning_amber_rounded; + return Symbols.warning_amber_rounded; } if (lowerTitle.contains('comedy') || lowerTitle.contains('comedier')) { - return Icons.mood; + return Symbols.mood_rounded; } if (lowerTitle.contains('action')) { - return Icons.flash_on; + return Symbols.flash_on_rounded; } if (lowerTitle.contains('drama')) { - return Icons.theater_comedy; + return Symbols.theater_comedy_rounded; } if (lowerTitle.contains('fantasy')) { - return Icons.auto_fix_high; + return Symbols.auto_fix_high_rounded; } if (lowerTitle.contains('science') || lowerTitle.contains('sci-fi')) { - return Icons.rocket_launch; + return Symbols.rocket_launch_rounded; } if (lowerTitle.contains('horror') || lowerTitle.contains('skräck')) { - return Icons.nights_stay; + return Symbols.nights_stay_rounded; } if (lowerTitle.contains('romance') || lowerTitle.contains('romantic')) { - return Icons.favorite_border; + return Symbols.favorite_border_rounded; } if (lowerTitle.contains('adventure') || lowerTitle.contains('äventyr')) { - return Icons.explore; + return Symbols.explore_rounded; } // Watchlist/Playlists if (lowerTitle.contains('playlist') || lowerTitle.contains('watchlist')) { - return Icons.playlist_play; + return Symbols.playlist_play_rounded; } if (lowerTitle.contains('unwatched') || lowerTitle.contains('unplayed')) { - return Icons.visibility_off; + return Symbols.visibility_off_rounded; } if (lowerTitle.contains('watched') || lowerTitle.contains('played')) { - return Icons.visibility; + return Symbols.visibility_rounded; } // Network/Studio if (lowerTitle.contains('network') || lowerTitle.contains('more from')) { - return Icons.tv; + return Symbols.tv_rounded; } // Actor/Director if (lowerTitle.contains('actor') || lowerTitle.contains('director')) { - return Icons.person; + return Symbols.person_rounded; } // Year-based (80s, 90s, etc.) if (lowerTitle.contains('80') || lowerTitle.contains('90') || lowerTitle.contains('00')) { - return Icons.history; + return Symbols.history_rounded; } // Rediscover/Start Watching if (lowerTitle.contains('rediscover') || lowerTitle.contains('start watching')) { - return Icons.play_arrow; + return Symbols.play_arrow_rounded; } // Default icon for other hubs - return Icons.auto_awesome; + return Symbols.auto_awesome_rounded; } @override @@ -762,7 +764,7 @@ class _DiscoverScreenState extends State value: 'switch_profile', child: Row( children: [ - Icon(Icons.people), + AppIcon(Symbols.people_rounded, fill: 1), SizedBox(width: 8), Text(t.discover.switchProfile), ], @@ -772,7 +774,7 @@ class _DiscoverScreenState extends State value: 'logout', child: Row( children: [ - Icon(Icons.logout), + AppIcon(Symbols.logout_rounded, fill: 1), SizedBox(width: 8), Text(t.discover.logout), ], @@ -817,7 +819,7 @@ class _DiscoverScreenState extends State borderRadius: BorderRadius.circular(20), ), child: IconButton( - icon: const Icon(Icons.refresh), + icon: const AppIcon(Symbols.refresh_rounded, fill: 1), onPressed: _loadContent, ), ), @@ -843,7 +845,7 @@ class _DiscoverScreenState extends State size: 32, showIndicators: false, ) - : const Icon(Icons.account_circle, size: 32), + : const AppIcon(Symbols.account_circle_rounded, fill: 1, size: 32), onSelected: (value) { if (value == 'switch_profile') { _handleSwitchProfile(context); @@ -858,7 +860,7 @@ class _DiscoverScreenState extends State value: 'switch_profile', child: Row( children: [ - Icon(Icons.people), + AppIcon(Symbols.people_rounded, fill: 1), SizedBox(width: 8), Text(t.discover.switchProfile), ], @@ -868,7 +870,7 @@ class _DiscoverScreenState extends State value: 'logout', child: Row( children: [ - Icon(Icons.logout), + AppIcon(Symbols.logout_rounded, fill: 1), SizedBox(width: 8), Text(t.discover.logout), ], @@ -892,11 +894,7 @@ class _DiscoverScreenState extends State child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon( - Icons.error_outline, - 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), @@ -933,7 +931,7 @@ class _DiscoverScreenState extends State more: false, items: _onDeck, ), - icon: Icons.play_circle_outline, + icon: Symbols.play_circle_rounded, onRefresh: updateItem, onRemoveFromContinueWatching: _refreshContinueWatching, isInContinueWatching: true, @@ -1010,11 +1008,7 @@ class _DiscoverScreenState extends State child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( - Icons.movie_outlined, - 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), @@ -1078,13 +1072,7 @@ class _DiscoverScreenState extends State _pauseAutoScroll(); } }, - child: Icon( - _isAutoScrollPaused ? Icons.play_arrow : Icons.pause, - 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), @@ -1537,7 +1525,7 @@ class _DiscoverScreenState extends State child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.play_arrow, 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 diff --git a/lib/screens/downloads/downloads_screen.dart b/lib/screens/downloads/downloads_screen.dart index ce52c032..20bdc1a8 100644 --- a/lib/screens/downloads/downloads_screen.dart +++ b/lib/screens/downloads/downloads_screen.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import '../../models/plex_metadata.dart'; import '../../providers/download_provider.dart'; @@ -337,11 +339,7 @@ class _DownloadsGridContent extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( - Icons.download_outlined, - 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, diff --git a/lib/screens/hub_detail_screen.dart b/lib/screens/hub_detail_screen.dart index ff7cbaf7..ccae0d52 100644 --- a/lib/screens/hub_detail_screen.dart +++ b/lib/screens/hub_detail_screen.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../services/plex_client.dart'; import '../models/plex_hub.dart'; import '../models/plex_metadata.dart'; @@ -243,7 +245,7 @@ class _HubDetailScreenState extends State with Refreshable { title: Text(widget.hub.title), actions: [ IconButton( - icon: Icon(Icons.swap_vert, semanticLabel: t.libraries.sort), + icon: AppIcon(Symbols.swap_vert_rounded, fill: 1, semanticLabel: t.libraries.sort), onPressed: _showSortBottomSheet, ), ], @@ -254,7 +256,7 @@ class _HubDetailScreenState extends State with Refreshable { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.error_outline, 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), diff --git a/lib/screens/libraries/content_state_builder.dart b/lib/screens/libraries/content_state_builder.dart index dc61d697..48688793 100644 --- a/lib/screens/libraries/content_state_builder.dart +++ b/lib/screens/libraries/content_state_builder.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../i18n/strings.g.dart'; import 'empty_state_widget.dart'; import 'error_state_widget.dart'; @@ -49,7 +50,7 @@ class ContentStateBuilder extends StatelessWidget { if (errorMessage != null && items.isEmpty) { return ErrorStateWidget( message: errorMessage!, - icon: Icons.error_outline, + icon: Symbols.error_outline_rounded, onRetry: onRetry, retryLabel: t.common.retry, ); diff --git a/lib/screens/libraries/empty_state_widget.dart b/lib/screens/libraries/empty_state_widget.dart index 5ed02294..8e33bbd9 100644 --- a/lib/screens/libraries/empty_state_widget.dart +++ b/lib/screens/libraries/empty_state_widget.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'state_message_widget.dart'; /// A reusable widget for displaying empty states throughout the app @@ -30,7 +31,7 @@ class EmptyStateWidget extends StatelessWidget { icon: icon, onAction: onAction, actionLabel: actionLabel, - actionIcon: Icons.add, + actionIcon: Symbols.add_rounded, ); } } diff --git a/lib/screens/libraries/error_state_widget.dart b/lib/screens/libraries/error_state_widget.dart index 2ec0e428..8d17d200 100644 --- a/lib/screens/libraries/error_state_widget.dart +++ b/lib/screens/libraries/error_state_widget.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'state_message_widget.dart'; /// A reusable widget for displaying error states throughout the app @@ -32,7 +33,7 @@ class ErrorStateWidget extends StatelessWidget { textColor: Theme.of(context).colorScheme.error, onAction: onRetry, actionLabel: retryLabel ?? 'Retry', - actionIcon: Icons.refresh, + actionIcon: Symbols.refresh_rounded, ); } } diff --git a/lib/screens/libraries/filters_bottom_sheet.dart b/lib/screens/libraries/filters_bottom_sheet.dart index 3f1bab48..633bb840 100644 --- a/lib/screens/libraries/filters_bottom_sheet.dart +++ b/lib/screens/libraries/filters_bottom_sheet.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../models/plex_filter.dart'; import '../../widgets/app_bar_back_button.dart'; import '../../widgets/bottom_sheet_header.dart'; @@ -201,7 +203,7 @@ class _FiltersBottomSheetState extends State { // Header BottomSheetHeader( title: t.libraries.filters, - leading: const Icon(Icons.filter_alt), + leading: const AppIcon(Symbols.filter_alt_rounded, fill: 1), action: _tempSelectedFilters.isNotEmpty ? TextButton.icon( onPressed: () { @@ -210,7 +212,7 @@ class _FiltersBottomSheetState extends State { }); _applyFilters(); }, - icon: const Icon(Icons.clear_all), + icon: const AppIcon(Symbols.clear_all_rounded, fill: 1), label: Text(t.libraries.clearAll), ) : null, @@ -275,7 +277,7 @@ class _FiltersBottomSheetState extends State { ), ), if (displayValue != null) const SizedBox(width: 8), - const Icon(Icons.chevron_right), + const AppIcon(Symbols.chevron_right_rounded, fill: 1), ], ), onTap: () => _loadFilterValues(filter), diff --git a/lib/screens/libraries/folder_tree_item.dart b/lib/screens/libraries/folder_tree_item.dart index 4409b38a..fdf5864b 100644 --- a/lib/screens/libraries/folder_tree_item.dart +++ b/lib/screens/libraries/folder_tree_item.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../models/plex_metadata.dart'; /// Individual item in the folder tree @@ -25,24 +27,24 @@ class FolderTreeItem extends StatelessWidget { IconData _getIcon() { if (isFolder) { - return Icons.folder; + return Symbols.folder_rounded; } // File icons based on type final type = item.type.toLowerCase(); switch (type) { case 'movie': - return Icons.movie; + return Symbols.movie_rounded; case 'show': - return Icons.tv; + return Symbols.tv_rounded; case 'season': - return Icons.video_library; + return Symbols.video_library_rounded; case 'episode': - return Icons.play_circle_outline; + return Symbols.play_circle_rounded; case 'collection': - return Icons.collections; + return Symbols.collections_rounded; default: - return Icons.insert_drive_file; + return Symbols.insert_drive_file_rounded; } } @@ -79,12 +81,7 @@ class FolderTreeItem extends StatelessWidget { height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) - : Icon( - isExpanded - ? Icons.keyboard_arrow_down - : Icons.keyboard_arrow_right, - size: 20, - ), + : AppIcon(isExpanded ? Symbols.keyboard_arrow_down_rounded : Symbols.keyboard_arrow_right_rounded, fill: 1, size: 20), ) else const SizedBox(width: 24), @@ -92,15 +89,7 @@ class FolderTreeItem extends StatelessWidget { const SizedBox(width: 8), // File/folder icon - Icon( - _getIcon(), - 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), diff --git a/lib/screens/libraries/folder_tree_view.dart b/lib/screens/libraries/folder_tree_view.dart index 59088187..4feb39a2 100644 --- a/lib/screens/libraries/folder_tree_view.dart +++ b/lib/screens/libraries/folder_tree_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../models/plex_metadata.dart'; import '../../utils/app_logger.dart'; import '../../utils/media_navigation_helper.dart'; @@ -209,7 +210,7 @@ class _FolderTreeViewState extends State { if (_errorMessage != null) { return ErrorStateWidget( message: _errorMessage!, - icon: Icons.error_outline, + icon: Symbols.error_outline_rounded, onRetry: _loadRootFolders, retryLabel: t.common.retry, ); @@ -218,7 +219,7 @@ class _FolderTreeViewState extends State { if (_rootFolders.isEmpty) { return EmptyStateWidget( message: t.libraries.noFoldersFound, - icon: Icons.folder_open, + icon: Symbols.folder_open_rounded, ); } diff --git a/lib/screens/libraries/libraries_screen.dart b/lib/screens/libraries/libraries_screen.dart index 122768cd..ebb5dbf4 100644 --- a/lib/screens/libraries/libraries_screen.dart +++ b/lib/screens/libraries/libraries_screen.dart @@ -1,4 +1,6 @@ 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:provider/provider.dart'; import 'package:dio/dio.dart'; @@ -763,7 +765,7 @@ class _LibrariesScreenState extends State return [ ContextMenuItem( value: 'scan', - icon: Icons.refresh, + icon: Symbols.refresh_rounded, label: t.libraries.scanLibraryFiles, requiresConfirmation: true, confirmationTitle: t.libraries.scanLibrary, @@ -773,7 +775,7 @@ class _LibrariesScreenState extends State ), ContextMenuItem( value: 'analyze', - icon: Icons.analytics_outlined, + icon: Symbols.analytics_rounded, label: t.libraries.analyze, requiresConfirmation: true, confirmationTitle: t.libraries.analyzeLibrary, @@ -783,7 +785,7 @@ class _LibrariesScreenState extends State ), ContextMenuItem( value: 'refresh', - icon: Icons.sync, + icon: Symbols.sync_rounded, label: t.libraries.refreshMetadata, requiresConfirmation: true, confirmationTitle: t.libraries.refreshMetadata, @@ -794,7 +796,7 @@ class _LibrariesScreenState extends State ), ContextMenuItem( value: 'empty_trash', - icon: Icons.delete_outline, + icon: Symbols.delete_outline_rounded, label: t.libraries.emptyTrash, requiresConfirmation: true, confirmationTitle: t.libraries.emptyTrash, @@ -961,11 +963,7 @@ class _LibrariesScreenState extends State value: library.globalKey, child: Row( children: [ - Icon( - _getLibraryIcon(library.type), - 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( @@ -1092,7 +1090,7 @@ class _LibrariesScreenState extends State child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(_getLibraryIcon(selectedLibrary.type), size: 20), + AppIcon(_getLibraryIcon(selectedLibrary.type), fill: 1, size: 20), const SizedBox(width: 8), if (_hasMultipleServers && selectedLibrary.serverName != null) Column( @@ -1119,7 +1117,7 @@ class _LibrariesScreenState extends State style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(width: 4), - const Icon(Icons.arrow_drop_down, size: 24), + const AppIcon(Symbols.arrow_drop_down_rounded, fill: 1, size: 24), ], ), ), @@ -1151,12 +1149,12 @@ class _LibrariesScreenState extends State actions: [ if (_allLibraries.isNotEmpty) IconButton( - icon: const Icon(Icons.edit), + icon: const AppIcon(Symbols.edit_rounded, fill: 1), tooltip: t.libraries.manageLibraries, onPressed: _showLibraryManagementSheet, ), IconButton( - icon: const Icon(Icons.refresh), + icon: const AppIcon(Symbols.refresh_rounded, fill: 1), tooltip: t.common.refresh, onPressed: _refreshCurrentTab, ), @@ -1172,11 +1170,7 @@ class _LibrariesScreenState extends State child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon( - Icons.error_outline, - 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), @@ -1194,11 +1188,7 @@ class _LibrariesScreenState extends State child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon( - Icons.video_library_outlined, - 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), ], @@ -1291,15 +1281,15 @@ class _LibrariesScreenState extends State IconData _getLibraryIcon(String type) { switch (type.toLowerCase()) { case 'movie': - return Icons.movie; + return Symbols.movie_rounded; case 'show': - return Icons.tv; + return Symbols.tv_rounded; case 'artist': - return Icons.music_note; + return Symbols.music_note_rounded; case 'photo': - return Icons.photo; + return Symbols.photo_rounded; default: - return Icons.folder; + return Symbols.folder_rounded; } } } @@ -1484,7 +1474,7 @@ class _LibraryManagementSheetState extends State<_LibraryManagementSheet> { ), ...menuItems.indexed.map( (entry) => ListTile( - leading: Icon(entry.$2.icon), + leading: AppIcon(entry.$2.icon, fill: 1), title: Text(entry.$2.label), onTap: () => Navigator.pop(context, entry.$2.value), ), @@ -1538,15 +1528,15 @@ class _LibraryManagementSheetState extends State<_LibraryManagementSheet> { IconData _getLibraryIcon(String type) { switch (type.toLowerCase()) { case 'movie': - return Icons.movie; + return Symbols.movie_rounded; case 'show': - return Icons.tv; + return Symbols.tv_rounded; case 'artist': - return Icons.music_note; + return Symbols.music_note_rounded; case 'photo': - return Icons.photo; + return Symbols.photo_rounded; default: - return Icons.folder; + return Symbols.folder_rounded; } } @@ -1586,7 +1576,7 @@ class _LibraryManagementSheetState extends State<_LibraryManagementSheet> { ), child: Row( children: [ - const Icon(Icons.edit), + const AppIcon(Symbols.edit_rounded, fill: 1), const SizedBox(width: 12), Expanded( child: Text( @@ -1598,7 +1588,7 @@ class _LibraryManagementSheetState extends State<_LibraryManagementSheet> { ), ), IconButton( - icon: const Icon(Icons.close), + icon: const AppIcon(Symbols.close_rounded, fill: 1), onPressed: () => Navigator.pop(context), ), ], @@ -1694,15 +1684,10 @@ class _LibraryManagementSheetState extends State<_LibraryManagementSheet> { children: [ ReorderableDragStartListener( index: index, - child: Icon( - isMoving ? Icons.swap_vert : Icons.drag_indicator, - 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), - Icon(_getLibraryIcon(library.type)), + AppIcon(_getLibraryIcon(library.type), fill: 1), ], ), title: Text(library.title), @@ -1728,9 +1713,7 @@ class _LibraryManagementSheetState extends State<_LibraryManagementSheet> { ) : null, child: IconButton( - icon: Icon( - isHidden ? Icons.visibility_off : Icons.visibility, - ), + icon: AppIcon(isHidden ? Symbols.visibility_off_rounded : Symbols.visibility_rounded, fill: 1), tooltip: isHidden ? t.libraries.showLibrary : t.libraries.hideLibrary, @@ -1745,7 +1728,7 @@ class _LibraryManagementSheetState extends State<_LibraryManagementSheet> { ) : null, child: IconButton( - icon: const Icon(Icons.more_vert), + icon: const AppIcon(Symbols.more_vert_rounded, fill: 1), tooltip: t.libraries.libraryOptions, onPressed: () => _showLibraryMenuBottomSheet(context, library), diff --git a/lib/screens/libraries/sort_bottom_sheet.dart b/lib/screens/libraries/sort_bottom_sheet.dart index 6ca87fef..b122e72c 100644 --- a/lib/screens/libraries/sort_bottom_sheet.dart +++ b/lib/screens/libraries/sort_bottom_sheet.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../models/plex_sort.dart'; import '../../widgets/bottom_sheet_header.dart'; import '../../widgets/focusable_bottom_sheet.dart'; @@ -108,11 +110,11 @@ class _SortBottomSheetState extends State { segments: const [ ButtonSegment( value: false, - icon: Icon(Icons.arrow_upward, size: 16), + icon: AppIcon(Symbols.arrow_upward_rounded, fill: 1, size: 16), ), ButtonSegment( value: true, - icon: Icon(Icons.arrow_downward, size: 16), + icon: AppIcon(Symbols.arrow_downward_rounded, fill: 1, size: 16), ), ], selected: {_currentDescending}, diff --git a/lib/screens/libraries/state_message_widget.dart b/lib/screens/libraries/state_message_widget.dart index add274e9..52ccbd10 100644 --- a/lib/screens/libraries/state_message_widget.dart +++ b/lib/screens/libraries/state_message_widget.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; /// Base widget for displaying state messages (empty, error, etc.) /// Provides a consistent UI pattern for showing icons, messages, and actions @@ -44,15 +46,11 @@ class StateMessageWidget extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ if (icon != null) ...[ - Icon( - icon, - 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( @@ -70,7 +68,7 @@ class StateMessageWidget extends StatelessWidget { const SizedBox(height: 24), FilledButton.icon( onPressed: onAction, - icon: Icon(actionIcon ?? Icons.refresh), + icon: AppIcon(actionIcon ?? Symbols.refresh_rounded, fill: 1), label: Text(actionLabel!), ), ], diff --git a/lib/screens/libraries/tabs/library_browse_tab.dart b/lib/screens/libraries/tabs/library_browse_tab.dart index 884c00a1..69cb0abe 100644 --- a/lib/screens/libraries/tabs/library_browse_tab.dart +++ b/lib/screens/libraries/tabs/library_browse_tab.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import 'package:dio/dio.dart'; import '../../../../services/plex_client.dart'; @@ -111,7 +112,7 @@ class _LibraryBrowseTabState // Required abstract implementations from base class @override - IconData get emptyIcon => Icons.folder_open; + IconData get emptyIcon => Symbols.folder_open_rounded; @override String get emptyMessage => t.libraries.thisLibraryIsEmpty; @@ -503,7 +504,7 @@ class _LibraryBrowseTabState // Grouping chip FocusableFilterChip( focusNode: _groupingChipFocusNode, - icon: Icons.category, + icon: Symbols.category_rounded, label: _getGroupingLabel(_selectedGrouping), onPressed: _showGroupingBottomSheet, onNavigateDown: _navigateToGrid, @@ -515,7 +516,7 @@ class _LibraryBrowseTabState if (_filters.isNotEmpty && _selectedGrouping != 'folders') FocusableFilterChip( focusNode: _filtersChipFocusNode, - icon: Icons.filter_alt, + icon: Symbols.filter_alt_rounded, label: _selectedFilters.isEmpty ? t.libraries.filters : t.libraries.filtersWithCount( @@ -532,7 +533,7 @@ class _LibraryBrowseTabState if (_sortOptions.isNotEmpty && _selectedGrouping != 'folders') FocusableFilterChip( focusNode: _sortChipFocusNode, - icon: Icons.sort, + icon: Symbols.sort_rounded, label: _selectedSort?.title ?? t.libraries.sort, onPressed: _showSortBottomSheet, onNavigateDown: _navigateToGrid, @@ -567,7 +568,7 @@ class _LibraryBrowseTabState if (errorMessage != null && items.isEmpty) { return ErrorStateWidget( message: errorMessage!, - icon: Icons.error_outline, + icon: Symbols.error_outline_rounded, onRetry: _loadContent, retryLabel: t.common.retry, ); @@ -576,7 +577,7 @@ class _LibraryBrowseTabState if (items.isEmpty) { return EmptyStateWidget( message: t.libraries.thisLibraryIsEmpty, - icon: Icons.folder_open, + icon: Symbols.folder_open_rounded, ); } diff --git a/lib/screens/libraries/tabs/library_collections_tab.dart b/lib/screens/libraries/tabs/library_collections_tab.dart index 163f6481..1ce088a1 100644 --- a/lib/screens/libraries/tabs/library_collections_tab.dart +++ b/lib/screens/libraries/tabs/library_collections_tab.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../../models/plex_metadata.dart'; import '../../../utils/library_refresh_notifier.dart'; import '../../../widgets/focusable_media_card.dart'; @@ -30,7 +31,7 @@ class _LibraryCollectionsTabState String get focusNodeDebugLabel => 'collections_first_item'; @override - IconData get emptyIcon => Icons.collections; + IconData get emptyIcon => Symbols.collections_rounded; @override String get emptyMessage => t.libraries.noCollections; diff --git a/lib/screens/libraries/tabs/library_playlists_tab.dart b/lib/screens/libraries/tabs/library_playlists_tab.dart index 6a5dd6fd..61466926 100644 --- a/lib/screens/libraries/tabs/library_playlists_tab.dart +++ b/lib/screens/libraries/tabs/library_playlists_tab.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../../models/plex_playlist.dart'; import '../../../utils/library_refresh_notifier.dart'; import '../../../widgets/focusable_media_card.dart'; @@ -30,7 +31,7 @@ class _LibraryPlaylistsTabState String get focusNodeDebugLabel => 'playlists_first_item'; @override - IconData get emptyIcon => Icons.playlist_play; + IconData get emptyIcon => Symbols.playlist_play_rounded; @override String get emptyMessage => t.playlists.noPlaylists; diff --git a/lib/screens/libraries/tabs/library_recommended_tab.dart b/lib/screens/libraries/tabs/library_recommended_tab.dart index 74ec0f38..76db4bef 100644 --- a/lib/screens/libraries/tabs/library_recommended_tab.dart +++ b/lib/screens/libraries/tabs/library_recommended_tab.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../../../services/plex_client.dart'; import '../../../i18n/strings.g.dart'; @@ -47,7 +48,7 @@ class _LibraryRecommendedTabState } @override - IconData get emptyIcon => Icons.recommend; + IconData get emptyIcon => Symbols.recommend_rounded; @override String get emptyMessage => t.libraries.noRecommendations; @@ -179,20 +180,20 @@ class _LibraryRecommendedTabState IconData _getHubIcon(PlexHub hub) { final title = hub.title.toLowerCase(); if (title.contains('continue watching') || title.contains('on deck')) { - return Icons.play_circle; + return Symbols.play_circle_rounded; } else if (title.contains('recently') || title.contains('new')) { - return Icons.fiber_new; + return Symbols.fiber_new_rounded; } else if (title.contains('popular') || title.contains('trending')) { - return Icons.trending_up; + return Symbols.trending_up_rounded; } else if (title.contains('top') || title.contains('rated')) { - return Icons.star; + return Symbols.star_rounded; } else if (title.contains('recommended')) { - return Icons.thumb_up; + return Symbols.thumb_up_rounded; } else if (title.contains('unwatched')) { - return Icons.visibility_off; + return Symbols.visibility_off_rounded; } else if (title.contains('genre')) { - return Icons.category; + return Symbols.category_rounded; } - return Icons.movie; + return Symbols.movie_rounded; } } diff --git a/lib/screens/main_screen.dart b/lib/screens/main_screen.dart index ca364e2b..63f4340b 100644 --- a/lib/screens/main_screen.dart +++ b/lib/screens/main_screen.dart @@ -1,4 +1,6 @@ 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:provider/provider.dart'; import '../../services/plex_client.dart'; @@ -447,40 +449,40 @@ class _MainScreenState extends State with RouteAware { final destinations = _isOffline ? [ NavigationDestination( - icon: const Icon(Icons.download_outlined), - selectedIcon: const Icon(Icons.download), + icon: const AppIcon(Symbols.download_rounded, fill: 1), + selectedIcon: const AppIcon(Symbols.download_rounded, fill: 1), label: t.navigation.downloads, ), NavigationDestination( - icon: const Icon(Icons.settings_outlined), - selectedIcon: const Icon(Icons.settings), + icon: const AppIcon(Symbols.settings_rounded, fill: 1), + selectedIcon: const AppIcon(Symbols.settings_rounded, fill: 1), label: t.navigation.settings, ), ] : [ NavigationDestination( - icon: const Icon(Icons.home_outlined), - selectedIcon: const Icon(Icons.home), + icon: const AppIcon(Symbols.home_rounded, fill: 1), + selectedIcon: const AppIcon(Symbols.home_rounded, fill: 1), label: t.navigation.home, ), NavigationDestination( - icon: const Icon(Icons.video_library_outlined), - selectedIcon: const Icon(Icons.video_library), + icon: const AppIcon(Symbols.video_library_rounded, fill: 1), + selectedIcon: const AppIcon(Symbols.video_library_rounded, fill: 1), label: t.navigation.libraries, ), NavigationDestination( - icon: const Icon(Icons.search), - selectedIcon: const Icon(Icons.search), + icon: const AppIcon(Symbols.search_rounded, fill: 1), + selectedIcon: const AppIcon(Symbols.search_rounded, fill: 1), label: t.navigation.search, ), NavigationDestination( - icon: const Icon(Icons.download_outlined), - selectedIcon: const Icon(Icons.download), + icon: const AppIcon(Symbols.download_rounded, fill: 1), + selectedIcon: const AppIcon(Symbols.download_rounded, fill: 1), label: t.navigation.downloads, ), NavigationDestination( - icon: const Icon(Icons.settings_outlined), - selectedIcon: const Icon(Icons.settings), + icon: const AppIcon(Symbols.settings_rounded, fill: 1), + selectedIcon: const AppIcon(Symbols.settings_rounded, fill: 1), label: t.navigation.settings, ), ]; @@ -508,11 +510,7 @@ class _OfflinePlaceholder extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( - Icons.cloud_off, - 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, diff --git a/lib/screens/media_detail_screen.dart b/lib/screens/media_detail_screen.dart index 1547a945..b7ca0a5b 100644 --- a/lib/screens/media_detail_screen.dart +++ b/lib/screens/media_detail_screen.dart @@ -2,6 +2,8 @@ import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import '../focus/key_event_utils.dart'; @@ -889,8 +891,9 @@ class _MediaDetailScreenState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon( - Icons.star, + const AppIcon( + Symbols.star_rounded, + fill: 1, color: Colors.white, size: 16, ), @@ -921,8 +924,9 @@ class _MediaDetailScreenState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon( - Icons.people, + const AppIcon( + Symbols.people_rounded, + fill: 1, color: Colors.white, size: 16, ), @@ -1008,8 +1012,9 @@ class _MediaDetailScreenState extends State { } } }, - icon: Icon( + icon: AppIcon( _getPlayButtonIcon(metadata), + fill: 1, size: 20, ), label: Text( @@ -1035,7 +1040,10 @@ class _MediaDetailScreenState extends State { metadata, ); }, - icon: const Icon(Icons.shuffle), + icon: const AppIcon( + Symbols.shuffle_rounded, + fill: 1, + ), tooltip: t.tooltips.shufflePlay, iconSize: 20, style: IconButton.styleFrom( @@ -1096,7 +1104,10 @@ class _MediaDetailScreenState extends State { return IconButton.filledTonal( onPressed: null, tooltip: tooltip, - icon: const Icon(Icons.schedule), + icon: const AppIcon( + Symbols.schedule_rounded, + fill: 1, + ), iconSize: 20, style: IconButton.styleFrom( minimumSize: const Size(48, 48), @@ -1152,7 +1163,10 @@ class _MediaDetailScreenState extends State { ); } }, - icon: const Icon(Icons.pause_circle_outline), + icon: const AppIcon( + Symbols.pause_circle_outline_rounded, + fill: 1, + ), tooltip: 'Resume download', iconSize: 20, style: IconButton.styleFrom( @@ -1209,7 +1223,10 @@ class _MediaDetailScreenState extends State { } } }, - icon: const Icon(Icons.error_outline), + icon: const AppIcon( + Symbols.error_outline_rounded, + fill: 1, + ), tooltip: 'Retry download', iconSize: 20, style: IconButton.styleFrom( @@ -1307,7 +1324,10 @@ class _MediaDetailScreenState extends State { } } }, - icon: const Icon(Icons.cancel_outlined), + icon: const AppIcon( + Symbols.cancel_rounded, + fill: 1, + ), tooltip: 'Cancelled download', iconSize: 20, style: IconButton.styleFrom( @@ -1350,7 +1370,10 @@ class _MediaDetailScreenState extends State { } }, tooltip: tooltip, - icon: const Icon(Icons.downloading), + icon: const AppIcon( + Symbols.downloading_rounded, + fill: 1, + ), iconSize: 20, style: IconButton.styleFrom( minimumSize: const Size(48, 48), @@ -1409,7 +1432,10 @@ class _MediaDetailScreenState extends State { } } }, - icon: const Icon(Icons.file_download_done), + icon: const AppIcon( + Symbols.file_download_done_rounded, + fill: 1, + ), tooltip: t.downloads.deleteDownload, iconSize: 20, style: IconButton.styleFrom( @@ -1438,7 +1464,10 @@ class _MediaDetailScreenState extends State { ); } }, - icon: const Icon(Icons.download), + icon: const AppIcon( + Symbols.download_rounded, + fill: 1, + ), tooltip: t.downloads.downloadNow, iconSize: 20, style: IconButton.styleFrom( @@ -1449,23 +1478,35 @@ class _MediaDetailScreenState extends State { }, ), const SizedBox(width: 12), - // Mark as watched/unwatched (works offline too) + // Mark as watched/unwatched toggle (works offline too) IconButton.filledTonal( onPressed: () async { try { + final isWatched = metadata.isWatched; if (widget.isOffline) { // Offline mode: queue action for later sync final offlineWatch = context .read(); - await offlineWatch.markAsWatched( - serverId: metadata.serverId!, - ratingKey: metadata.ratingKey, - ); + if (isWatched) { + await offlineWatch.markAsUnwatched( + serverId: metadata.serverId!, + ratingKey: metadata.ratingKey, + ); + } else { + await offlineWatch.markAsWatched( + serverId: metadata.serverId!, + ratingKey: metadata.ratingKey, + ); + } if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( - t.messages.markedAsWatchedOffline, + isWatched + ? t + .messages + .markedAsUnwatchedOffline + : t.messages.markedAsWatchedOffline, ), ), ); @@ -1477,12 +1518,24 @@ class _MediaDetailScreenState extends State { final client = _getClientForMetadata(context); if (client == null) return; - await client.markAsWatched(metadata.ratingKey); + if (isWatched) { + await client.markAsUnwatched( + metadata.ratingKey, + ); + } else { + await client.markAsWatched( + metadata.ratingKey, + ); + } if (context.mounted) { _watchStateChanged = true; ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text(t.messages.markedAsWatched), + content: Text( + isWatched + ? t.messages.markedAsUnwatched + : t.messages.markedAsWatched, + ), ), ); // Update watch state without full rebuild @@ -1503,74 +1556,15 @@ class _MediaDetailScreenState extends State { } } }, - icon: const Icon(Icons.check), - tooltip: t.tooltips.markAsWatched, - iconSize: 20, - style: IconButton.styleFrom( - minimumSize: const Size(48, 48), - maximumSize: const Size(48, 48), + icon: AppIcon( + metadata.isWatched + ? Symbols.remove_done_rounded + : Symbols.check_rounded, + fill: 1, ), - ), - const SizedBox(width: 12), - IconButton.filledTonal( - onPressed: () async { - try { - if (widget.isOffline) { - // Offline mode: queue action for later sync - final offlineWatch = context - .read(); - await offlineWatch.markAsUnwatched( - serverId: metadata.serverId!, - ratingKey: metadata.ratingKey, - ); - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - t.messages.markedAsUnwatchedOffline, - ), - ), - ); - // Refresh offline OnDeck - _loadOfflineOnDeckEpisode(); - } - } else { - // Online mode: send to server - final client = _getClientForMetadata(context); - if (client == null) return; - - await client.markAsUnwatched( - metadata.ratingKey, - ); - if (context.mounted) { - _watchStateChanged = true; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - t.messages.markedAsUnwatched, - ), - ), - ); - // Update watch state without full rebuild - _updateWatchState(); - } - } - } catch (e) { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - t.messages.errorLoading( - error: e.toString(), - ), - ), - ), - ); - } - } - }, - icon: const Icon(Icons.remove_done), - tooltip: t.tooltips.markAsUnwatched, + tooltip: metadata.isWatched + ? t.tooltips.markAsUnwatched + : t.tooltips.markAsWatched, iconSize: 20, style: IconButton.styleFrom( minimumSize: const Size(48, 48), @@ -1721,7 +1715,10 @@ class _MediaDetailScreenState extends State { .colorScheme .surfaceContainerHighest, child: const Center( - child: Icon(Icons.person), + child: AppIcon( + Symbols.person_rounded, + fill: 1, + ), ), ), errorWidget: @@ -1736,7 +1733,10 @@ class _MediaDetailScreenState extends State { .colorScheme .surfaceContainerHighest, child: const Center( - child: Icon(Icons.person), + child: AppIcon( + Symbols.person_rounded, + fill: 1, + ), ), ), ) @@ -1747,7 +1747,10 @@ class _MediaDetailScreenState extends State { .colorScheme .surfaceContainerHighest, child: const Center( - child: Icon(Icons.person), + child: AppIcon( + Symbols.person_rounded, + fill: 1, + ), ), ), ), @@ -1872,17 +1875,17 @@ class _MediaDetailScreenState extends State { final episode = _onDeckEpisode!; // Check if episode has been partially watched if (episode.viewOffset != null && episode.viewOffset! > 0) { - return Icons.restore; // Resume icon + return Symbols.resume_rounded; // Resume icon } } } else { // For movies or episodes if (metadata.viewOffset != null && metadata.viewOffset! > 0) { - return Icons.restore; // Resume icon + return Symbols.resume_rounded; // Resume icon } } - return Icons.play_arrow; // Default play icon + return Symbols.play_arrow_rounded; // Default play icon } } @@ -1939,7 +1942,11 @@ class _SeasonCard extends StatelessWidget { color: Theme.of( context, ).colorScheme.surfaceContainerHighest, - child: const Icon(Icons.movie, size: 32), + child: const AppIcon( + Symbols.movie_rounded, + fill: 1, + size: 32, + ), ), ) : season.thumb != null @@ -1962,7 +1969,11 @@ class _SeasonCard extends StatelessWidget { color: Theme.of( context, ).colorScheme.surfaceContainerHighest, - child: const Icon(Icons.movie, size: 32), + child: const AppIcon( + Symbols.movie_rounded, + fill: 1, + size: 32, + ), ), ) : Container( @@ -1971,7 +1982,11 @@ class _SeasonCard extends StatelessWidget { color: Theme.of( context, ).colorScheme.surfaceContainerHighest, - child: const Icon(Icons.movie, size: 32), + child: const AppIcon( + Symbols.movie_rounded, + fill: 1, + size: 32, + ), ), ), const SizedBox(width: 16), @@ -2035,7 +2050,7 @@ class _SeasonCard extends StatelessWidget { ), ), - const Icon(Icons.chevron_right), + const AppIcon(Symbols.chevron_right_rounded, fill: 1), ], ), ), diff --git a/lib/screens/playlist/playlist_detail_screen.dart b/lib/screens/playlist/playlist_detail_screen.dart index f78de493..180079bd 100644 --- a/lib/screens/playlist/playlist_detail_screen.dart +++ b/lib/screens/playlist/playlist_detail_screen.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../services/plex_client.dart'; import '../../services/play_queue_launcher.dart'; import '../../models/plex_playlist.dart'; @@ -35,7 +37,7 @@ class _PlaylistDetailScreenState String get emptyMessage => t.playlists.emptyPlaylist; @override - IconData get emptyIcon => Icons.playlist_play; + IconData get emptyIcon => Symbols.playlist_play_rounded; @override Future> fetchItems() async { @@ -231,7 +233,7 @@ class _PlaylistDetailScreenState Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.auto_awesome, 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, diff --git a/lib/screens/playlist/playlist_item_card.dart b/lib/screens/playlist/playlist_item_card.dart index b9d27cdf..bdea1f37 100644 --- a/lib/screens/playlist/playlist_item_card.dart +++ b/lib/screens/playlist/playlist_item_card.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../services/plex_client.dart'; import '../../models/plex_metadata.dart'; import '../../utils/duration_formatter.dart'; @@ -56,7 +58,7 @@ class _PlaylistItemCardState extends State { index: widget.index, child: const Padding( padding: EdgeInsets.only(right: 12), - child: Icon(Icons.drag_indicator, color: Colors.grey), + child: AppIcon(Symbols.drag_indicator_rounded, fill: 1, color: Colors.grey), ), ), @@ -122,7 +124,7 @@ class _PlaylistItemCardState extends State { // Remove button IconButton( - icon: const Icon(Icons.close, size: 20), + icon: const AppIcon(Symbols.close_rounded, fill: 1, size: 20), onPressed: widget.onRemove, tooltip: t.playlists.removeItem, color: Colors.grey[400], @@ -164,7 +166,7 @@ class _PlaylistItemCardState extends State { color: Colors.grey[850], borderRadius: BorderRadius.circular(6), ), - child: const Icon(Icons.movie, color: Colors.grey, size: 24), + child: const AppIcon(Symbols.movie_rounded, fill: 1, color: Colors.grey, size: 24), ); } diff --git a/lib/screens/profile/pin_entry_dialog.dart b/lib/screens/profile/pin_entry_dialog.dart index 7ccd1e64..6e72c231 100644 --- a/lib/screens/profile/pin_entry_dialog.dart +++ b/lib/screens/profile/pin_entry_dialog.dart @@ -1,4 +1,6 @@ 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 '../../i18n/strings.g.dart'; @@ -86,11 +88,7 @@ class _PinEntryDialogState extends State child: AlertDialog( title: Row( children: [ - Icon( - Icons.lock_outline, - 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), @@ -116,10 +114,7 @@ class _PinEntryDialogState extends State errorText: widget.errorMessage, errorMaxLines: 2, suffixIcon: IconButton( - icon: Icon( - _obscureText ? Icons.visibility_off : Icons.visibility, - size: 20, - ), + icon: AppIcon(_obscureText ? Symbols.visibility_off_rounded : Symbols.visibility_rounded, fill: 1, size: 20), onPressed: () { setState(() { _obscureText = !_obscureText; diff --git a/lib/screens/profile/profile_list_tile.dart b/lib/screens/profile/profile_list_tile.dart index bdffc241..07e36782 100644 --- a/lib/screens/profile/profile_list_tile.dart +++ b/lib/screens/profile/profile_list_tile.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../i18n/strings.g.dart'; import '../../models/plex_home_user.dart'; import 'user_avatar_widget.dart'; @@ -46,7 +48,7 @@ class ProfileListTile extends StatelessWidget { ), ), ) - : (showTrailingIcon ? const Icon(Icons.chevron_right) : null), + : (showTrailingIcon ? const AppIcon(Symbols.chevron_right_rounded, fill: 1) : null), onTap: isCurrentUser ? null : onTap, enabled: !isCurrentUser, ); diff --git a/lib/screens/profile/profile_switch_screen.dart b/lib/screens/profile/profile_switch_screen.dart index a532413b..be3b98e5 100644 --- a/lib/screens/profile/profile_switch_screen.dart +++ b/lib/screens/profile/profile_switch_screen.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import '../../models/plex_home_user.dart'; import '../../providers/user_profile_provider.dart'; @@ -54,11 +56,7 @@ class ProfileSwitchScreen extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( - Icons.person_off, - 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', diff --git a/lib/screens/profile/user_avatar_widget.dart b/lib/screens/profile/user_avatar_widget.dart index 93f54fae..af28abc4 100644 --- a/lib/screens/profile/user_avatar_widget.dart +++ b/lib/screens/profile/user_avatar_widget.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:cached_network_image/cached_network_image.dart'; import '../../models/plex_home_user.dart'; import '../../i18n/strings.g.dart'; @@ -27,11 +29,7 @@ class UserAvatarWidget extends StatelessWidget { color: theme.colorScheme.surfaceContainerHighest, shape: BoxShape.circle, ), - child: Icon( - Icons.person, - size: size * 0.6, - color: theme.colorScheme.onSurfaceVariant, - ), + child: AppIcon(Symbols.person_rounded, fill: 1, size: size * 0.6, color: theme.colorScheme.onSurfaceVariant), ); } @@ -69,7 +67,7 @@ class UserAvatarWidget extends StatelessWidget { width: 1, ), ), - child: Icon(icon, size: iconSize, color: iconColor), + child: AppIcon(icon, fill: 1, size: iconSize, color: iconColor), ), ); } @@ -176,7 +174,7 @@ class UserAvatarWidget extends StatelessWidget { if (user.isAdminUser) _buildBadge( context: context, - icon: Icons.admin_panel_settings, + icon: Symbols.admin_panel_settings_rounded, color: theme.colorScheme.primary, iconColor: theme.colorScheme.onPrimary, position: 'topRight', @@ -186,7 +184,7 @@ class UserAvatarWidget extends StatelessWidget { if (user.isRestrictedUser && !user.isAdminUser) _buildBadge( context: context, - icon: Icons.security, + icon: Symbols.security_rounded, color: theme.colorScheme.warning ?? Colors.orange, iconColor: theme.colorScheme.onPrimary, position: 'topRight', @@ -196,7 +194,7 @@ class UserAvatarWidget extends StatelessWidget { if (user.requiresPassword) _buildBadge( context: context, - icon: Icons.lock, + icon: Symbols.lock_rounded, color: theme.colorScheme.secondary, iconColor: theme.colorScheme.onSecondary, position: 'bottomRight', diff --git a/lib/screens/search_screen.dart b/lib/screens/search_screen.dart index dd2c7ca3..845a9474 100644 --- a/lib/screens/search_screen.dart +++ b/lib/screens/search_screen.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import 'package:rate_limiter/rate_limiter.dart'; @@ -171,10 +173,10 @@ class _SearchScreenState extends State with Refreshable { focusNode: _searchFocusNode, decoration: InputDecoration( hintText: t.search.hint, - prefixIcon: const Icon(Icons.search), + prefixIcon: const AppIcon(Symbols.search_rounded, fill: 1), suffixIcon: _searchController.text.isNotEmpty ? IconButton( - icon: const Icon(Icons.clear), + icon: const AppIcon(Symbols.clear_rounded, fill: 1), onPressed: () { _searchController.clear(); // State update handled by listener @@ -215,7 +217,7 @@ class _SearchScreenState extends State with Refreshable { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.search, 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, @@ -238,11 +240,7 @@ class _SearchScreenState extends State with Refreshable { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( - Icons.search_off, - 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, diff --git a/lib/screens/season_detail_screen.dart b/lib/screens/season_detail_screen.dart index 906c1d09..d5dea5cd 100644 --- a/lib/screens/season_detail_screen.dart +++ b/lib/screens/season_detail_screen.dart @@ -1,6 +1,8 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import '../../services/plex_client.dart'; import '../focus/key_event_utils.dart'; @@ -147,11 +149,7 @@ class _SeasonDetailScreenState extends State child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( - Icons.movie_outlined, - 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, @@ -317,7 +315,7 @@ class _EpisodeCard extends StatelessWidget { color: Theme.of( context, ).colorScheme.surfaceContainerHighest, - child: const Icon(Icons.movie, size: 32), + child: const AppIcon(Symbols.movie_rounded, fill: 1, size: 32), ), ) : episode.thumb != null @@ -335,14 +333,14 @@ class _EpisodeCard extends StatelessWidget { color: Theme.of( context, ).colorScheme.surfaceContainerHighest, - child: const Icon(Icons.movie, size: 32), + child: const AppIcon(Symbols.movie_rounded, fill: 1, size: 32), ), ) : Container( color: Theme.of( context, ).colorScheme.surfaceContainerHighest, - child: const Icon(Icons.movie, size: 32), + child: const AppIcon(Symbols.movie_rounded, fill: 1, size: 32), ), ), ), @@ -368,11 +366,7 @@ class _EpisodeCard extends StatelessWidget { color: Colors.black.withValues(alpha: 0.6), shape: BoxShape.circle, ), - child: const Icon( - Icons.play_arrow, - color: Colors.white, - size: 20, - ), + child: const AppIcon(Symbols.play_arrow_rounded, fill: 1, color: Colors.white, size: 20), ), ), ), @@ -446,11 +440,7 @@ class _EpisodeCard extends StatelessWidget { } else if (progress?.status == DownloadStatus.queued) { // Queued state - waiting to download - downloadStatusIcon = Icon( - Icons.schedule, - 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 @@ -484,35 +474,19 @@ class _EpisodeCard extends StatelessWidget { } else if (progress?.status == DownloadStatus.paused) { // Paused state - download paused - downloadStatusIcon = Icon( - Icons.pause_circle_outline, - 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 = Icon( - Icons.error_outline, - 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 = Icon( - Icons.cancel_outlined, - 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 = Icon( - Icons.file_download_done, - 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) } diff --git a/lib/screens/settings/about_screen.dart b/lib/screens/settings/about_screen.dart index e1a3b31b..16b77d25 100644 --- a/lib/screens/settings/about_screen.dart +++ b/lib/screens/settings/about_screen.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:package_info_plus/package_info_plus.dart'; import '../../widgets/focused_scroll_scaffold.dart'; import '../../i18n/strings.g.dart'; @@ -75,10 +77,10 @@ class _AboutScreenState extends State { // Open Source Licenses Card( child: ListTile( - leading: const Icon(Icons.description), + leading: const AppIcon(Symbols.description_rounded, fill: 1), title: Text(t.about.openSourceLicenses), subtitle: Text(t.about.viewLicensesDescription), - trailing: const Icon(Icons.chevron_right), + trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: () { Navigator.push( context, diff --git a/lib/screens/settings/hotkey_recorder_widget.dart b/lib/screens/settings/hotkey_recorder_widget.dart index 323e303e..349610d5 100644 --- a/lib/screens/settings/hotkey_recorder_widget.dart +++ b/lib/screens/settings/hotkey_recorder_widget.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:hotkey_manager/hotkey_manager.dart'; import '../../i18n/strings.g.dart'; @@ -71,7 +73,7 @@ class _HotKeyRecorderWidgetState extends State { ), if (_recordedHotKey != null) IconButton( - icon: const Icon(Icons.backspace, size: 18), + icon: const AppIcon(Symbols.backspace_rounded, fill: 1, size: 18), onPressed: () { setState(() { _recordedHotKey = null; diff --git a/lib/screens/settings/licenses_screen.dart b/lib/screens/settings/licenses_screen.dart index ff08e1b8..6cfcf066 100644 --- a/lib/screens/settings/licenses_screen.dart +++ b/lib/screens/settings/licenses_screen.dart @@ -1,5 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../widgets/focused_scroll_scaffold.dart'; import '../../i18n/strings.g.dart'; @@ -104,7 +106,7 @@ class _LicensesScreenState extends State { ), ) : null, - trailing: const Icon(Icons.chevron_right), + trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: () => _showLicenseDetail(mergedLicense), ), ); diff --git a/lib/screens/settings/logs_screen.dart b/lib/screens/settings/logs_screen.dart index e9447650..5a36be7e 100644 --- a/lib/screens/settings/logs_screen.dart +++ b/lib/screens/settings/logs_screen.dart @@ -1,4 +1,6 @@ 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:logger/logger.dart'; import '../../i18n/strings.g.dart'; @@ -91,16 +93,16 @@ class _LogsScreenState extends State { switch (level) { case Level.error: case Level.fatal: - return Icons.error; + return Symbols.error_rounded; case Level.warning: - return Icons.warning; + return Symbols.warning_rounded; case Level.info: - return Icons.info; + return Symbols.info_rounded; case Level.debug: case Level.trace: - return Icons.bug_report; + return Symbols.bug_report_rounded; default: - return Icons.circle; + return Symbols.circle_rounded; } } @@ -114,17 +116,17 @@ class _LogsScreenState extends State { pinned: true, actions: [ IconButton( - icon: const Icon(Icons.refresh), + icon: const AppIcon(Symbols.refresh_rounded, fill: 1), onPressed: _loadLogs, tooltip: t.common.refresh, ), IconButton( - icon: const Icon(Icons.copy), + icon: const AppIcon(Symbols.content_copy_rounded, fill: 1), onPressed: _logs.isNotEmpty ? _copyAllLogs : null, tooltip: t.logs.copyLogs, ), IconButton( - icon: const Icon(Icons.delete_outline), + icon: const AppIcon(Symbols.delete_outline_rounded, fill: 1), onPressed: _logs.isNotEmpty ? _clearLogs : null, tooltip: t.logs.clearLogs, ), @@ -194,7 +196,7 @@ class _LogEntryCardState extends State<_LogEntryCard> { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Icon(widget.levelIcon, color: widget.levelColor, size: 20), + AppIcon(widget.levelIcon, fill: 1, color: widget.levelColor, size: 20), const SizedBox(width: 8), Expanded( child: Column( @@ -233,12 +235,9 @@ class _LogEntryCardState extends State<_LogEntryCard> { ), ), if (hasErrorOrStackTrace) - Icon( - _isExpanded ? Icons.expand_less : Icons.expand_more, - 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) ...[ diff --git a/lib/screens/settings/settings_screen.dart b/lib/screens/settings/settings_screen.dart index 8d58e116..e9ee2e84 100644 --- a/lib/screens/settings/settings_screen.dart +++ b/lib/screens/settings/settings_screen.dart @@ -2,6 +2,8 @@ import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:hotkey_manager/hotkey_manager.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -129,30 +131,30 @@ class _SettingsScreenState extends State { Consumer( builder: (context, themeProvider, child) { return ListTile( - leading: Icon(themeProvider.themeModeIcon), + leading: AppIcon(themeProvider.themeModeIcon, fill: 1), title: Text(t.settings.theme), subtitle: Text(themeProvider.themeModeDisplayName), - trailing: const Icon(Icons.chevron_right), + trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: () => _showThemeDialog(themeProvider), ); }, ), ListTile( - leading: const Icon(Icons.language), + leading: const AppIcon(Symbols.language_rounded, fill: 1), title: Text(t.settings.language), subtitle: Text( _getLanguageDisplayName(LocaleSettings.currentLocale), ), - trailing: const Icon(Icons.chevron_right), + trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: () => _showLanguageDialog(), ), Consumer( builder: (context, settingsProvider, child) { return ListTile( - leading: const Icon(Icons.grid_view), + leading: const AppIcon(Symbols.grid_view_rounded, fill: 1), title: Text(t.settings.libraryDensity), subtitle: Text(settingsProvider.libraryDensityDisplayName), - trailing: const Icon(Icons.chevron_right), + trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: () => _showLibraryDensityDialog(), ); }, @@ -160,14 +162,14 @@ class _SettingsScreenState extends State { Consumer( builder: (context, settingsProvider, child) { return ListTile( - leading: const Icon(Icons.view_list), + leading: const AppIcon(Symbols.view_list_rounded, fill: 1), title: Text(t.settings.viewMode), subtitle: Text( settingsProvider.viewMode == settings.ViewMode.grid ? t.settings.gridView : t.settings.listView, ), - trailing: const Icon(Icons.chevron_right), + trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: () => _showViewModeDialog(), ); }, @@ -175,7 +177,7 @@ class _SettingsScreenState extends State { Consumer( builder: (context, settingsProvider, child) { return SwitchListTile( - secondary: const Icon(Icons.image), + secondary: const AppIcon(Symbols.image_rounded, fill: 1), title: Text(t.settings.useSeasonPosters), subtitle: Text(t.settings.useSeasonPostersDescription), value: settingsProvider.useSeasonPoster, @@ -188,7 +190,10 @@ class _SettingsScreenState extends State { Consumer( builder: (context, settingsProvider, child) { return SwitchListTile( - secondary: const Icon(Icons.featured_play_list), + secondary: const AppIcon( + Symbols.featured_play_list_rounded, + fill: 1, + ), title: Text(t.settings.showHeroSection), subtitle: Text(t.settings.showHeroSectionDescription), value: settingsProvider.showHeroSection, @@ -218,7 +223,7 @@ class _SettingsScreenState extends State { ), ), SwitchListTile( - secondary: const Icon(Icons.hardware), + secondary: const AppIcon(Symbols.hardware_rounded, fill: 1), title: Text(t.settings.hardwareDecoding), subtitle: Text(t.settings.hardwareDecodingDescription), value: _enableHardwareDecoding, @@ -230,19 +235,19 @@ class _SettingsScreenState extends State { }, ), ListTile( - leading: const Icon(Icons.memory), + leading: const AppIcon(Symbols.memory_rounded, fill: 1), title: Text(t.settings.bufferSize), subtitle: Text( t.settings.bufferSizeMB(size: _bufferSize.toString()), ), - trailing: const Icon(Icons.chevron_right), + trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: () => _showBufferSizeDialog(), ), ListTile( - leading: const Icon(Icons.subtitles), + leading: const AppIcon(Symbols.subtitles_rounded, fill: 1), title: Text(t.settings.subtitleStyling), subtitle: Text(t.settings.subtitleStylingDescription), - trailing: const Icon(Icons.chevron_right), + trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: () { Navigator.push( context, @@ -253,34 +258,34 @@ class _SettingsScreenState extends State { }, ), ListTile( - leading: const Icon(Icons.replay_10), + leading: const AppIcon(Symbols.replay_10_rounded, fill: 1), title: Text(t.settings.smallSkipDuration), subtitle: Text( t.settings.secondsUnit(seconds: _seekTimeSmall.toString()), ), - trailing: const Icon(Icons.chevron_right), + trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: () => _showSeekTimeSmallDialog(), ), ListTile( - leading: const Icon(Icons.replay_30), + leading: const AppIcon(Symbols.replay_30_rounded, fill: 1), title: Text(t.settings.largeSkipDuration), subtitle: Text( t.settings.secondsUnit(seconds: _seekTimeLarge.toString()), ), - trailing: const Icon(Icons.chevron_right), + trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: () => _showSeekTimeLargeDialog(), ), ListTile( - leading: const Icon(Icons.bedtime), + leading: const AppIcon(Symbols.bedtime_rounded, fill: 1), title: Text(t.settings.defaultSleepTimer), subtitle: Text( t.settings.minutesUnit(minutes: _sleepTimerDuration.toString()), ), - trailing: const Icon(Icons.chevron_right), + trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: () => _showSleepTimerDurationDialog(), ), SwitchListTile( - secondary: const Icon(Icons.bookmark), + secondary: const AppIcon(Symbols.bookmark_rounded, fill: 1), title: Text(t.settings.rememberTrackSelections), subtitle: Text(t.settings.rememberTrackSelectionsDescription), value: _rememberTrackSelections, @@ -303,7 +308,7 @@ class _SettingsScreenState extends State { ), ), SwitchListTile( - secondary: const Icon(Icons.fast_forward), + secondary: const AppIcon(Symbols.fast_forward_rounded, fill: 1), title: Text(t.settings.autoSkipIntro), subtitle: Text(t.settings.autoSkipIntroDescription), value: _autoSkipIntro, @@ -315,7 +320,7 @@ class _SettingsScreenState extends State { }, ), SwitchListTile( - secondary: const Icon(Icons.skip_next), + secondary: const AppIcon(Symbols.skip_next_rounded, fill: 1), title: Text(t.settings.autoSkipCredits), subtitle: Text(t.settings.autoSkipCreditsDescription), value: _autoSkipCredits, @@ -327,14 +332,14 @@ class _SettingsScreenState extends State { }, ), ListTile( - leading: const Icon(Icons.timer), + leading: const AppIcon(Symbols.timer_rounded, fill: 1), title: Text(t.settings.autoSkipDelay), subtitle: Text( t.settings.autoSkipDelayDescription( seconds: _autoSkipDelay.toString(), ), ), - trailing: const Icon(Icons.chevron_right), + trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: () => _showAutoSkipDelayDialog(), ), ], @@ -367,7 +372,7 @@ class _SettingsScreenState extends State { final currentPath = snapshot.data ?? '...'; return ListTile( - leading: const Icon(Icons.folder), + leading: const AppIcon(Symbols.folder_rounded, fill: 1), title: Text( isCustom ? t.settings.downloadLocationCustom @@ -378,13 +383,13 @@ class _SettingsScreenState extends State { maxLines: 2, overflow: TextOverflow.ellipsis, ), - trailing: const Icon(Icons.chevron_right), + trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: () => _showDownloadLocationDialog(), ); }, ), SwitchListTile( - secondary: const Icon(Icons.wifi), + secondary: const AppIcon(Symbols.wifi_rounded, fill: 1), title: Text(t.settings.downloadOnWifiOnly), subtitle: Text(t.settings.downloadOnWifiOnlyDescription), value: _downloadOnWifiOnly, @@ -534,10 +539,10 @@ class _SettingsScreenState extends State { ), ), ListTile( - leading: const Icon(Icons.keyboard), + leading: const AppIcon(Symbols.keyboard_rounded, fill: 1), title: Text(t.settings.videoPlayerControls), subtitle: Text(t.settings.keyboardShortcutsDescription), - trailing: const Icon(Icons.chevron_right), + trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: () => _showKeyboardShortcutsDialog(), ), ], @@ -560,7 +565,7 @@ class _SettingsScreenState extends State { ), ), SwitchListTile( - secondary: const Icon(Icons.bug_report), + secondary: const AppIcon(Symbols.bug_report_rounded, fill: 1), title: Text(t.settings.debugLogging), subtitle: Text(t.settings.debugLoggingDescription), value: _enableDebugLogging, @@ -572,10 +577,10 @@ class _SettingsScreenState extends State { }, ), ListTile( - leading: const Icon(Icons.article), + leading: const AppIcon(Symbols.article_rounded, fill: 1), title: Text(t.settings.viewLogs), subtitle: Text(t.settings.viewLogsDescription), - trailing: const Icon(Icons.chevron_right), + trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: () { Navigator.push( context, @@ -584,17 +589,17 @@ class _SettingsScreenState extends State { }, ), ListTile( - leading: const Icon(Icons.cleaning_services), + leading: const AppIcon(Symbols.cleaning_services_rounded, fill: 1), title: Text(t.settings.clearCache), subtitle: Text(t.settings.clearCacheDescription), - trailing: const Icon(Icons.chevron_right), + trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: () => _showClearCacheDialog(), ), ListTile( - leading: const Icon(Icons.restore), + leading: const AppIcon(Symbols.restore_rounded, fill: 1), title: Text(t.settings.resetSettings), subtitle: Text(t.settings.resetSettingsDescription), - trailing: const Icon(Icons.chevron_right), + trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: () => _showResetSettingsDialog(), ), ], @@ -619,8 +624,11 @@ class _SettingsScreenState extends State { ), ), ListTile( - leading: Icon( - hasUpdate ? Icons.system_update : Icons.check_circle, + leading: AppIcon( + hasUpdate + ? Symbols.system_update_rounded + : Symbols.check_circle_rounded, + fill: 1, color: hasUpdate ? Colors.orange : null, ), title: Text( @@ -641,7 +649,7 @@ class _SettingsScreenState extends State { height: 24, child: CircularProgressIndicator(strokeWidth: 2), ) - : const Icon(Icons.chevron_right), + : const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: _isCheckingForUpdate ? null : () { @@ -660,10 +668,10 @@ class _SettingsScreenState extends State { Widget _buildAboutSection() { return Card( child: ListTile( - leading: const Icon(Icons.info), + leading: const AppIcon(Symbols.info_rounded, fill: 1), title: Text(t.settings.about), subtitle: Text(t.settings.aboutDescription), - trailing: const Icon(Icons.chevron_right), + trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: () { Navigator.push( context, @@ -684,10 +692,11 @@ class _SettingsScreenState extends State { mainAxisSize: MainAxisSize.min, children: [ ListTile( - leading: Icon( + leading: AppIcon( themeProvider.themeMode == settings.ThemeMode.system - ? Icons.radio_button_checked - : Icons.radio_button_unchecked, + ? Symbols.radio_button_checked_rounded + : Symbols.radio_button_unchecked_rounded, + fill: 1, ), title: Text(t.settings.systemTheme), subtitle: Text(t.settings.systemThemeDescription), @@ -697,10 +706,11 @@ class _SettingsScreenState extends State { }, ), ListTile( - leading: Icon( + leading: AppIcon( themeProvider.themeMode == settings.ThemeMode.light - ? Icons.radio_button_checked - : Icons.radio_button_unchecked, + ? Symbols.radio_button_checked_rounded + : Symbols.radio_button_unchecked_rounded, + fill: 1, ), title: Text(t.settings.lightTheme), onTap: () { @@ -709,10 +719,11 @@ class _SettingsScreenState extends State { }, ), ListTile( - leading: Icon( + leading: AppIcon( themeProvider.themeMode == settings.ThemeMode.dark - ? Icons.radio_button_checked - : Icons.radio_button_unchecked, + ? Symbols.radio_button_checked_rounded + : Symbols.radio_button_unchecked_rounded, + fill: 1, ), title: Text(t.settings.darkTheme), onTap: () { @@ -745,10 +756,11 @@ class _SettingsScreenState extends State { mainAxisSize: MainAxisSize.min, children: options.map((size) { return ListTile( - leading: Icon( + leading: AppIcon( _bufferSize == size - ? Icons.radio_button_checked - : Icons.radio_button_unchecked, + ? Symbols.radio_button_checked_rounded + : Symbols.radio_button_unchecked_rounded, + fill: 1, ), title: Text('${size}MB'), onTap: () { @@ -1148,10 +1160,11 @@ class _SettingsScreenState extends State { final isSelected = LocaleSettings.currentLocale == locale; return ListTile( title: Text(_getLanguageDisplayName(locale)), - leading: Icon( + leading: AppIcon( isSelected - ? Icons.radio_button_checked - : Icons.radio_button_unchecked, + ? Symbols.radio_button_checked_rounded + : Symbols.radio_button_unchecked_rounded, + fill: 1, color: isSelected ? Theme.of(context).colorScheme.primary : null, @@ -1298,10 +1311,11 @@ class _SettingsScreenState extends State { mainAxisSize: MainAxisSize.min, children: [ ListTile( - leading: Icon( + leading: AppIcon( provider.libraryDensity == settings.LibraryDensity.compact - ? Icons.radio_button_checked - : Icons.radio_button_unchecked, + ? Symbols.radio_button_checked_rounded + : Symbols.radio_button_unchecked_rounded, + fill: 1, ), title: Text(t.settings.compact), subtitle: Text(t.settings.compactDescription), @@ -1313,10 +1327,11 @@ class _SettingsScreenState extends State { }, ), ListTile( - leading: Icon( + leading: AppIcon( provider.libraryDensity == settings.LibraryDensity.normal - ? Icons.radio_button_checked - : Icons.radio_button_unchecked, + ? Symbols.radio_button_checked_rounded + : Symbols.radio_button_unchecked_rounded, + fill: 1, ), title: Text(t.settings.normal), subtitle: Text(t.settings.normalDescription), @@ -1328,11 +1343,12 @@ class _SettingsScreenState extends State { }, ), ListTile( - leading: Icon( + leading: AppIcon( provider.libraryDensity == settings.LibraryDensity.comfortable - ? Icons.radio_button_checked - : Icons.radio_button_unchecked, + ? Symbols.radio_button_checked_rounded + : Symbols.radio_button_unchecked_rounded, + fill: 1, ), title: Text(t.settings.comfortable), subtitle: Text(t.settings.comfortableDescription), @@ -1371,10 +1387,11 @@ class _SettingsScreenState extends State { mainAxisSize: MainAxisSize.min, children: [ ListTile( - leading: Icon( + leading: AppIcon( provider.viewMode == settings.ViewMode.grid - ? Icons.radio_button_checked - : Icons.radio_button_unchecked, + ? Symbols.radio_button_checked_rounded + : Symbols.radio_button_unchecked_rounded, + fill: 1, ), title: Text(t.settings.gridView), subtitle: Text(t.settings.gridViewDescription), @@ -1386,10 +1403,11 @@ class _SettingsScreenState extends State { }, ), ListTile( - leading: Icon( + leading: AppIcon( provider.viewMode == settings.ViewMode.list - ? Icons.radio_button_checked - : Icons.radio_button_unchecked, + ? Symbols.radio_button_checked_rounded + : Symbols.radio_button_unchecked_rounded, + fill: 1, ), title: Text(t.settings.listView), subtitle: Text(t.settings.listViewDescription), diff --git a/lib/screens/settings/subtitle_styling_screen.dart b/lib/screens/settings/subtitle_styling_screen.dart index d54bcd79..b2bb019d 100644 --- a/lib/screens/settings/subtitle_styling_screen.dart +++ b/lib/screens/settings/subtitle_styling_screen.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:flex_color_picker/flex_color_picker.dart'; import '../../i18n/strings.g.dart'; import '../../services/settings_service.dart'; @@ -103,7 +105,7 @@ class _ColorSettingTile extends StatelessWidget { ), title: Text(label), subtitle: Text(currentColor), - trailing: const Icon(Icons.chevron_right), + trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: onTap, ); } diff --git a/lib/screens/video_player_screen.dart b/lib/screens/video_player_screen.dart index c47813bf..c0ec70ac 100644 --- a/lib/screens/video_player_screen.dart +++ b/lib/screens/video_player_screen.dart @@ -2,6 +2,8 @@ import 'dart:async'; import 'dart:io'; 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:os_media_controls/os_media_controls.dart'; import 'package:provider/provider.dart'; @@ -1373,11 +1375,7 @@ class VideoPlayerScreenState extends State child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Icon( - Icons.play_circle_outline, - size: 64, - color: Colors.white, - ), + const AppIcon(Symbols.play_circle_rounded, fill: 1, size: 64, color: Colors.white), const SizedBox(height: 24), Consumer( builder: (context, playbackState, child) { @@ -1396,11 +1394,7 @@ class VideoPlayerScreenState extends State ), if (isShuffleActive) ...[ const SizedBox(width: 8), - const Icon( - Icons.shuffle, - size: 20, - color: Colors.white70, - ), + const AppIcon(Symbols.shuffle_rounded, fill: 1, size: 20, color: Colors.white70), ], ], ); diff --git a/lib/utils/video_control_icons.dart b/lib/utils/video_control_icons.dart index a31b9f8b..d9dfdc03 100644 --- a/lib/utils/video_control_icons.dart +++ b/lib/utils/video_control_icons.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; /// Get the replay icon based on the duration /// Returns numbered icons (replay_5, replay_10, replay_30) when available, @@ -6,13 +7,13 @@ import 'package:flutter/material.dart'; IconData getReplayIcon(int seconds) { switch (seconds) { case 5: - return Icons.replay_5; + return Symbols.replay_5_rounded; case 10: - return Icons.replay_10; + return Symbols.replay_10_rounded; case 30: - return Icons.replay_30; + return Symbols.replay_30_rounded; default: - return Icons.replay; // Generic icon for custom durations + return Symbols.replay_rounded; // Generic icon for custom durations } } @@ -22,12 +23,12 @@ IconData getReplayIcon(int seconds) { IconData getForwardIcon(int seconds) { switch (seconds) { case 5: - return Icons.forward_5; + return Symbols.forward_5_rounded; case 10: - return Icons.forward_10; + return Symbols.forward_10_rounded; case 30: - return Icons.forward_30; + return Symbols.forward_30_rounded; default: - return Icons.forward; // Generic icon for custom durations + return Symbols.forward_rounded; // Generic icon for custom durations } } diff --git a/lib/widgets/app_bar_back_button.dart b/lib/widgets/app_bar_back_button.dart index 7a46a585..2b17c8a9 100644 --- a/lib/widgets/app_bar_back_button.dart +++ b/lib/widgets/app_bar_back_button.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; /// Defines the visual style of the back button enum BackButtonStyle { @@ -152,11 +154,7 @@ class _AppBarBackButtonState extends State color: currentColor, shape: BoxShape.circle, ), - child: Icon( - Icons.arrow_back, - color: effectiveColor, - size: 20, - ), + child: AppIcon(Symbols.arrow_back_rounded, fill: 1, color: effectiveColor, size: 20), ); }, ), diff --git a/lib/widgets/app_icon.dart b/lib/widgets/app_icon.dart new file mode 100644 index 00000000..88febcea --- /dev/null +++ b/lib/widgets/app_icon.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; + +/// Wrapper around [Icon] that centralizes our Material Symbols defaults. +/// Defaults: fill=1 (filled) and weight=700 (bold). Update [AppIconDefaults] +/// to tweak app-wide icon appearance from one place. +class AppIcon extends StatelessWidget { + const AppIcon( + this.icon, { + super.key, + this.size, + this.color, + this.fill, + this.weight, + this.grade, + this.opticalSize, + this.shadows, + this.semanticLabel, + this.textDirection, + }); + + final IconData? icon; + final double? size; + final Color? color; + final double? fill; + final double? weight; + final double? grade; + final double? opticalSize; + final List? shadows; + final String? semanticLabel; + final TextDirection? textDirection; + + @override + Widget build(BuildContext context) { + if (icon == null) return const SizedBox.shrink(); + return Icon( + icon, + size: size, + color: color, + fill: fill ?? AppIconDefaults.fill, + weight: weight ?? AppIconDefaults.weight, + grade: grade ?? AppIconDefaults.grade, + opticalSize: opticalSize ?? AppIconDefaults.opticalSize, + shadows: shadows ?? AppIconDefaults.shadows, + semanticLabel: semanticLabel, + textDirection: textDirection, + ); + } +} + +/// Central place to adjust default Material Symbol variations. +class AppIconDefaults { + static double fill = 1; + static double weight = 700; + static double? grade; + static double? opticalSize; + static Color? color; + static List? shadows; + + static void update({ + double? fill, + double? weight, + double? grade, + double? opticalSize, + Color? color, + List? shadows, + }) { + if (fill != null) AppIconDefaults.fill = fill; + if (weight != null) AppIconDefaults.weight = weight; + if (grade != null) AppIconDefaults.grade = grade; + if (opticalSize != null) AppIconDefaults.opticalSize = opticalSize; + if (color != null) AppIconDefaults.color = color; + if (shadows != null) AppIconDefaults.shadows = shadows; + } +} diff --git a/lib/widgets/bottom_sheet_header.dart b/lib/widgets/bottom_sheet_header.dart index 8b77c75a..389fc453 100644 --- a/lib/widgets/bottom_sheet_header.dart +++ b/lib/widgets/bottom_sheet_header.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; /// A reusable header widget for bottom sheets /// Provides consistent styling with title, optional leading widget, optional action, and close button @@ -44,7 +46,7 @@ class BottomSheetHeader extends StatelessWidget { ), if (action != null) action!, IconButton( - icon: const Icon(Icons.close), + icon: const AppIcon(Symbols.close_rounded, fill: 1), onPressed: onClose ?? () => Navigator.pop(context), ), ], diff --git a/lib/widgets/desktop_app_bar.dart b/lib/widgets/desktop_app_bar.dart index fdc7ea71..13296f1f 100644 --- a/lib/widgets/desktop_app_bar.dart +++ b/lib/widgets/desktop_app_bar.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../utils/desktop_window_padding.dart'; import '../services/fullscreen_state_manager.dart'; import 'app_bar_back_button.dart'; @@ -53,7 +55,7 @@ class DesktopAppBarSections { if (canPop) { effectiveLeading = IconButton( - icon: const Icon(Icons.arrow_back), + icon: const AppIcon(Symbols.arrow_back_rounded, fill: 1), onPressed: () => Navigator.of(context).pop(), tooltip: MaterialLocalizations.of(context).backButtonTooltip, ); diff --git a/lib/widgets/download_tree_item.dart b/lib/widgets/download_tree_item.dart index e4ddb48a..05a31207 100644 --- a/lib/widgets/download_tree_item.dart +++ b/lib/widgets/download_tree_item.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../models/download_progress.dart'; import '../models/download_status.dart'; @@ -119,10 +121,7 @@ class DownloadTreeItem extends StatelessWidget { Widget _buildExpandIcon() { return Padding( padding: const EdgeInsets.only(right: 8.0), - child: Icon( - isExpanded ? Icons.expand_more : Icons.chevron_right, - size: 24, - ), + child: AppIcon(isExpanded ? Symbols.expand_more_rounded : Symbols.chevron_right_rounded, fill: 1, size: 24), ); } @@ -263,7 +262,7 @@ class DownloadTreeItem extends StatelessWidget { switch (status) { case DownloadStatus.downloading: return IconButton( - icon: const Icon(Icons.pause), + icon: const AppIcon(Symbols.pause_rounded, fill: 1), onPressed: onPause, tooltip: 'Pause', iconSize: 20, @@ -271,7 +270,7 @@ class DownloadTreeItem extends StatelessWidget { case DownloadStatus.queued: return IconButton( - icon: const Icon(Icons.close), + icon: const AppIcon(Symbols.close_rounded, fill: 1), onPressed: onCancel, tooltip: 'Cancel', iconSize: 20, @@ -282,13 +281,13 @@ class DownloadTreeItem extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ IconButton( - icon: const Icon(Icons.play_arrow), + icon: const AppIcon(Symbols.play_arrow_rounded, fill: 1), onPressed: onResume, tooltip: 'Resume', iconSize: 20, ), IconButton( - icon: const Icon(Icons.close), + icon: const AppIcon(Symbols.close_rounded, fill: 1), onPressed: onCancel, tooltip: 'Cancel', iconSize: 20, @@ -298,7 +297,7 @@ class DownloadTreeItem extends StatelessWidget { case DownloadStatus.completed: return IconButton( - icon: const Icon(Icons.delete_outline), + icon: const AppIcon(Symbols.delete_outline_rounded, fill: 1), onPressed: onDelete, tooltip: 'Delete', iconSize: 20, @@ -309,13 +308,13 @@ class DownloadTreeItem extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ IconButton( - icon: const Icon(Icons.refresh), + icon: const AppIcon(Symbols.refresh_rounded, fill: 1), onPressed: onRetry, tooltip: 'Retry', iconSize: 20, ), IconButton( - icon: const Icon(Icons.delete_outline), + icon: const AppIcon(Symbols.delete_outline_rounded, fill: 1), onPressed: onDelete, tooltip: 'Delete', iconSize: 20, @@ -325,7 +324,7 @@ class DownloadTreeItem extends StatelessWidget { case DownloadStatus.cancelled: return IconButton( - icon: const Icon(Icons.delete_outline), + icon: const AppIcon(Symbols.delete_outline_rounded, fill: 1), onPressed: onDelete, tooltip: 'Delete', iconSize: 20, diff --git a/lib/widgets/download_tree_view.dart b/lib/widgets/download_tree_view.dart index af723f3b..deb4e720 100644 --- a/lib/widgets/download_tree_view.dart +++ b/lib/widgets/download_tree_view.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../i18n/strings.g.dart'; import '../models/download_progress.dart'; import '../models/download_status.dart'; @@ -362,7 +364,7 @@ class _DownloadTreeViewState extends State { children: [ // Expand/collapse icon if (canExpand) - Icon(isExpanded ? Icons.expand_more : Icons.chevron_right, size: 20) + AppIcon(isExpanded ? Symbols.expand_more_rounded : Symbols.chevron_right_rounded, fill: 1, size: 20) else const SizedBox(width: 20), @@ -433,36 +435,36 @@ class _DownloadTreeViewState extends State { switch (status) { case DownloadStatus.downloading: - iconData = Icons.downloading; + iconData = Symbols.downloading_rounded; color = Colors.blue; break; case DownloadStatus.queued: - iconData = Icons.schedule; + iconData = Symbols.schedule_rounded; color = Colors.orange; break; case DownloadStatus.paused: - iconData = Icons.pause_circle_outline; + iconData = Symbols.pause_circle_outline_rounded; color = Colors.grey; break; case DownloadStatus.completed: - iconData = Icons.check_circle; + iconData = Symbols.check_circle_rounded; color = Colors.green; break; case DownloadStatus.failed: - iconData = Icons.error; + iconData = Symbols.error_rounded; color = Colors.red; break; case DownloadStatus.cancelled: - iconData = Icons.cancel; + iconData = Symbols.cancel_rounded; color = Colors.grey; break; case DownloadStatus.partial: - iconData = Icons.downloading; + iconData = Symbols.downloading_rounded; color = Colors.orange; break; } - return Icon(iconData, size: 20, color: color); + return AppIcon(iconData, fill: 1, size: 20, color: color); } /// Get summary text for container nodes (shows/seasons) @@ -488,7 +490,7 @@ class _DownloadTreeViewState extends State { if (node.status == DownloadStatus.downloading && widget.onPause != null) IconButton( - icon: const Icon(Icons.pause, size: 20), + icon: const AppIcon(Symbols.pause_rounded, fill: 1, size: 20), onPressed: () => widget.onPause!(globalKey), tooltip: 'Pause', ), @@ -496,7 +498,7 @@ class _DownloadTreeViewState extends State { // Resume button for paused items if (node.status == DownloadStatus.paused && widget.onResume != null) IconButton( - icon: const Icon(Icons.play_arrow, size: 20), + icon: const AppIcon(Symbols.play_arrow_rounded, fill: 1, size: 20), onPressed: () => widget.onResume!(globalKey), tooltip: 'Resume', ), @@ -506,7 +508,7 @@ class _DownloadTreeViewState extends State { node.status == DownloadStatus.queued) && widget.onCancel != null) IconButton( - icon: const Icon(Icons.close, size: 20), + icon: const AppIcon(Symbols.close_rounded, fill: 1, size: 20), onPressed: () => widget.onCancel!(globalKey), tooltip: 'Cancel', ), @@ -514,7 +516,7 @@ class _DownloadTreeViewState extends State { // Retry button for failed items if (node.status == DownloadStatus.failed && widget.onRetry != null) IconButton( - icon: const Icon(Icons.refresh, size: 20), + icon: const AppIcon(Symbols.refresh_rounded, fill: 1, size: 20), onPressed: () => widget.onRetry!(globalKey), tooltip: t.downloads.retryDownload, ), @@ -525,7 +527,7 @@ class _DownloadTreeViewState extends State { node.status == DownloadStatus.cancelled) && widget.onDelete != null) IconButton( - icon: const Icon(Icons.delete, size: 20), + icon: const AppIcon(Symbols.delete_rounded, fill: 1, size: 20), onPressed: () => widget.onDelete!(globalKey), tooltip: 'Delete', ), @@ -538,7 +540,7 @@ class _DownloadTreeViewState extends State { node.status == DownloadStatus.queued) && widget.onPause != null) IconButton( - icon: const Icon(Icons.pause, size: 20), + icon: const AppIcon(Symbols.pause_rounded, fill: 1, size: 20), onPressed: () => _pauseAllChildren(node), tooltip: 'Pause all', ), @@ -546,7 +548,7 @@ class _DownloadTreeViewState extends State { // Resume all button - show if container is paused if (node.status == DownloadStatus.paused && widget.onResume != null) IconButton( - icon: const Icon(Icons.play_arrow, size: 20), + icon: const AppIcon(Symbols.play_arrow_rounded, fill: 1, size: 20), onPressed: () => _resumeAllChildren(node), tooltip: 'Resume all', ), @@ -554,7 +556,7 @@ class _DownloadTreeViewState extends State { // Delete all button if (widget.onDelete != null) IconButton( - icon: const Icon(Icons.delete_sweep, size: 20), + icon: const AppIcon(Symbols.delete_sweep_rounded, fill: 1, size: 20), onPressed: () => _deleteAllChildren(node), tooltip: 'Delete all', ), diff --git a/lib/widgets/file_info_bottom_sheet.dart b/lib/widgets/file_info_bottom_sheet.dart index 333377ad..20daed4a 100644 --- a/lib/widgets/file_info_bottom_sheet.dart +++ b/lib/widgets/file_info_bottom_sheet.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../models/plex_file_info.dart'; import '../i18n/strings.g.dart'; import 'focusable_bottom_sheet.dart'; @@ -56,11 +58,7 @@ class _FileInfoBottomSheetState extends State { padding: const EdgeInsets.all(16), child: Row( children: [ - const Icon( - Icons.info_outline, - color: Colors.white, - size: 24, - ), + const AppIcon(Symbols.info_rounded, fill: 1, color: Colors.white, size: 24), const SizedBox(width: 12), Expanded( child: Text( @@ -74,7 +72,7 @@ class _FileInfoBottomSheetState extends State { ), IconButton( focusNode: _initialFocusNode, - icon: const Icon(Icons.close, color: Colors.white), + icon: const AppIcon(Symbols.close_rounded, fill: 1, color: Colors.white), onPressed: () => Navigator.pop(context), ), ], diff --git a/lib/widgets/focusable_filter_chip.dart b/lib/widgets/focusable_filter_chip.dart index a5919051..9d365bca 100644 --- a/lib/widgets/focusable_filter_chip.dart +++ b/lib/widgets/focusable_filter_chip.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; import 'package:flutter/services.dart'; import '../focus/dpad_navigator.dart'; @@ -126,7 +127,7 @@ class _FocusableFilterChipState extends State child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(icon, size: 16, color: foregroundColor), + AppIcon(icon, fill: 1, size: 16, color: foregroundColor), const SizedBox(width: 6), Text( widget.label, diff --git a/lib/widgets/horizontal_scroll_with_arrows.dart b/lib/widgets/horizontal_scroll_with_arrows.dart index 4b9b1fbd..76fedd84 100644 --- a/lib/widgets/horizontal_scroll_with_arrows.dart +++ b/lib/widgets/horizontal_scroll_with_arrows.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../utils/platform_detector.dart'; /// A wrapper widget that adds hover-activated navigation arrows to horizontal scrolling content. @@ -128,13 +130,13 @@ class _HorizontalScrollWithArrowsState child, _buildArrowButton( position: 8, - icon: Icons.chevron_left, + icon: Symbols.chevron_left_rounded, onPressed: _scrollLeft, canScroll: _canScrollLeft, ), _buildArrowButton( position: -8, - icon: Icons.chevron_right, + icon: Symbols.chevron_right_rounded, onPressed: _scrollRight, canScroll: _canScrollRight, ), @@ -182,7 +184,7 @@ class _NavigationArrowState extends State<_NavigationArrow> { ), ], ), - child: Icon(widget.icon, color: Colors.white, size: 32), + child: AppIcon(widget.icon, fill: 1, color: Colors.white, size: 32), ), ), ); diff --git a/lib/widgets/hub_section.dart b/lib/widgets/hub_section.dart index 816a31a0..2ef69f9b 100644 --- a/lib/widgets/hub_section.dart +++ b/lib/widgets/hub_section.dart @@ -1,4 +1,6 @@ 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 '../focus/dpad_navigator.dart'; import '../focus/locked_hub_controller.dart'; @@ -273,7 +275,7 @@ class HubSectionState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(widget.icon), + AppIcon(widget.icon, fill: 1), const SizedBox(width: 8), Expanded( child: Text( @@ -285,7 +287,7 @@ class HubSectionState extends State { ), if (widget.hub.more) ...[ const SizedBox(width: 4), - const Icon(Icons.chevron_right, size: 20), + const AppIcon(Symbols.chevron_right_rounded, fill: 1, size: 20), ], ], ), diff --git a/lib/widgets/media_card.dart b/lib/widgets/media_card.dart index 0e4c34fb..80a33746 100644 --- a/lib/widgets/media_card.dart +++ b/lib/widgets/media_card.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import '../../services/plex_client.dart'; import '../models/plex_metadata.dart'; @@ -686,11 +688,11 @@ Widget _buildPosterImage( String? localPosterPath, }) { String? posterUrl; - IconData fallbackIcon = Icons.movie; + IconData fallbackIcon = Symbols.movie_rounded; if (item is PlexPlaylist) { posterUrl = item.displayImage; - fallbackIcon = Icons.playlist_play; + fallbackIcon = Symbols.playlist_play_rounded; return PlexOptimizedImage.playlist( client: isOffline ? null : _getClientForItem(context, item), @@ -715,7 +717,7 @@ Widget _buildPosterImage( } return SkeletonLoader( - child: Center(child: Icon(fallbackIcon, size: 40, color: Colors.white54)), + child: Center(child: AppIcon(fallbackIcon, fill: 1, size: 40, color: Colors.white54)), ); } @@ -838,7 +840,7 @@ class _MediaCardHelpers { ), ], ), - child: Icon(Icons.check, 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 diff --git a/lib/widgets/media_context_menu.dart b/lib/widgets/media_context_menu.dart index bdb9feb9..6658715f 100644 --- a/lib/widgets/media_context_menu.dart +++ b/lib/widgets/media_context_menu.dart @@ -1,5 +1,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import '../services/plex_client.dart'; import '../services/play_queue_launcher.dart'; @@ -152,7 +154,7 @@ class MediaContextMenuState extends State { menuActions.add( _MenuAction( value: 'play', - icon: Icons.play_arrow, + icon: Symbols.play_arrow_rounded, label: t.discover.play, ), ); @@ -161,7 +163,7 @@ class MediaContextMenuState extends State { menuActions.add( _MenuAction( value: 'shuffle', - icon: Icons.shuffle, + icon: Symbols.shuffle_rounded, label: t.mediaMenu.shufflePlay, ), ); @@ -170,7 +172,7 @@ class MediaContextMenuState extends State { menuActions.add( _MenuAction( value: 'delete', - icon: Icons.delete, + icon: Symbols.delete_rounded, label: t.common.delete, ), ); @@ -184,7 +186,7 @@ class MediaContextMenuState extends State { menuActions.add( _MenuAction( value: 'watch', - icon: Icons.check_circle_outline, + icon: Symbols.check_circle_outline_rounded, label: t.mediaMenu.markAsWatched, ), ); @@ -195,7 +197,7 @@ class MediaContextMenuState extends State { menuActions.add( _MenuAction( value: 'unwatch', - icon: Icons.remove_circle_outline, + icon: Symbols.remove_circle_outline_rounded, label: t.mediaMenu.markAsUnwatched, ), ); @@ -206,7 +208,7 @@ class MediaContextMenuState extends State { menuActions.add( _MenuAction( value: 'remove_from_continue_watching', - icon: Icons.close, + icon: Symbols.close_rounded, label: t.mediaMenu.removeFromContinueWatching, ), ); @@ -217,7 +219,7 @@ class MediaContextMenuState extends State { menuActions.add( _MenuAction( value: 'remove_from_collection', - icon: Icons.delete_outline, + icon: Symbols.delete_outline_rounded, label: t.collections.removeFromCollection, ), ); @@ -229,7 +231,7 @@ class MediaContextMenuState extends State { menuActions.add( _MenuAction( value: 'series', - icon: Icons.tv, + icon: Symbols.tv_rounded, label: t.mediaMenu.goToSeries, ), ); @@ -240,7 +242,7 @@ class MediaContextMenuState extends State { menuActions.add( _MenuAction( value: 'season', - icon: Icons.playlist_play, + icon: Symbols.playlist_play_rounded, label: t.mediaMenu.goToSeason, ), ); @@ -251,7 +253,7 @@ class MediaContextMenuState extends State { menuActions.add( _MenuAction( value: 'shuffle_play', - icon: Icons.shuffle, + icon: Symbols.shuffle_rounded, label: t.mediaMenu.shufflePlay, ), ); @@ -262,7 +264,7 @@ class MediaContextMenuState extends State { menuActions.add( _MenuAction( value: 'fileinfo', - icon: Icons.info_outline, + icon: Symbols.info_rounded, label: t.mediaMenu.fileInfo, ), ); @@ -285,7 +287,7 @@ class MediaContextMenuState extends State { menuActions.add( _MenuAction( value: 'delete_download', - icon: Icons.delete, + icon: Symbols.delete_rounded, label: t.downloads.deleteDownload, ), ); @@ -294,7 +296,7 @@ class MediaContextMenuState extends State { menuActions.add( _MenuAction( value: 'download', - icon: Icons.download, + icon: Symbols.download_rounded, label: t.downloads.downloadNow, ), ); @@ -307,7 +309,7 @@ class MediaContextMenuState extends State { itemType == 'show' || itemType == 'season') { menuActions.add( - _MenuAction(value: 'add_to', icon: Icons.add, label: t.common.addTo), + _MenuAction(value: 'add_to', icon: Symbols.add_rounded, label: t.common.addTo), ); } } // End of regular menu items else block @@ -626,12 +628,12 @@ class MediaContextMenuState extends State { final submenuActions = [ _MenuAction( value: 'playlist', - icon: Icons.playlist_play, + icon: Symbols.playlist_play_rounded, label: t.playlists.playlist, ), _MenuAction( value: 'collection', - icon: Icons.collections, + icon: Symbols.collections_rounded, label: t.collections.collection, ), ]; @@ -656,7 +658,7 @@ class MediaContextMenuState extends State { ), ...submenuActions.map((action) { return ListTile( - leading: Icon(action.icon), + leading: AppIcon(action.icon, fill: 1), title: Text(action.label), onTap: () => Navigator.pop(context, action.value), ); @@ -682,7 +684,7 @@ class MediaContextMenuState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(action.icon, size: 20), + AppIcon(action.icon, fill: 1, size: 20), const SizedBox(width: 12), Text(action.label), ], @@ -1354,7 +1356,7 @@ class _PlaylistSelectionDialog extends StatelessWidget { if (index == 0) { // Create new playlist option (always shown first) return ListTile( - leading: const Icon(Icons.add), + leading: const AppIcon(Symbols.add_rounded, fill: 1), title: Text(t.playlists.createNewPlaylist), onTap: () => Navigator.pop(context, '_create_new'), ); @@ -1363,8 +1365,8 @@ class _PlaylistSelectionDialog extends StatelessWidget { final playlist = playlists[index - 1]; return ListTile( leading: playlist.smart - ? const Icon(Icons.auto_awesome) - : const Icon(Icons.playlist_play), + ? const AppIcon(Symbols.auto_awesome_rounded, fill: 1) + : const AppIcon(Symbols.playlist_play_rounded, fill: 1), title: Text(playlist.title), subtitle: playlist.leafCount != null ? Text( @@ -1460,7 +1462,7 @@ class _CollectionSelectionDialog extends StatelessWidget { if (index == 0) { // Create new collection option (always shown first) return ListTile( - leading: const Icon(Icons.add), + leading: const AppIcon(Symbols.add_rounded, fill: 1), title: Text(t.collections.createNewCollection), onTap: () => Navigator.pop(context, '_create_new'), ); @@ -1468,7 +1470,7 @@ class _CollectionSelectionDialog extends StatelessWidget { final collection = collections[index - 1]; return ListTile( - leading: const Icon(Icons.collections), + leading: const AppIcon(Symbols.collections_rounded, fill: 1), title: Text(collection.title), subtitle: collection.childCount != null ? Text('${collection.childCount} items') @@ -1594,7 +1596,7 @@ class _FocusableContextMenuSheetState final action = entry.value; return FocusableListTile( focusNode: index == 0 ? _initialFocusNode : null, - leading: Icon(action.icon), + leading: AppIcon(action.icon, fill: 1), title: Text(action.label), onTap: () => Navigator.pop(context, action.value), ); @@ -1695,7 +1697,7 @@ class _FocusablePopupMenuState extends State<_FocusablePopupMenu> { final action = entry.value; return FocusableListTile( focusNode: index == 0 ? _initialFocusNode : null, - leading: Icon(action.icon, size: 20), + leading: AppIcon(action.icon, fill: 1, size: 20), title: Text(action.label), onTap: () => Navigator.pop(context, action.value), ); diff --git a/lib/widgets/plex_optimized_image.dart b/lib/widgets/plex_optimized_image.dart index 72b854cb..de1deea3 100644 --- a/lib/widgets/plex_optimized_image.dart +++ b/lib/widgets/plex_optimized_image.dart @@ -1,5 +1,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:cached_network_image/cached_network_image.dart'; import '../../services/plex_client.dart'; import '../utils/plex_image_helper.dart'; @@ -144,7 +146,7 @@ class PlexOptimizedImage extends StatelessWidget { enableTranscoding: enableTranscoding, cacheKey: cacheKey, alignment: alignment, - fallbackIcon: Icons.movie, + fallbackIcon: Symbols.movie_rounded, imageType: ImageType.poster, localFilePath: localFilePath, ); @@ -178,7 +180,7 @@ class PlexOptimizedImage extends StatelessWidget { enableTranscoding: enableTranscoding, cacheKey: cacheKey, alignment: alignment, - fallbackIcon: Icons.video_library, + fallbackIcon: Symbols.video_library_rounded, imageType: ImageType.thumb, localFilePath: localFilePath, ); @@ -212,7 +214,7 @@ class PlexOptimizedImage extends StatelessWidget { enableTranscoding: enableTranscoding, cacheKey: cacheKey, alignment: alignment, - fallbackIcon: Icons.playlist_play, + fallbackIcon: Symbols.playlist_play_rounded, imageType: ImageType.poster, localFilePath: localFilePath, ); @@ -334,7 +336,7 @@ class PlexOptimizedImage extends StatelessWidget { Widget _buildPlaceholder(BuildContext context) { return SkeletonLoader( child: fallbackIcon != null - ? Center(child: Icon(fallbackIcon!, size: 40, color: Colors.white54)) + ? Center(child: AppIcon(fallbackIcon!, fill: 1, size: 40, color: Colors.white54)) : null, ); } @@ -343,11 +345,7 @@ class PlexOptimizedImage extends StatelessWidget { return Container( color: Theme.of(context).colorScheme.surfaceContainerHighest, child: Center( - child: Icon( - fallbackIcon ?? Icons.broken_image, - 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), ), ); } @@ -358,11 +356,7 @@ class PlexOptimizedImage extends StatelessWidget { height: height, color: Theme.of(context).colorScheme.surfaceContainerHighest, child: Center( - child: Icon( - fallbackIcon ?? Icons.image_not_supported, - 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), ), ); } diff --git a/lib/widgets/side_navigation_rail.dart b/lib/widgets/side_navigation_rail.dart index d90b3d3e..80fd8ef7 100644 --- a/lib/widgets/side_navigation_rail.dart +++ b/lib/widgets/side_navigation_rail.dart @@ -1,6 +1,8 @@ import 'dart:io' show Platform; 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:provider/provider.dart'; @@ -78,11 +80,7 @@ class NavigationRailItem extends StatelessWidget { ), child: Row( children: [ - Icon( - isSelected && selectedIcon != null ? selectedIcon! : icon, - 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), ], @@ -304,30 +302,30 @@ class SideNavigationRailState extends State { IconData _getLibraryIcon(String type) { switch (type.toLowerCase()) { case 'movie': - return Icons.movie_outlined; + return Symbols.movie_rounded; case 'show': - return Icons.tv_outlined; + return Symbols.tv_rounded; case 'artist': - return Icons.music_note_outlined; + return Symbols.music_note_rounded; case 'photo': - return Icons.photo_outlined; + return Symbols.photo_rounded; default: - return Icons.folder_outlined; + return Symbols.folder_rounded; } } IconData _getLibraryIconFilled(String type) { switch (type.toLowerCase()) { case 'movie': - return Icons.movie; + return Symbols.movie_rounded; case 'show': - return Icons.tv; + return Symbols.tv_rounded; case 'artist': - return Icons.music_note; + return Symbols.music_note_rounded; case 'photo': - return Icons.photo; + return Symbols.photo_rounded; default: - return Icons.folder; + return Symbols.folder_rounded; } } @@ -379,8 +377,8 @@ class SideNavigationRailState extends State { if (!widget.isOfflineMode) ...[ // Home _buildNavItem( - icon: Icons.home_outlined, - selectedIcon: Icons.home, + icon: Symbols.home_rounded, + selectedIcon: Symbols.home_rounded, label: Translations.of(context).navigation.home, isSelected: widget.selectedIndex == 0, isFocused: _isHomeFocused, @@ -397,8 +395,8 @@ class SideNavigationRailState extends State { // Search _buildNavItem( - icon: Icons.search, - selectedIcon: Icons.search, + icon: Symbols.search_rounded, + selectedIcon: Symbols.search_rounded, label: Translations.of(context).navigation.search, isSelected: widget.selectedIndex == 2, isFocused: _isSearchFocused, @@ -411,8 +409,8 @@ class SideNavigationRailState extends State { // Downloads (index 0 in offline mode, 3 in online mode) _buildNavItem( - icon: Icons.download_outlined, - selectedIcon: Icons.download, + icon: Symbols.download_rounded, + selectedIcon: Symbols.download_rounded, label: Translations.of(context).navigation.downloads, isSelected: widget.isOfflineMode ? widget.selectedIndex == 0 @@ -428,8 +426,8 @@ class SideNavigationRailState extends State { // Settings (index 1 in offline mode, 4 in online mode) _buildNavItem( - icon: Icons.settings_outlined, - selectedIcon: Icons.settings, + icon: Symbols.settings_rounded, + selectedIcon: Symbols.settings_rounded, label: Translations.of(context).navigation.settings, isSelected: widget.isOfflineMode ? widget.selectedIndex == 1 @@ -525,13 +523,7 @@ class SideNavigationRailState extends State { ), child: Row( children: [ - Icon( - widget.selectedIndex == 1 - ? Icons.video_library - : Icons.video_library_outlined, - 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( @@ -547,13 +539,7 @@ class SideNavigationRailState extends State { ), ), ), - Icon( - _librariesExpanded - ? Icons.expand_less - : Icons.expand_more, - size: 20, - color: t.textMuted, - ), + AppIcon(_librariesExpanded ? Symbols.expand_less_rounded : Symbols.expand_more_rounded, fill: 1, size: 20, color: t.textMuted), ], ), ), diff --git a/lib/widgets/video_controls/desktop_video_controls.dart b/lib/widgets/video_controls/desktop_video_controls.dart index 8cbd8842..f7ed47d2 100644 --- a/lib/widgets/video_controls/desktop_video_controls.dart +++ b/lib/widgets/video_controls/desktop_video_controls.dart @@ -1,6 +1,8 @@ import 'dart:io' show Platform; 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 '../../mpv/mpv.dart'; @@ -390,7 +392,7 @@ class DesktopVideoControlsState extends State { _buildFocusableButton( focusNode: _prevItemFocusNode, index: 0, - icon: Icons.skip_previous, + icon: Symbols.skip_previous_rounded, color: widget.onPrevious != null ? Colors.white : Colors.white54, @@ -403,7 +405,7 @@ class DesktopVideoControlsState extends State { index: 1, icon: widget.chapters.isEmpty ? widget.getReplayIcon(widget.seekTimeSmall) - : Icons.fast_rewind, + : Symbols.fast_rewind_rounded, onPressed: widget.onSeekToPreviousChapter, semanticLabel: widget.chapters.isEmpty ? t.videoControls.seekBackwardButton( @@ -420,7 +422,7 @@ class DesktopVideoControlsState extends State { return _buildFocusableButton( focusNode: _playPauseFocusNode, index: 2, - icon: isPlaying ? Icons.pause : Icons.play_arrow, + icon: isPlaying ? Symbols.pause_rounded : Symbols.play_arrow_rounded, iconSize: 32, onPressed: () { if (isPlaying) { @@ -441,7 +443,7 @@ class DesktopVideoControlsState extends State { index: 3, icon: widget.chapters.isEmpty ? widget.getForwardIcon(widget.seekTimeSmall) - : Icons.fast_forward, + : Symbols.fast_forward_rounded, onPressed: widget.onSeekToNextChapter, semanticLabel: widget.chapters.isEmpty ? t.videoControls.seekForwardButton( @@ -453,7 +455,7 @@ class DesktopVideoControlsState extends State { _buildFocusableButton( focusNode: _nextItemFocusNode, index: 4, - icon: Icons.skip_next, + icon: Symbols.skip_next_rounded, color: widget.onNext != null ? Colors.white : Colors.white54, onPressed: widget.onNext, semanticLabel: t.videoControls.nextButton, @@ -523,7 +525,7 @@ class DesktopVideoControlsState extends State { button: true, excludeSemantics: true, child: IconButton( - icon: Icon(icon, color: color, size: iconSize), + icon: AppIcon(icon, fill: 1, color: color, size: iconSize), iconSize: iconSize, onPressed: onPressed, ), diff --git a/lib/widgets/video_controls/helpers/track_selection_helper.dart b/lib/widgets/video_controls/helpers/track_selection_helper.dart index 2c8d6d45..c78af99d 100644 --- a/lib/widgets/video_controls/helpers/track_selection_helper.dart +++ b/lib/widgets/video_controls/helpers/track_selection_helper.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../../mpv/mpv.dart'; import '../../../widgets/focusable_list_tile.dart'; @@ -85,7 +87,7 @@ class TrackSelectionHelper { label, style: TextStyle(color: isSelected ? Colors.blue : Colors.white), ), - trailing: isSelected ? const Icon(Icons.check, color: Colors.blue) : null, + trailing: isSelected ? const AppIcon(Symbols.check_rounded, fill: 1, color: Colors.blue) : null, onTap: onTap, ); } diff --git a/lib/widgets/video_controls/mobile_video_controls.dart b/lib/widgets/video_controls/mobile_video_controls.dart index 19a7bb0a..bb8e154e 100644 --- a/lib/widgets/video_controls/mobile_video_controls.dart +++ b/lib/widgets/video_controls/mobile_video_controls.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../mpv/mpv.dart'; import '../../models/plex_media_info.dart'; @@ -101,7 +103,7 @@ class MobileVideoControls extends StatelessWidget { semanticLabel: isPlaying ? t.videoControls.pauseButton : t.videoControls.playButton, - icon: isPlaying ? Icons.pause : Icons.play_arrow, + icon: isPlaying ? Symbols.pause_rounded : Symbols.play_arrow_rounded, iconSize: 72, onPressed: () { if (isPlaying) { @@ -164,7 +166,7 @@ class MobileVideoControls extends StatelessWidget { button: true, excludeSemantics: true, child: IconButton( - icon: Icon(icon, color: Colors.white, size: iconSize), + icon: AppIcon(icon, fill: 1, color: Colors.white, size: iconSize), iconSize: iconSize, onPressed: onPressed, ), diff --git a/lib/widgets/video_controls/sheets/audio_track_sheet.dart b/lib/widgets/video_controls/sheets/audio_track_sheet.dart index 8aaf547b..e35326d6 100644 --- a/lib/widgets/video_controls/sheets/audio_track_sheet.dart +++ b/lib/widgets/video_controls/sheets/audio_track_sheet.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../../mpv/mpv.dart'; import '../../../i18n/strings.g.dart'; @@ -17,7 +18,7 @@ class AudioTrackSheet { context: context, player: player, title: t.videoControls.audioLabel, - icon: Icons.audiotrack, + icon: Symbols.audiotrack_rounded, extractTracks: (tracks) => tracks?.audio ?? [], getCurrentTrack: (track) => track.audio, buildLabel: (audioTrack, index) { diff --git a/lib/widgets/video_controls/sheets/chapter_sheet.dart b/lib/widgets/video_controls/sheets/chapter_sheet.dart index 34ec8ef7..42214ac3 100644 --- a/lib/widgets/video_controls/sheets/chapter_sheet.dart +++ b/lib/widgets/video_controls/sheets/chapter_sheet.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../../mpv/mpv.dart'; import '../../../services/plex_client.dart'; @@ -147,11 +149,7 @@ class _ChapterSheetState extends State { height: 34, fit: BoxFit.cover, errorWidget: (context, url, error) => - const Icon( - Icons.image, - color: Colors.white54, - size: 34, - ), + const AppIcon(Symbols.image_rounded, fill: 1, color: Colors.white54, size: 34), ), ), if (isCurrentChapter) @@ -188,7 +186,7 @@ class _ChapterSheetState extends State { ), ), trailing: isCurrentChapter - ? const Icon(Icons.play_circle_filled, color: Colors.blue) + ? const AppIcon(Symbols.play_circle_rounded, fill: 1, color: Colors.blue) : null, onTap: () { widget.player.seek(chapter.startTime); @@ -201,7 +199,7 @@ class _ChapterSheetState extends State { return BaseVideoControlSheet( title: 'Chapters', - icon: Icons.video_library, + icon: Symbols.video_library_rounded, child: content, ); }, diff --git a/lib/widgets/video_controls/sheets/subtitle_track_sheet.dart b/lib/widgets/video_controls/sheets/subtitle_track_sheet.dart index 0d7fb806..ec4409ca 100644 --- a/lib/widgets/video_controls/sheets/subtitle_track_sheet.dart +++ b/lib/widgets/video_controls/sheets/subtitle_track_sheet.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../../mpv/mpv.dart'; import '../../../i18n/strings.g.dart'; @@ -17,7 +18,7 @@ class SubtitleTrackSheet { context: context, player: player, title: t.videoControls.subtitlesLabel, - icon: Icons.subtitles, + icon: Symbols.subtitles_rounded, extractTracks: (tracks) => tracks?.subtitle ?? [], getCurrentTrack: (track) => track.subtitle, buildLabel: (subtitle, index) { diff --git a/lib/widgets/video_controls/sheets/version_sheet.dart b/lib/widgets/video_controls/sheets/version_sheet.dart index b21a59eb..845f9d87 100644 --- a/lib/widgets/video_controls/sheets/version_sheet.dart +++ b/lib/widgets/video_controls/sheets/version_sheet.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../../models/plex_media_version.dart'; import '../../../widgets/focusable_bottom_sheet.dart'; import '../../../widgets/focusable_list_tile.dart'; @@ -63,7 +65,7 @@ class _VersionSheetState extends State { initialFocusNode: _initialFocusNode, child: BaseVideoControlSheet( title: 'Video Version', - icon: Icons.video_file, + icon: Symbols.video_file_rounded, child: ListView.builder( itemCount: widget.availableVersions.length, itemBuilder: (context, index) { @@ -79,7 +81,7 @@ class _VersionSheetState extends State { ), ), trailing: isSelected - ? const Icon(Icons.check, color: Colors.blue) + ? const AppIcon(Symbols.check_rounded, fill: 1, color: Colors.blue) : null, onTap: () { Navigator.pop(context); diff --git a/lib/widgets/video_controls/sheets/video_settings_sheet.dart b/lib/widgets/video_controls/sheets/video_settings_sheet.dart index 2b0b77a6..70e22b66 100644 --- a/lib/widgets/video_controls/sheets/video_settings_sheet.dart +++ b/lib/widgets/video_controls/sheets/video_settings_sheet.dart @@ -1,6 +1,8 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../../mpv/mpv.dart'; import '../../../services/settings_service.dart'; @@ -49,14 +51,14 @@ class _SettingsMenuItem extends StatelessWidget { return FocusableListTile( focusNode: focusNode, - leading: Icon(icon, 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 Icon(Icons.chevron_right, color: Colors.white70), + const AppIcon(Symbols.chevron_right_rounded, fill: 1, color: Colors.white70), ], ), onTap: onTap, @@ -173,17 +175,17 @@ class _VideoSettingsSheetState extends State { IconData _getIcon() { switch (_currentView) { case _SettingsView.menu: - return Icons.tune; + return Symbols.tune_rounded; case _SettingsView.speed: - return Icons.speed; + return Symbols.speed_rounded; case _SettingsView.sleep: - return Icons.bedtime; + return Symbols.bedtime_rounded; case _SettingsView.audioSync: - return Icons.sync; + return Symbols.sync_rounded; case _SettingsView.subtitleSync: - return Icons.subtitles; + return Symbols.subtitles_rounded; case _SettingsView.audioDevice: - return Icons.speaker; + return Symbols.speaker_rounded; } } @@ -213,7 +215,7 @@ class _VideoSettingsSheetState extends State { final currentRate = snapshot.data ?? 1.0; return _SettingsMenuItem( focusNode: _initialFocusNode, - icon: Icons.speed, + icon: Symbols.speed_rounded, title: 'Playback Speed', valueText: _formatSpeed(currentRate), onTap: () => _navigateTo(_SettingsView.speed), @@ -227,7 +229,7 @@ class _VideoSettingsSheetState extends State { builder: (context, _) { final isActive = sleepTimer.isActive; return _SettingsMenuItem( - icon: isActive ? Icons.bedtime : Icons.bedtime_outlined, + icon: isActive ? Symbols.bedtime_rounded : Symbols.bedtime_rounded, title: 'Sleep Timer', valueText: _formatSleepTimer(sleepTimer), isHighlighted: isActive, @@ -238,7 +240,7 @@ class _VideoSettingsSheetState extends State { // Audio Sync _SettingsMenuItem( - icon: Icons.sync, + icon: Symbols.sync_rounded, title: 'Audio Sync', valueText: formatSyncOffset(_audioSyncOffset.toDouble()), isHighlighted: _audioSyncOffset != 0, @@ -247,7 +249,7 @@ class _VideoSettingsSheetState extends State { // Subtitle Sync _SettingsMenuItem( - icon: Icons.subtitles, + icon: Symbols.subtitles_rounded, title: 'Subtitle Sync', valueText: formatSyncOffset(_subtitleSyncOffset.toDouble()), isHighlighted: _subtitleSyncOffset != 0, @@ -257,10 +259,7 @@ class _VideoSettingsSheetState extends State { // HDR Toggle (iOS, macOS, and Windows) if (Platform.isIOS || Platform.isMacOS || Platform.isWindows) ListTile( - leading: Icon( - Icons.hdr_strong, - 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, @@ -283,7 +282,7 @@ class _VideoSettingsSheetState extends State { : '${currentDevice.name} · ${currentDevice.description}'; return _SettingsMenuItem( - icon: Icons.speaker, + icon: Symbols.speaker_rounded, title: 'Audio Output', valueText: deviceLabel, allowValueOverflow: true, @@ -320,7 +319,7 @@ class _VideoSettingsSheetState extends State { ), ), trailing: isSelected - ? const Icon(Icons.check, color: Colors.blue) + ? const AppIcon(Symbols.check_rounded, fill: 1, color: Colors.blue) : null, onTap: () { widget.player.setRate(speed); @@ -406,7 +405,7 @@ class _VideoSettingsSheetState extends State { ), ), trailing: isSelected - ? const Icon(Icons.check, color: Colors.blue) + ? const AppIcon(Symbols.check_rounded, fill: 1, color: Colors.blue) : null, onTap: () { widget.player.setAudioDevice(device); diff --git a/lib/widgets/video_controls/sheets/video_sheet_header.dart b/lib/widgets/video_controls/sheets/video_sheet_header.dart index c5170e0d..d25eda87 100644 --- a/lib/widgets/video_controls/sheets/video_sheet_header.dart +++ b/lib/widgets/video_controls/sheets/video_sheet_header.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; /// Shared header widget for video control sheets /// @@ -28,11 +30,11 @@ class VideoSheetHeader extends StatelessWidget { // Back button or icon if (onBack != null) IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), + icon: const AppIcon(Symbols.arrow_back_rounded, fill: 1, color: Colors.white), onPressed: onBack, ) else if (icon != null) - Icon(icon, color: iconColor ?? Colors.white), + AppIcon(icon, fill: 1, color: iconColor ?? Colors.white), const SizedBox(width: 12), Text( title, @@ -44,7 +46,7 @@ class VideoSheetHeader extends StatelessWidget { ), const Spacer(), IconButton( - icon: const Icon(Icons.close, color: Colors.white), + icon: const AppIcon(Symbols.close_rounded, fill: 1, color: Colors.white), onPressed: onClose ?? () => Navigator.pop(context), ), ], diff --git a/lib/widgets/video_controls/video_control_button.dart b/lib/widgets/video_controls/video_control_button.dart index fe5c508a..82194561 100644 --- a/lib/widgets/video_controls/video_control_button.dart +++ b/lib/widgets/video_controls/video_control_button.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; import 'package:flutter/services.dart'; import '../../focus/focusable_wrapper.dart'; @@ -62,7 +63,7 @@ class VideoControlButton extends StatelessWidget { final effectiveColor = color ?? (isActive ? Colors.amber : Colors.white); final button = IconButton( - icon: Icon(icon, color: effectiveColor), + icon: AppIcon(icon, fill: 1, color: effectiveColor), onPressed: onPressed, tooltip: tooltip, constraints: const BoxConstraints(minWidth: 40, minHeight: 40), diff --git a/lib/widgets/video_controls/video_controls.dart b/lib/widgets/video_controls/video_controls.dart index ae2045ef..56133084 100644 --- a/lib/widgets/video_controls/video_controls.dart +++ b/lib/widgets/video_controls/video_controls.dart @@ -2,6 +2,8 @@ import 'dart:async' show StreamSubscription, Timer; import 'dart:io' show Platform; import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:rate_limiter/rate_limiter.dart'; import 'package:flutter/services.dart' show @@ -795,13 +797,9 @@ class _PlexVideoControlsState extends State color: Colors.black.withValues(alpha: 0.6), shape: BoxShape.circle, ), - child: Icon( - _lastDoubleTapWasForward + child: AppIcon(_lastDoubleTapWasForward ? getForwardIcon(_seekTimeSmall) - : getReplayIcon(_seekTimeSmall), - color: Colors.white, - size: 48, - ), + : getReplayIcon(_seekTimeSmall), fill: 1, color: Colors.white, size: 48), ), ); } @@ -1243,8 +1241,8 @@ class _PlexVideoControlsState extends State ? '$baseButtonText ($remainingSeconds)' : baseButtonText; final IconData buttonIcon = showNextEpisode - ? Icons.skip_next - : Icons.fast_forward; + ? Symbols.skip_next_rounded + : Symbols.fast_forward_rounded; return Material( color: Colors.transparent, @@ -1284,7 +1282,7 @@ class _PlexVideoControlsState extends State ), ), const SizedBox(width: 8), - Icon(buttonIcon, color: Colors.black, size: 20), + AppIcon(buttonIcon, fill: 1, color: Colors.black, size: 20), ], ), ), diff --git a/lib/widgets/video_controls/widgets/sleep_timer_active_status.dart b/lib/widgets/video_controls/widgets/sleep_timer_active_status.dart index fde98207..a9edf491 100644 --- a/lib/widgets/video_controls/widgets/sleep_timer_active_status.dart +++ b/lib/widgets/video_controls/widgets/sleep_timer_active_status.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../../services/sleep_timer_service.dart'; import '../../../i18n/strings.g.dart'; import '../../../utils/duration_formatter.dart'; @@ -43,7 +45,7 @@ class SleepTimerActiveStatus extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ OutlinedButton.icon( - icon: const Icon(Icons.add), + icon: const AppIcon(Symbols.add_rounded, fill: 1), label: Text( t.videoControls.addTime(amount: "15", unit: " min"), ), @@ -57,7 +59,7 @@ class SleepTimerActiveStatus extends StatelessWidget { ), const SizedBox(width: 12), FilledButton.icon( - icon: const Icon(Icons.cancel), + icon: const AppIcon(Symbols.cancel_rounded, fill: 1), label: Text(t.common.cancel), style: FilledButton.styleFrom(backgroundColor: Colors.red), onPressed: () { diff --git a/lib/widgets/video_controls/widgets/sleep_timer_duration_list.dart b/lib/widgets/video_controls/widgets/sleep_timer_duration_list.dart index 7d74e2a5..167455fa 100644 --- a/lib/widgets/video_controls/widgets/sleep_timer_duration_list.dart +++ b/lib/widgets/video_controls/widgets/sleep_timer_duration_list.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../../mpv/mpv.dart'; import '../../../services/sleep_timer_service.dart'; @@ -37,7 +39,7 @@ class SleepTimerDurationList extends StatelessWidget { ); return ListTile( - leading: const Icon(Icons.timer, color: Colors.white70), + leading: const AppIcon(Symbols.timer_rounded, fill: 1, color: Colors.white70), title: Text( label, style: const TextStyle( diff --git a/lib/widgets/video_controls/widgets/sync_offset_control.dart b/lib/widgets/video_controls/widgets/sync_offset_control.dart index b1ce5eb6..3c56f55f 100644 --- a/lib/widgets/video_controls/widgets/sync_offset_control.dart +++ b/lib/widgets/video_controls/widgets/sync_offset_control.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:plezy/widgets/app_icon.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../../../mpv/mpv.dart'; import '../../../i18n/strings.g.dart'; @@ -132,7 +134,7 @@ class _SyncOffsetControlState extends State { // Reset button ElevatedButton.icon( onPressed: _currentOffset != 0 ? _resetOffset : null, - icon: const Icon(Icons.restart_alt), + icon: const AppIcon(Symbols.restart_alt_rounded, fill: 1), label: Text(t.videoControls.resetToZero), style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[800], diff --git a/lib/widgets/video_controls/widgets/track_chapter_controls.dart b/lib/widgets/video_controls/widgets/track_chapter_controls.dart index bd1ca932..1ed5eb04 100644 --- a/lib/widgets/video_controls/widgets/track_chapter_controls.dart +++ b/lib/widgets/video_controls/widgets/track_chapter_controls.dart @@ -1,6 +1,7 @@ import 'dart:io' show Platform; import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:flutter/services.dart'; import '../../../mpv/mpv.dart'; @@ -143,7 +144,7 @@ class TrackChapterControls extends StatelessWidget { subtitleSyncOffset != 0; final currentIndex = 0; return VideoControlButton( - icon: Icons.tune, + icon: Symbols.tune_rounded, isActive: isActive, semanticLabel: t.videoControls.settingsButton, focusNode: focusNodes != null && focusNodes!.isNotEmpty @@ -180,7 +181,7 @@ class TrackChapterControls extends StatelessWidget { final currentIndex = buttonIndex; buttons.add( VideoControlButton( - icon: Icons.audiotrack, + icon: Symbols.audiotrack_rounded, semanticLabel: t.videoControls.audioTrackButton, focusNode: focusNodes != null && focusNodes!.length > currentIndex ? focusNodes![currentIndex] @@ -211,7 +212,7 @@ class TrackChapterControls extends StatelessWidget { final currentIndex = buttonIndex; buttons.add( VideoControlButton( - icon: Icons.subtitles, + icon: Symbols.subtitles_rounded, semanticLabel: t.videoControls.subtitlesButton, focusNode: focusNodes != null && focusNodes!.length > currentIndex ? focusNodes![currentIndex] @@ -242,7 +243,7 @@ class TrackChapterControls extends StatelessWidget { final currentIndex = buttonIndex; buttons.add( VideoControlButton( - icon: Icons.video_library, + icon: Symbols.video_library_rounded, semanticLabel: t.videoControls.chaptersButton, focusNode: focusNodes != null && focusNodes!.length > currentIndex ? focusNodes![currentIndex] @@ -275,7 +276,7 @@ class TrackChapterControls extends StatelessWidget { final currentIndex = buttonIndex; buttons.add( VideoControlButton( - icon: Icons.video_file, + icon: Symbols.video_file_rounded, semanticLabel: t.videoControls.versionsButton, focusNode: focusNodes != null && focusNodes!.length > currentIndex ? focusNodes![currentIndex] @@ -334,8 +335,8 @@ class TrackChapterControls extends StatelessWidget { buttons.add( VideoControlButton( icon: isRotationLocked - ? Icons.screen_lock_rotation - : Icons.screen_rotation, + ? Symbols.screen_lock_rotation_rounded + : Symbols.screen_rotation_rounded, tooltip: isRotationLocked ? t.videoControls.unlockRotation : t.videoControls.lockRotation, @@ -363,7 +364,7 @@ class TrackChapterControls extends StatelessWidget { final currentIndex = buttonIndex; buttons.add( VideoControlButton( - icon: isFullscreen ? Icons.fullscreen_exit : Icons.fullscreen, + icon: isFullscreen ? Symbols.fullscreen_exit_rounded : Symbols.fullscreen_rounded, semanticLabel: isFullscreen ? t.videoControls.exitFullscreenButton : t.videoControls.fullscreenButton, @@ -421,13 +422,13 @@ class TrackChapterControls extends StatelessWidget { IconData _getBoxFitIcon(int mode) { switch (mode) { case 0: - return Icons.fit_screen; // contain (letterbox) + return Symbols.fit_screen_rounded; // contain (letterbox) case 1: - return Icons.aspect_ratio; // cover (fill screen) + return Symbols.aspect_ratio_rounded; // cover (fill screen) case 2: - return Icons.settings_overscan; // fill (stretch) + return Symbols.settings_overscan_rounded; // fill (stretch) default: - return Icons.fit_screen; + return Symbols.fit_screen_rounded; } } diff --git a/lib/widgets/video_controls/widgets/volume_control.dart b/lib/widgets/video_controls/widgets/volume_control.dart index f7650608..7de6aff6 100644 --- a/lib/widgets/video_controls/widgets/volume_control.dart +++ b/lib/widgets/video_controls/widgets/volume_control.dart @@ -1,4 +1,6 @@ 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 '../../../mpv/mpv.dart'; @@ -147,10 +149,7 @@ class _VolumeControlState extends State { button: true, excludeSemantics: true, child: IconButton( - icon: Icon( - isMuted ? Icons.volume_off : Icons.volume_up, - 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); diff --git a/pubspec.lock b/pubspec.lock index f1207683..4b8bf258 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -153,6 +153,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + chalkdart: + dependency: transitive + description: + name: chalkdart + sha256: "7ffc6bd39c81453fb9ba8dbce042a9c960219b75ea1c07196a7fa41c2fab9e86" + url: "https://pub.dev" + source: hosted + version: "3.0.5" characters: dependency: transitive description: @@ -736,6 +744,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.11.1" + material_symbols_icons: + dependency: "direct main" + description: + name: material_symbols_icons + sha256: "02555a48e1ec02b16e532dfd4ef13c4f6bf7ec7c20230e58e56641a393433dc3" + url: "https://pub.dev" + source: hosted + version: "4.2892.0" meta: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 73c8ddfb..7790670b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -44,6 +44,7 @@ dependencies: file_picker: ^8.0.0 saf_util: ^0.11.0 saf_stream: ^0.12.2 + material_symbols_icons: ^4.2892.0 dev_dependencies: flutter_test: