fix: plex track preference
This commit is contained in:
+7
-8
@@ -169,15 +169,14 @@ class _SetupScreenState extends State<SetupScreen> {
|
||||
if (mounted) {
|
||||
context.plexClient.setClient(result.client!);
|
||||
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => MainScreen(
|
||||
client: result.client!,
|
||||
userProfile: result.userProfile,
|
||||
if (mounted) {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => MainScreen(client: result.client!),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
|
||||
@@ -32,18 +32,18 @@ class PlexHomeUser {
|
||||
factory PlexHomeUser.fromJson(Map<String, dynamic> json) {
|
||||
return PlexHomeUser(
|
||||
id: (json['id'] as num?)?.toInt() ?? 0,
|
||||
uuid: json['uuid'] as String,
|
||||
title: json['title'] as String,
|
||||
uuid: json['uuid'] as String? ?? '',
|
||||
title: json['title'] as String? ?? 'Unknown',
|
||||
username: json['username'] as String?,
|
||||
email: json['email'] as String?,
|
||||
friendlyName: json['friendlyName'] as String?,
|
||||
thumb: json['thumb'] as String,
|
||||
hasPassword: json['hasPassword'] as bool,
|
||||
restricted: json['restricted'] as bool,
|
||||
thumb: json['thumb'] as String? ?? '',
|
||||
hasPassword: json['hasPassword'] as bool? ?? false,
|
||||
restricted: json['restricted'] as bool? ?? false,
|
||||
updatedAt: (json['updatedAt'] as num?)?.toInt(),
|
||||
admin: json['admin'] as bool,
|
||||
guest: json['guest'] as bool,
|
||||
protected: json['protected'] as bool,
|
||||
admin: json['admin'] as bool? ?? false,
|
||||
guest: json['guest'] as bool? ?? false,
|
||||
protected: json['protected'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../models/plex_home.dart';
|
||||
import '../models/plex_home_user.dart';
|
||||
import '../models/plex_user_profile.dart';
|
||||
import '../services/plex_auth_service.dart';
|
||||
import '../services/storage_service.dart';
|
||||
import '../utils/app_logger.dart';
|
||||
@@ -10,11 +11,13 @@ import 'plex_client_provider.dart';
|
||||
class UserProfileProvider extends ChangeNotifier {
|
||||
PlexHome? _home;
|
||||
PlexHomeUser? _currentUser;
|
||||
PlexUserProfile? _profileSettings;
|
||||
bool _isLoading = false;
|
||||
String? _error;
|
||||
|
||||
PlexHome? get home => _home;
|
||||
PlexHomeUser? get currentUser => _currentUser;
|
||||
PlexUserProfile? get profileSettings => _profileSettings;
|
||||
bool get isLoading => _isLoading;
|
||||
String? get error => _error;
|
||||
bool get hasMultipleUsers {
|
||||
@@ -66,6 +69,19 @@ class UserProfileProvider extends ChangeNotifier {
|
||||
// Don't set error here as it's not critical for app startup
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch fresh profile settings from API
|
||||
appLogger.d('UserProfileProvider: Fetching profile settings from API');
|
||||
try {
|
||||
await refreshProfileSettings();
|
||||
} catch (e) {
|
||||
appLogger.w(
|
||||
'UserProfileProvider: Failed to fetch profile settings during initialization',
|
||||
error: e,
|
||||
);
|
||||
// Don't set error here, cached profile (if any) was already loaded
|
||||
}
|
||||
|
||||
appLogger.d('UserProfileProvider: Initialization complete');
|
||||
} catch (e) {
|
||||
appLogger.e(
|
||||
@@ -98,9 +114,41 @@ class UserProfileProvider extends ChangeNotifier {
|
||||
_currentUser = _home!.getUserByUUID(currentUserUUID);
|
||||
}
|
||||
|
||||
// Profile settings are NOT cached - they will be fetched fresh from API
|
||||
// in refreshProfileSettings()
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Fetch the user's profile settings from the API
|
||||
Future<void> refreshProfileSettings() async {
|
||||
if (_authService == null || _storageService == null) {
|
||||
appLogger.w('refreshProfileSettings: Services not initialized, skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
appLogger.d('Fetching user profile settings from Plex API');
|
||||
try {
|
||||
final currentToken = _storageService!.getPlexToken();
|
||||
if (currentToken == null) {
|
||||
appLogger.w(
|
||||
'refreshProfileSettings: No Plex token available, cannot fetch profile',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final profile = await _authService!.getUserProfile(currentToken);
|
||||
_profileSettings = profile;
|
||||
|
||||
appLogger.i('Successfully fetched user profile settings from API');
|
||||
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
appLogger.w('Failed to fetch user profile settings from API', error: e);
|
||||
// Don't set error state, profile will remain null or keep existing value
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadHomeUsers({bool forceRefresh = false}) async {
|
||||
appLogger.d('loadHomeUsers called - forceRefresh: $forceRefresh');
|
||||
|
||||
@@ -263,8 +311,17 @@ class UserProfileProvider extends ChangeNotifier {
|
||||
// Update current user
|
||||
_currentUser = user;
|
||||
|
||||
// Save user profile
|
||||
await _storageService!.saveUserProfile(switchResponse.profile.toJson());
|
||||
// Update user profile settings (fresh from API)
|
||||
_profileSettings = switchResponse.profile;
|
||||
appLogger.d(
|
||||
'Updated profile settings for user: ${user.displayName}',
|
||||
error: {
|
||||
'defaultAudioLanguage':
|
||||
_profileSettings?.defaultAudioLanguage ?? 'not set',
|
||||
'defaultSubtitleLanguage':
|
||||
_profileSettings?.defaultSubtitleLanguage ?? 'not set',
|
||||
},
|
||||
);
|
||||
|
||||
// Update PlexClient with proper server access token
|
||||
if (clientProvider != null) {
|
||||
@@ -322,6 +379,7 @@ class UserProfileProvider extends ChangeNotifier {
|
||||
// Clear user-specific provider state but keep services for future sign-ins
|
||||
_home = null;
|
||||
_currentUser = null;
|
||||
_profileSettings = null;
|
||||
_onDataInvalidationRequested = null;
|
||||
|
||||
_clearError();
|
||||
@@ -346,6 +404,7 @@ class UserProfileProvider extends ChangeNotifier {
|
||||
// Clear cached data from previous server (both memory and storage)
|
||||
_home = null;
|
||||
_currentUser = null;
|
||||
_profileSettings = null;
|
||||
_clearError();
|
||||
|
||||
// Re-initialize services with current storage state
|
||||
@@ -401,6 +460,15 @@ class UserProfileProvider extends ChangeNotifier {
|
||||
appLogger.w(
|
||||
'UserProfileProvider: Cannot perform complete profile switch - no context provided',
|
||||
);
|
||||
// Still try to fetch profile settings even without full switch
|
||||
try {
|
||||
await refreshProfileSettings();
|
||||
} catch (e) {
|
||||
appLogger.w(
|
||||
'UserProfileProvider: Failed to refresh profile settings for new server',
|
||||
error: e,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
appLogger.w(
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../client/plex_client.dart';
|
||||
import '../models/plex_metadata.dart';
|
||||
import '../models/plex_user_profile.dart';
|
||||
import '../providers/plex_client_provider.dart';
|
||||
import '../services/storage_service.dart';
|
||||
import '../services/plex_auth_service.dart';
|
||||
@@ -22,10 +21,9 @@ import '../utils/video_player_navigation.dart';
|
||||
import 'auth_screen.dart';
|
||||
|
||||
class DiscoverScreen extends StatefulWidget {
|
||||
final PlexUserProfile? userProfile;
|
||||
final VoidCallback? onBecameVisible;
|
||||
|
||||
const DiscoverScreen({super.key, this.userProfile, this.onBecameVisible});
|
||||
const DiscoverScreen({super.key, this.onBecameVisible});
|
||||
|
||||
@override
|
||||
State<DiscoverScreen> createState() => _DiscoverScreenState();
|
||||
@@ -569,11 +567,7 @@ class _DiscoverScreenState extends State<DiscoverScreen>
|
||||
if (client == null) return;
|
||||
|
||||
appLogger.d('Navigating to VideoPlayerScreen for: ${heroItem.title}');
|
||||
navigateToVideoPlayer(
|
||||
context,
|
||||
metadata: heroItem,
|
||||
userProfile: widget.userProfile,
|
||||
);
|
||||
navigateToVideoPlayer(context, metadata: heroItem);
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
@@ -898,11 +892,7 @@ class _DiscoverScreenState extends State<DiscoverScreen>
|
||||
if (client == null) return;
|
||||
|
||||
appLogger.d('Playing: ${heroItem.title}');
|
||||
navigateToVideoPlayer(
|
||||
context,
|
||||
metadata: heroItem,
|
||||
userProfile: widget.userProfile,
|
||||
);
|
||||
navigateToVideoPlayer(context, metadata: heroItem);
|
||||
},
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
child: Container(
|
||||
@@ -999,7 +989,6 @@ class _DiscoverScreenState extends State<DiscoverScreen>
|
||||
width: cardWidth,
|
||||
height: cardHeight,
|
||||
onRefresh: updateItem,
|
||||
userProfile: widget.userProfile,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -4,7 +4,6 @@ import '../client/plex_client.dart';
|
||||
import '../models/plex_library.dart';
|
||||
import '../models/plex_metadata.dart';
|
||||
import '../models/plex_filter.dart';
|
||||
import '../models/plex_user_profile.dart';
|
||||
import '../providers/plex_client_provider.dart';
|
||||
import '../utils/provider_extensions.dart';
|
||||
import '../widgets/media_card.dart';
|
||||
@@ -16,9 +15,7 @@ import '../mixins/item_updatable.dart';
|
||||
import '../theme/theme_helper.dart';
|
||||
|
||||
class LibrariesScreen extends StatefulWidget {
|
||||
final PlexUserProfile? userProfile;
|
||||
|
||||
const LibrariesScreen({super.key, this.userProfile});
|
||||
const LibrariesScreen({super.key});
|
||||
|
||||
@override
|
||||
State<LibrariesScreen> createState() => _LibrariesScreenState();
|
||||
@@ -436,7 +433,6 @@ class _LibrariesScreenState extends State<LibrariesScreen>
|
||||
key: Key(item.ratingKey),
|
||||
item: item,
|
||||
onRefresh: updateItem,
|
||||
userProfile: widget.userProfile,
|
||||
);
|
||||
}, childCount: _items.length),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../client/plex_client.dart';
|
||||
import '../models/plex_user_profile.dart';
|
||||
import '../utils/app_logger.dart';
|
||||
import '../utils/provider_extensions.dart';
|
||||
import '../main.dart';
|
||||
@@ -12,9 +11,8 @@ import 'settings_screen.dart';
|
||||
|
||||
class MainScreen extends StatefulWidget {
|
||||
final PlexClient client;
|
||||
final PlexUserProfile? userProfile;
|
||||
|
||||
const MainScreen({super.key, required this.client, this.userProfile});
|
||||
const MainScreen({super.key, required this.client});
|
||||
|
||||
@override
|
||||
State<MainScreen> createState() => _MainScreenState();
|
||||
@@ -36,11 +34,10 @@ class _MainScreenState extends State<MainScreen> with RouteAware {
|
||||
_screens = [
|
||||
DiscoverScreen(
|
||||
key: _discoverKey,
|
||||
userProfile: widget.userProfile,
|
||||
onBecameVisible: _onDiscoverBecameVisible,
|
||||
),
|
||||
LibrariesScreen(key: _librariesKey, userProfile: widget.userProfile),
|
||||
SearchScreen(key: _searchKey, userProfile: widget.userProfile),
|
||||
LibrariesScreen(key: _librariesKey),
|
||||
SearchScreen(key: _searchKey),
|
||||
SettingsScreen(key: _settingsKey),
|
||||
];
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../models/plex_metadata.dart';
|
||||
import '../models/plex_user_profile.dart';
|
||||
import '../providers/plex_client_provider.dart';
|
||||
import '../widgets/desktop_app_bar.dart';
|
||||
import '../widgets/app_bar_back_button.dart';
|
||||
@@ -15,13 +14,8 @@ import 'season_detail_screen.dart';
|
||||
|
||||
class MediaDetailScreen extends StatefulWidget {
|
||||
final PlexMetadata metadata;
|
||||
final PlexUserProfile? userProfile;
|
||||
|
||||
const MediaDetailScreen({
|
||||
super.key,
|
||||
required this.metadata,
|
||||
this.userProfile,
|
||||
});
|
||||
const MediaDetailScreen({super.key, required this.metadata});
|
||||
|
||||
@override
|
||||
State<MediaDetailScreen> createState() => _MediaDetailScreenState();
|
||||
@@ -214,11 +208,7 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
|
||||
if (client == null) return;
|
||||
|
||||
appLogger.d('Playing first episode: ${firstEpisode.title}');
|
||||
await navigateToVideoPlayer(
|
||||
context,
|
||||
metadata: firstEpisode,
|
||||
userProfile: widget.userProfile,
|
||||
);
|
||||
await navigateToVideoPlayer(context, metadata: firstEpisode);
|
||||
appLogger.d('Returned from playback, refreshing metadata');
|
||||
// Refresh metadata when returning from video player
|
||||
_loadFullMetadata();
|
||||
@@ -541,7 +531,6 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
|
||||
await navigateToVideoPlayer(
|
||||
context,
|
||||
metadata: _onDeckEpisode!,
|
||||
userProfile: widget.userProfile,
|
||||
);
|
||||
appLogger.d(
|
||||
'Returned from playback, refreshing metadata',
|
||||
@@ -562,7 +551,6 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
|
||||
await navigateToVideoPlayer(
|
||||
context,
|
||||
metadata: metadata,
|
||||
userProfile: widget.userProfile,
|
||||
);
|
||||
appLogger.d(
|
||||
'Returned from playback, refreshing metadata',
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../client/plex_client.dart';
|
||||
import '../models/plex_metadata.dart';
|
||||
import '../models/plex_user_profile.dart';
|
||||
import '../utils/provider_extensions.dart';
|
||||
import '../widgets/media_card.dart';
|
||||
import '../widgets/desktop_app_bar.dart';
|
||||
@@ -10,9 +9,7 @@ import '../mixins/refreshable.dart';
|
||||
import '../mixins/item_updatable.dart';
|
||||
|
||||
class SearchScreen extends StatefulWidget {
|
||||
final PlexUserProfile? userProfile;
|
||||
|
||||
const SearchScreen({super.key, this.userProfile});
|
||||
const SearchScreen({super.key});
|
||||
|
||||
@override
|
||||
State<SearchScreen> createState() => _SearchScreenState();
|
||||
@@ -228,7 +225,6 @@ class _SearchScreenState extends State<SearchScreen>
|
||||
key: Key(item.ratingKey),
|
||||
item: item,
|
||||
onRefresh: updateItem,
|
||||
userProfile: widget.userProfile,
|
||||
);
|
||||
}, childCount: _searchResults.length),
|
||||
),
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../client/plex_client.dart';
|
||||
import '../models/plex_metadata.dart';
|
||||
import '../models/plex_user_profile.dart';
|
||||
import '../providers/plex_client_provider.dart';
|
||||
import '../utils/provider_extensions.dart';
|
||||
import '../utils/video_player_navigation.dart';
|
||||
@@ -14,9 +13,8 @@ import '../theme/theme_helper.dart';
|
||||
|
||||
class SeasonDetailScreen extends StatefulWidget {
|
||||
final PlexMetadata season;
|
||||
final PlexUserProfile? userProfile;
|
||||
|
||||
const SeasonDetailScreen({super.key, required this.season, this.userProfile});
|
||||
const SeasonDetailScreen({super.key, required this.season});
|
||||
|
||||
@override
|
||||
State<SeasonDetailScreen> createState() => _SeasonDetailScreenState();
|
||||
@@ -136,11 +134,7 @@ class _SeasonDetailScreenState extends State<SeasonDetailScreen>
|
||||
metadata: episode,
|
||||
onRefresh: updateItem,
|
||||
onTap: () async {
|
||||
await navigateToVideoPlayer(
|
||||
context,
|
||||
metadata: episode,
|
||||
userProfile: widget.userProfile,
|
||||
);
|
||||
await navigateToVideoPlayer(context, metadata: episode);
|
||||
// Refresh episodes when returning from video player
|
||||
_loadEpisodes();
|
||||
},
|
||||
|
||||
@@ -20,7 +20,6 @@ class VideoPlayerScreen extends StatefulWidget {
|
||||
final AudioTrack? preferredAudioTrack;
|
||||
final SubtitleTrack? preferredSubtitleTrack;
|
||||
final double? preferredPlaybackRate;
|
||||
final PlexUserProfile? userProfile;
|
||||
|
||||
const VideoPlayerScreen({
|
||||
super.key,
|
||||
@@ -28,7 +27,6 @@ class VideoPlayerScreen extends StatefulWidget {
|
||||
this.preferredAudioTrack,
|
||||
this.preferredSubtitleTrack,
|
||||
this.preferredPlaybackRate,
|
||||
this.userProfile,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -52,9 +50,6 @@ class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
|
||||
super.initState();
|
||||
|
||||
appLogger.d('VideoPlayerScreen initialized for: ${widget.metadata.title}');
|
||||
if (widget.userProfile != null) {
|
||||
appLogger.d('Using user profile for track selection');
|
||||
}
|
||||
if (widget.preferredAudioTrack != null) {
|
||||
appLogger.d(
|
||||
'Preferred audio track: ${widget.preferredAudioTrack!.title ?? widget.preferredAudioTrack!.id} (${widget.preferredAudioTrack!.language ?? "unknown"})',
|
||||
@@ -477,6 +472,9 @@ class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
|
||||
Future<void> processTracks(Tracks tracks) async {
|
||||
appLogger.d('Starting track selection process');
|
||||
|
||||
// Get profile settings for track selection
|
||||
final profileSettings = context.profileSettings;
|
||||
|
||||
// Get real tracks (excluding auto and no)
|
||||
final realAudioTracks = tracks.audio
|
||||
.where((t) => t.id != 'auto' && t.id != 'no')
|
||||
@@ -523,11 +521,11 @@ class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
|
||||
}
|
||||
|
||||
// Priority 2: If no preferred track matched, try user profile preferences
|
||||
if (trackToSelect == null && widget.userProfile != null) {
|
||||
if (trackToSelect == null && profileSettings != null) {
|
||||
appLogger.d('Priority 2: Checking user profile preferences');
|
||||
trackToSelect = _findAudioTrackByProfile(
|
||||
realAudioTracks,
|
||||
widget.userProfile!,
|
||||
profileSettings,
|
||||
);
|
||||
} else if (trackToSelect == null) {
|
||||
appLogger.d('Priority 2: No user profile available');
|
||||
@@ -585,12 +583,12 @@ class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
|
||||
|
||||
// Priority 2: If no preferred match, apply user profile preferences
|
||||
if (subtitleToSelect == null &&
|
||||
widget.userProfile != null &&
|
||||
profileSettings != null &&
|
||||
realSubtitleTracks.isNotEmpty) {
|
||||
appLogger.d('Priority 2: Checking user profile preferences');
|
||||
subtitleToSelect = _findSubtitleTrackByProfile(
|
||||
realSubtitleTracks,
|
||||
widget.userProfile!,
|
||||
profileSettings,
|
||||
);
|
||||
} else if (subtitleToSelect == null && realSubtitleTracks.isNotEmpty) {
|
||||
appLogger.d('Priority 2: No user profile available');
|
||||
@@ -720,7 +718,6 @@ class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
|
||||
navigateToVideoPlayer(
|
||||
context,
|
||||
metadata: episodeMetadata,
|
||||
userProfile: widget.userProfile,
|
||||
usePushReplacement: true,
|
||||
);
|
||||
}
|
||||
@@ -745,7 +742,6 @@ class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
|
||||
preferredAudioTrack: currentAudioTrack,
|
||||
preferredSubtitleTrack: currentSubtitleTrack,
|
||||
preferredPlaybackRate: currentRate,
|
||||
userProfile: widget.userProfile,
|
||||
usePushReplacement: true,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ class ServerConnectionService {
|
||||
// Fetch user profile if requested
|
||||
PlexUserProfile? userProfile;
|
||||
if (fetchUserProfile && plexToken != null) {
|
||||
userProfile = await _fetchAndCacheUserProfile(plexToken);
|
||||
userProfile = await _fetchUserProfile(plexToken);
|
||||
}
|
||||
|
||||
// Return success result
|
||||
@@ -123,17 +123,15 @@ class ServerConnectionService {
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch user profile from Plex API and cache it locally
|
||||
static Future<PlexUserProfile?> _fetchAndCacheUserProfile(
|
||||
String plexToken,
|
||||
) async {
|
||||
/// Fetch user profile from Plex API
|
||||
static Future<PlexUserProfile?> _fetchUserProfile(String plexToken) async {
|
||||
appLogger.d('Fetching user profile from Plex API');
|
||||
try {
|
||||
final authService = await PlexAuthService.create();
|
||||
final profile = await authService.getUserProfile(plexToken);
|
||||
|
||||
appLogger.i(
|
||||
'Successfully fetched user profile',
|
||||
'Successfully fetched user profile from API',
|
||||
error: {
|
||||
'autoSelectAudio': profile.autoSelectAudio,
|
||||
'defaultAudioLanguage': profile.defaultAudioLanguage ?? 'not set',
|
||||
@@ -144,40 +142,9 @@ class ServerConnectionService {
|
||||
},
|
||||
);
|
||||
|
||||
// Cache the profile
|
||||
final storage = await StorageService.getInstance();
|
||||
await storage.saveUserProfile(profile.toJson());
|
||||
appLogger.d('User profile cached locally');
|
||||
|
||||
return profile;
|
||||
} catch (e) {
|
||||
appLogger.w(
|
||||
'Failed to fetch user profile from API, attempting to load from cache',
|
||||
error: e,
|
||||
);
|
||||
|
||||
// Failed to fetch profile, try to load from cache
|
||||
final storage = await StorageService.getInstance();
|
||||
final cachedProfile = storage.getUserProfile();
|
||||
if (cachedProfile != null) {
|
||||
final profile = PlexUserProfile.fromJson(cachedProfile);
|
||||
appLogger.i(
|
||||
'Loaded user profile from cache',
|
||||
error: {
|
||||
'autoSelectAudio': profile.autoSelectAudio,
|
||||
'defaultAudioLanguage': profile.defaultAudioLanguage ?? 'not set',
|
||||
'autoSelectSubtitle': profile.autoSelectSubtitle,
|
||||
'defaultSubtitleLanguage':
|
||||
profile.defaultSubtitleLanguage ?? 'not set',
|
||||
'defaultSubtitleForced': profile.defaultSubtitleForced,
|
||||
},
|
||||
);
|
||||
return profile;
|
||||
}
|
||||
|
||||
appLogger.w(
|
||||
'No cached user profile available, track selection will use defaults',
|
||||
);
|
||||
appLogger.w('Failed to fetch user profile from API', error: e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,14 +11,4 @@ import 'package:logger/logger.dart';
|
||||
/// appLogger.w('Warning message');
|
||||
/// appLogger.e('Error message', error: e, stackTrace: stackTrace);
|
||||
/// ```
|
||||
final appLogger = Logger(
|
||||
printer: PrettyPrinter(
|
||||
methodCount: 2,
|
||||
errorMethodCount: 8,
|
||||
lineLength: 120,
|
||||
colors: true,
|
||||
printEmojis: true,
|
||||
dateTimeFormat: DateTimeFormat.onlyTimeAndSinceStart,
|
||||
),
|
||||
level: Level.debug,
|
||||
);
|
||||
final appLogger = Logger(printer: SimplePrinter(), level: Level.debug);
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
|
||||
import '../providers/plex_client_provider.dart';
|
||||
import '../providers/user_profile_provider.dart';
|
||||
import '../client/plex_client.dart';
|
||||
import '../models/plex_user_profile.dart';
|
||||
|
||||
extension ProviderExtensions on BuildContext {
|
||||
PlexClientProvider get plexClient =>
|
||||
@@ -22,4 +23,7 @@ extension ProviderExtensions on BuildContext {
|
||||
|
||||
// Null-safe client access
|
||||
PlexClient get clientSafe => plexClient.client!;
|
||||
|
||||
// Direct profile settings access (nullable)
|
||||
PlexUserProfile? get profileSettings => userProfile.profileSettings;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import '../models/plex_metadata.dart';
|
||||
import '../models/plex_user_profile.dart';
|
||||
import '../screens/video_player_screen.dart';
|
||||
|
||||
/// Navigates to the VideoPlayerScreen with instant transitions to prevent white flash.
|
||||
@@ -13,7 +12,6 @@ import '../screens/video_player_screen.dart';
|
||||
/// Parameters:
|
||||
/// - [context]: The build context for navigation
|
||||
/// - [metadata]: The Plex metadata for the content to play
|
||||
/// - [userProfile]: Optional user profile for track selection preferences
|
||||
/// - [preferredAudioTrack]: Optional audio track to select on playback start
|
||||
/// - [preferredSubtitleTrack]: Optional subtitle track to select on playback start
|
||||
/// - [preferredPlaybackRate]: Optional playback speed to set on playback start
|
||||
@@ -25,7 +23,6 @@ import '../screens/video_player_screen.dart';
|
||||
Future<bool?> navigateToVideoPlayer(
|
||||
BuildContext context, {
|
||||
required PlexMetadata metadata,
|
||||
PlexUserProfile? userProfile,
|
||||
AudioTrack? preferredAudioTrack,
|
||||
SubtitleTrack? preferredSubtitleTrack,
|
||||
double? preferredPlaybackRate,
|
||||
@@ -34,7 +31,6 @@ Future<bool?> navigateToVideoPlayer(
|
||||
final route = PageRouteBuilder<bool>(
|
||||
pageBuilder: (context, animation, secondaryAnimation) => VideoPlayerScreen(
|
||||
metadata: metadata,
|
||||
userProfile: userProfile,
|
||||
preferredAudioTrack: preferredAudioTrack,
|
||||
preferredSubtitleTrack: preferredSubtitleTrack,
|
||||
preferredPlaybackRate: preferredPlaybackRate,
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../models/plex_metadata.dart';
|
||||
import '../models/plex_user_profile.dart';
|
||||
import '../providers/plex_client_provider.dart';
|
||||
import '../utils/provider_extensions.dart';
|
||||
import '../utils/video_player_navigation.dart';
|
||||
@@ -16,7 +15,6 @@ class MediaCard extends StatefulWidget {
|
||||
final double? width;
|
||||
final double? height;
|
||||
final void Function(String ratingKey)? onRefresh;
|
||||
final PlexUserProfile? userProfile;
|
||||
|
||||
const MediaCard({
|
||||
super.key,
|
||||
@@ -24,7 +22,6 @@ class MediaCard extends StatefulWidget {
|
||||
this.width,
|
||||
this.height,
|
||||
this.onRefresh,
|
||||
this.userProfile,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -47,7 +44,6 @@ class _MediaCardState extends State<MediaCard>
|
||||
final result = await navigateToVideoPlayer(
|
||||
context,
|
||||
metadata: widget.item,
|
||||
userProfile: widget.userProfile,
|
||||
);
|
||||
// Refresh parent screen if result indicates it's needed
|
||||
if (result == true) {
|
||||
@@ -58,10 +54,7 @@ class _MediaCardState extends State<MediaCard>
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => SeasonDetailScreen(
|
||||
season: widget.item,
|
||||
userProfile: widget.userProfile,
|
||||
),
|
||||
builder: (context) => SeasonDetailScreen(season: widget.item),
|
||||
),
|
||||
);
|
||||
// Season screen doesn't return a refresh flag, but we can refresh anyway
|
||||
@@ -71,10 +64,7 @@ class _MediaCardState extends State<MediaCard>
|
||||
final result = await Navigator.push<bool>(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => MediaDetailScreen(
|
||||
metadata: widget.item,
|
||||
userProfile: widget.userProfile,
|
||||
),
|
||||
builder: (context) => MediaDetailScreen(metadata: widget.item),
|
||||
),
|
||||
);
|
||||
// Refresh parent screen if result indicates it's needed
|
||||
|
||||
@@ -1097,7 +1097,9 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
|
||||
initialData: widget.player.state.track,
|
||||
builder: (context, selectedSnapshot) {
|
||||
// Use snapshot data or fall back to current state
|
||||
final currentTrack = selectedSnapshot.data ?? widget.player.state.track;
|
||||
final currentTrack =
|
||||
selectedSnapshot.data ??
|
||||
widget.player.state.track;
|
||||
final selectedTrack = currentTrack.audio;
|
||||
final selectedId = selectedTrack?.id;
|
||||
|
||||
@@ -1221,7 +1223,9 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
|
||||
initialData: widget.player.state.track,
|
||||
builder: (context, selectedSnapshot) {
|
||||
// Use snapshot data or fall back to current state
|
||||
final currentTrack = selectedSnapshot.data ?? widget.player.state.track;
|
||||
final currentTrack =
|
||||
selectedSnapshot.data ??
|
||||
widget.player.state.track;
|
||||
final selectedTrack = currentTrack.subtitle;
|
||||
final selectedId = selectedTrack?.id;
|
||||
final isOffSelected = selectedId == 'no';
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
name: plezy
|
||||
description: "A beautiful Plex client for Flutter"
|
||||
publish_to: "none"
|
||||
version: 1.2.1+7
|
||||
version: 1.2.2+8
|
||||
|
||||
environment:
|
||||
sdk: ^3.8.1
|
||||
|
||||
Reference in New Issue
Block a user