refactor: new icons
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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) ...[
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
),
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user