refactor: new icons

This commit is contained in:
edde746
2025-12-12 15:57:22 +01:00
parent bf26894231
commit 263eddbdba
68 changed files with 739 additions and 669 deletions
+4 -3
View File
@@ -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;
}
}
@@ -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<T extends StatefulWidget>
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<T extends StatefulWidget>
? 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<T extends StatefulWidget>
// 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,
+37 -49
View File
@@ -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<DiscoverScreen>
// 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<DiscoverScreen>
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<DiscoverScreen>
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<DiscoverScreen>
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<DiscoverScreen>
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<DiscoverScreen>
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<DiscoverScreen>
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<DiscoverScreen>
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<DiscoverScreen>
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<DiscoverScreen>
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<DiscoverScreen>
_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<DiscoverScreen>
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
+3 -5
View File
@@ -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,
+4 -2
View File
@@ -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<HubDetailScreen> 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<HubDetailScreen> 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),
@@ -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<T> 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,
);
@@ -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,
);
}
}
@@ -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,
);
}
}
@@ -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<FiltersBottomSheet> {
// 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<FiltersBottomSheet> {
});
_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<FiltersBottomSheet> {
),
),
if (displayValue != null) const SizedBox(width: 8),
const Icon(Icons.chevron_right),
const AppIcon(Symbols.chevron_right_rounded, fill: 1),
],
),
onTap: () => _loadFilterValues(filter),
+11 -22
View File
@@ -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),
+3 -2
View File
@@ -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<FolderTreeView> {
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<FolderTreeView> {
if (_rootFolders.isEmpty) {
return EmptyStateWidget(
message: t.libraries.noFoldersFound,
icon: Icons.folder_open,
icon: Symbols.folder_open_rounded,
);
}
+30 -47
View File
@@ -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<LibrariesScreen>
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<LibrariesScreen>
),
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<LibrariesScreen>
),
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<LibrariesScreen>
),
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<LibrariesScreen>
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<LibrariesScreen>
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<LibrariesScreen>
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<LibrariesScreen>
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<LibrariesScreen>
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<LibrariesScreen>
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<LibrariesScreen>
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),
+4 -2
View File
@@ -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<SortBottomSheet> {
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},
@@ -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!),
),
],
@@ -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,
);
}
@@ -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;
@@ -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;
@@ -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;
}
}
+17 -19
View File
@@ -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<MainScreen> 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,
+113 -98
View File
@@ -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<MediaDetailScreen> {
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<MediaDetailScreen> {
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<MediaDetailScreen> {
}
}
},
icon: Icon(
icon: AppIcon(
_getPlayButtonIcon(metadata),
fill: 1,
size: 20,
),
label: Text(
@@ -1035,7 +1040,10 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
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<MediaDetailScreen> {
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<MediaDetailScreen> {
);
}
},
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<MediaDetailScreen> {
}
}
},
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<MediaDetailScreen> {
}
}
},
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<MediaDetailScreen> {
}
},
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<MediaDetailScreen> {
}
}
},
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<MediaDetailScreen> {
);
}
},
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<MediaDetailScreen> {
},
),
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<OfflineWatchProvider>();
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<MediaDetailScreen> {
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<MediaDetailScreen> {
}
}
},
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<OfflineWatchProvider>();
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<MediaDetailScreen> {
.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<MediaDetailScreen> {
.colorScheme
.surfaceContainerHighest,
child: const Center(
child: Icon(Icons.person),
child: AppIcon(
Symbols.person_rounded,
fill: 1,
),
),
),
)
@@ -1747,7 +1747,10 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
.colorScheme
.surfaceContainerHighest,
child: const Center(
child: Icon(Icons.person),
child: AppIcon(
Symbols.person_rounded,
fill: 1,
),
),
),
),
@@ -1872,17 +1875,17 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
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),
],
),
),
@@ -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<List<PlexMetadata>> 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,
+5 -3
View File
@@ -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<PlaylistItemCard> {
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<PlaylistItemCard> {
// 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<PlaylistItemCard> {
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),
);
}
+4 -9
View File
@@ -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<PinEntryDialog>
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<PinEntryDialog>
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;
+3 -1
View File
@@ -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,
);
@@ -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',
+7 -9
View File
@@ -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',
+6 -8
View File
@@ -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<SearchScreen> 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<SearchScreen> 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<SearchScreen> 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,
+12 -38
View File
@@ -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<SeasonDetailScreen>
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)
}
+4 -2
View File
@@ -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<AboutScreen> {
// 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,
@@ -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<HotKeyRecorderWidget> {
),
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;
+3 -1
View File
@@ -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<LicensesScreen> {
),
)
: null,
trailing: const Icon(Icons.chevron_right),
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
onTap: () => _showLicenseDetail(mergedLicense),
),
);
+13 -14
View File
@@ -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<LogsScreen> {
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<LogsScreen> {
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) ...[
+91 -73
View File
@@ -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<SettingsScreen> {
Consumer<ThemeProvider>(
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<SettingsProvider>(
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<SettingsScreen> {
Consumer<SettingsProvider>(
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<SettingsScreen> {
Consumer<SettingsProvider>(
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<SettingsScreen> {
Consumer<SettingsProvider>(
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<SettingsScreen> {
),
),
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<SettingsScreen> {
},
),
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<SettingsScreen> {
},
),
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<SettingsScreen> {
),
),
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<SettingsScreen> {
},
),
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<SettingsScreen> {
},
),
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<SettingsScreen> {
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<SettingsScreen> {
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<SettingsScreen> {
),
),
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<SettingsScreen> {
),
),
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<SettingsScreen> {
},
),
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<SettingsScreen> {
},
),
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<SettingsScreen> {
),
),
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<SettingsScreen> {
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<SettingsScreen> {
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<SettingsScreen> {
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<SettingsScreen> {
},
),
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<SettingsScreen> {
},
),
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<SettingsScreen> {
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<SettingsScreen> {
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<SettingsScreen> {
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<SettingsScreen> {
},
),
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<SettingsScreen> {
},
),
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<SettingsScreen> {
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<SettingsScreen> {
},
),
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),
@@ -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,
);
}
+4 -10
View File
@@ -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<VideoPlayerScreen>
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<PlaybackStateProvider>(
builder: (context, playbackState, child) {
@@ -1396,11 +1394,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen>
),
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),
],
],
);
+9 -8
View File
@@ -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
}
}
+3 -5
View File
@@ -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<AppBarBackButton>
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),
);
},
),
+74
View File
@@ -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<Shadow>? 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<Shadow>? shadows;
static void update({
double? fill,
double? weight,
double? grade,
double? opticalSize,
Color? color,
List<Shadow>? 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;
}
}
+3 -1
View File
@@ -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),
),
],
+3 -1
View File
@@ -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,
);
+11 -12
View File
@@ -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,
+19 -17
View File
@@ -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<DownloadTreeView> {
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<DownloadTreeView> {
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<DownloadTreeView> {
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<DownloadTreeView> {
// 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<DownloadTreeView> {
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<DownloadTreeView> {
// 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<DownloadTreeView> {
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<DownloadTreeView> {
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<DownloadTreeView> {
// 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<DownloadTreeView> {
// 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',
),
+4 -6
View File
@@ -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<FileInfoBottomSheet> {
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<FileInfoBottomSheet> {
),
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),
),
],
+2 -1
View File
@@ -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<FocusableFilterChip>
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,
@@ -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),
),
),
);
+4 -2
View File
@@ -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<HubSection> {
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<HubSection> {
),
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),
],
],
),
+6 -4
View File
@@ -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
+27 -25
View File
@@ -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<MediaContextMenu> {
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<MediaContextMenu> {
menuActions.add(
_MenuAction(
value: 'shuffle',
icon: Icons.shuffle,
icon: Symbols.shuffle_rounded,
label: t.mediaMenu.shufflePlay,
),
);
@@ -170,7 +172,7 @@ class MediaContextMenuState extends State<MediaContextMenu> {
menuActions.add(
_MenuAction(
value: 'delete',
icon: Icons.delete,
icon: Symbols.delete_rounded,
label: t.common.delete,
),
);
@@ -184,7 +186,7 @@ class MediaContextMenuState extends State<MediaContextMenu> {
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<MediaContextMenu> {
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<MediaContextMenu> {
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<MediaContextMenu> {
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<MediaContextMenu> {
menuActions.add(
_MenuAction(
value: 'series',
icon: Icons.tv,
icon: Symbols.tv_rounded,
label: t.mediaMenu.goToSeries,
),
);
@@ -240,7 +242,7 @@ class MediaContextMenuState extends State<MediaContextMenu> {
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<MediaContextMenu> {
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<MediaContextMenu> {
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<MediaContextMenu> {
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<MediaContextMenu> {
menuActions.add(
_MenuAction(
value: 'download',
icon: Icons.download,
icon: Symbols.download_rounded,
label: t.downloads.downloadNow,
),
);
@@ -307,7 +309,7 @@ class MediaContextMenuState extends State<MediaContextMenu> {
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<MediaContextMenu> {
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<MediaContextMenu> {
),
...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<MediaContextMenu> {
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),
);
+8 -14
View File
@@ -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),
),
);
}
+23 -37
View File
@@ -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<SideNavigationRail> {
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<SideNavigationRail> {
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<SideNavigationRail> {
// 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<SideNavigationRail> {
// 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<SideNavigationRail> {
// 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<SideNavigationRail> {
),
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<SideNavigationRail> {
),
),
),
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),
],
),
),
@@ -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<DesktopVideoControls> {
_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<DesktopVideoControls> {
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<DesktopVideoControls> {
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<DesktopVideoControls> {
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<DesktopVideoControls> {
_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<DesktopVideoControls> {
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,
),
@@ -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,
);
}
@@ -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,
),
@@ -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) {
@@ -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<ChapterSheet> {
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<ChapterSheet> {
),
),
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<ChapterSheet> {
return BaseVideoControlSheet(
title: 'Chapters',
icon: Icons.video_library,
icon: Symbols.video_library_rounded,
child: content,
);
},
@@ -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) {
@@ -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<VersionSheet> {
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<VersionSheet> {
),
),
trailing: isSelected
? const Icon(Icons.check, color: Colors.blue)
? const AppIcon(Symbols.check_rounded, fill: 1, color: Colors.blue)
: null,
onTap: () {
Navigator.pop(context);
@@ -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<VideoSettingsSheet> {
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<VideoSettingsSheet> {
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<VideoSettingsSheet> {
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<VideoSettingsSheet> {
// 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<VideoSettingsSheet> {
// 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<VideoSettingsSheet> {
// 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<VideoSettingsSheet> {
: '${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<VideoSettingsSheet> {
),
),
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<VideoSettingsSheet> {
),
),
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);
@@ -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),
),
],
@@ -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),
@@ -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<PlexVideoControls>
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<PlexVideoControls>
? '$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<PlexVideoControls>
),
),
const SizedBox(width: 8),
Icon(buttonIcon, color: Colors.black, size: 20),
AppIcon(buttonIcon, fill: 1, color: Colors.black, size: 20),
],
),
),
@@ -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: () {
@@ -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(
@@ -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<SyncOffsetControl> {
// 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],
@@ -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;
}
}
@@ -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<VolumeControl> {
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);
+16
View File
@@ -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:
+1
View File
@@ -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: