mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
Enhance Trakt integration by adding refreshAuthStatus function and ProfilesSettings screen
This update introduces a new `refreshAuthStatus` function in the TraktContext and hooks, allowing for manual refresh of authentication status. Additionally, a new `ProfilesSettings` screen has been added to the navigation stack, enabling users to manage profiles. The SettingsScreen has been updated to trigger a refresh of the auth status when focused, improving user experience. Styling adjustments have been made to accommodate the new profiles feature.
This commit is contained in:
parent
953556c65a
commit
64193b4154
6 changed files with 634 additions and 4 deletions
|
|
@ -9,6 +9,7 @@ interface TraktContextProps {
|
|||
watchedMovies: TraktWatchedItem[];
|
||||
watchedShows: TraktWatchedItem[];
|
||||
checkAuthStatus: () => Promise<void>;
|
||||
refreshAuthStatus: () => Promise<void>;
|
||||
loadWatchedItems: () => Promise<void>;
|
||||
isMovieWatched: (imdbId: string) => Promise<boolean>;
|
||||
isEpisodeWatched: (imdbId: string, season: number, episode: number) => Promise<boolean>;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export function useTraktIntegration() {
|
|||
const [userProfile, setUserProfile] = useState<TraktUser | null>(null);
|
||||
const [watchedMovies, setWatchedMovies] = useState<TraktWatchedItem[]>([]);
|
||||
const [watchedShows, setWatchedShows] = useState<TraktWatchedItem[]>([]);
|
||||
const [lastAuthCheck, setLastAuthCheck] = useState<number>(Date.now());
|
||||
|
||||
// Check authentication status
|
||||
const checkAuthStatus = useCallback(async () => {
|
||||
|
|
@ -22,6 +23,9 @@ export function useTraktIntegration() {
|
|||
} else {
|
||||
setUserProfile(null);
|
||||
}
|
||||
|
||||
// Update the last auth check timestamp to trigger dependent components to update
|
||||
setLastAuthCheck(Date.now());
|
||||
} catch (error) {
|
||||
logger.error('[useTraktIntegration] Error checking auth status:', error);
|
||||
} finally {
|
||||
|
|
@ -29,6 +33,12 @@ export function useTraktIntegration() {
|
|||
}
|
||||
}, []);
|
||||
|
||||
// Function to force refresh the auth status
|
||||
const refreshAuthStatus = useCallback(async () => {
|
||||
logger.log('[useTraktIntegration] Refreshing auth status');
|
||||
await checkAuthStatus();
|
||||
}, [checkAuthStatus]);
|
||||
|
||||
// Load watched items
|
||||
const loadWatchedItems = useCallback(async () => {
|
||||
if (!isAuthenticated) return;
|
||||
|
|
@ -141,6 +151,7 @@ export function useTraktIntegration() {
|
|||
isMovieWatched,
|
||||
isEpisodeWatched,
|
||||
markMovieAsWatched,
|
||||
markEpisodeAsWatched
|
||||
markEpisodeAsWatched,
|
||||
refreshAuthStatus
|
||||
};
|
||||
}
|
||||
|
|
@ -38,6 +38,7 @@ import TraktSettingsScreen from '../screens/TraktSettingsScreen';
|
|||
import PlayerSettingsScreen from '../screens/PlayerSettingsScreen';
|
||||
import LogoSourceSettings from '../screens/LogoSourceSettings';
|
||||
import ThemeScreen from '../screens/ThemeScreen';
|
||||
import ProfilesScreen from '../screens/ProfilesScreen';
|
||||
|
||||
// Stack navigator types
|
||||
export type RootStackParamList = {
|
||||
|
|
@ -95,6 +96,7 @@ export type RootStackParamList = {
|
|||
PlayerSettings: undefined;
|
||||
LogoSourceSettings: undefined;
|
||||
ThemeSettings: undefined;
|
||||
ProfilesSettings: undefined;
|
||||
};
|
||||
|
||||
export type RootStackNavigationProp = NativeStackNavigationProp<RootStackParamList>;
|
||||
|
|
@ -858,6 +860,21 @@ const AppNavigator = () => {
|
|||
},
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="ProfilesSettings"
|
||||
component={ProfilesScreen}
|
||||
options={{
|
||||
animation: 'fade',
|
||||
animationDuration: 200,
|
||||
presentation: 'card',
|
||||
gestureEnabled: true,
|
||||
gestureDirection: 'horizontal',
|
||||
headerShown: false,
|
||||
contentStyle: {
|
||||
backgroundColor: currentTheme.colors.darkBackground,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
</PaperProvider>
|
||||
</SafeAreaProvider>
|
||||
|
|
|
|||
441
src/screens/ProfilesScreen.tsx
Normal file
441
src/screens/ProfilesScreen.tsx
Normal file
|
|
@ -0,0 +1,441 @@
|
|||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
FlatList,
|
||||
Alert,
|
||||
StatusBar,
|
||||
Platform,
|
||||
SafeAreaView,
|
||||
TextInput,
|
||||
Modal
|
||||
} from 'react-native';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import { useTraktContext } from '../contexts/TraktContext';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0;
|
||||
const PROFILE_STORAGE_KEY = 'user_profiles';
|
||||
|
||||
interface Profile {
|
||||
id: string;
|
||||
name: string;
|
||||
avatar?: string;
|
||||
isActive: boolean;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
const ProfilesScreen: React.FC = () => {
|
||||
const navigation = useNavigation();
|
||||
const { currentTheme } = useTheme();
|
||||
const { isAuthenticated, userProfile, refreshAuthStatus } = useTraktContext();
|
||||
|
||||
const [profiles, setProfiles] = useState<Profile[]>([]);
|
||||
const [showAddModal, setShowAddModal] = useState(false);
|
||||
const [newProfileName, setNewProfileName] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
// Load profiles from AsyncStorage
|
||||
const loadProfiles = useCallback(async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const storedProfiles = await AsyncStorage.getItem(PROFILE_STORAGE_KEY);
|
||||
if (storedProfiles) {
|
||||
setProfiles(JSON.parse(storedProfiles));
|
||||
} else {
|
||||
// If no profiles exist, create a default one with the Trakt username
|
||||
const defaultProfile: Profile = {
|
||||
id: new Date().getTime().toString(),
|
||||
name: userProfile?.username || 'Default',
|
||||
isActive: true,
|
||||
createdAt: new Date().getTime()
|
||||
};
|
||||
setProfiles([defaultProfile]);
|
||||
await AsyncStorage.setItem(PROFILE_STORAGE_KEY, JSON.stringify([defaultProfile]));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading profiles:', error);
|
||||
Alert.alert('Error', 'Failed to load profiles');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [userProfile]);
|
||||
|
||||
// Add a focus listener to refresh authentication status
|
||||
useEffect(() => {
|
||||
const unsubscribe = navigation.addListener('focus', () => {
|
||||
// Refresh the auth status when the screen comes into focus
|
||||
refreshAuthStatus().then(() => {
|
||||
if (isAuthenticated) {
|
||||
loadProfiles();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, [navigation, refreshAuthStatus, isAuthenticated, loadProfiles]);
|
||||
|
||||
// Save profiles to AsyncStorage
|
||||
const saveProfiles = useCallback(async (updatedProfiles: Profile[]) => {
|
||||
try {
|
||||
await AsyncStorage.setItem(PROFILE_STORAGE_KEY, JSON.stringify(updatedProfiles));
|
||||
} catch (error) {
|
||||
console.error('Error saving profiles:', error);
|
||||
Alert.alert('Error', 'Failed to save profiles');
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Only authenticated users can access profiles
|
||||
if (!isAuthenticated) {
|
||||
navigation.goBack();
|
||||
return;
|
||||
}
|
||||
|
||||
loadProfiles();
|
||||
}, [isAuthenticated, loadProfiles, navigation]);
|
||||
|
||||
const handleAddProfile = useCallback(() => {
|
||||
if (!newProfileName.trim()) {
|
||||
Alert.alert('Error', 'Please enter a profile name');
|
||||
return;
|
||||
}
|
||||
|
||||
const newProfile: Profile = {
|
||||
id: new Date().getTime().toString(),
|
||||
name: newProfileName.trim(),
|
||||
isActive: false,
|
||||
createdAt: new Date().getTime()
|
||||
};
|
||||
|
||||
const updatedProfiles = [...profiles, newProfile];
|
||||
setProfiles(updatedProfiles);
|
||||
saveProfiles(updatedProfiles);
|
||||
setNewProfileName('');
|
||||
setShowAddModal(false);
|
||||
}, [newProfileName, profiles, saveProfiles]);
|
||||
|
||||
const handleSelectProfile = useCallback((id: string) => {
|
||||
const updatedProfiles = profiles.map(profile => ({
|
||||
...profile,
|
||||
isActive: profile.id === id
|
||||
}));
|
||||
|
||||
setProfiles(updatedProfiles);
|
||||
saveProfiles(updatedProfiles);
|
||||
}, [profiles, saveProfiles]);
|
||||
|
||||
const handleDeleteProfile = useCallback((id: string) => {
|
||||
// Prevent deleting the active profile
|
||||
const isActiveProfile = profiles.find(p => p.id === id)?.isActive;
|
||||
if (isActiveProfile) {
|
||||
Alert.alert('Error', 'Cannot delete the active profile. Switch to another profile first.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent deleting the last profile
|
||||
if (profiles.length <= 1) {
|
||||
Alert.alert('Error', 'Cannot delete the only profile');
|
||||
return;
|
||||
}
|
||||
|
||||
Alert.alert(
|
||||
'Delete Profile',
|
||||
'Are you sure you want to delete this profile? This action cannot be undone.',
|
||||
[
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
{
|
||||
text: 'Delete',
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
const updatedProfiles = profiles.filter(profile => profile.id !== id);
|
||||
setProfiles(updatedProfiles);
|
||||
saveProfiles(updatedProfiles);
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
}, [profiles, saveProfiles]);
|
||||
|
||||
const handleBack = () => {
|
||||
navigation.goBack();
|
||||
};
|
||||
|
||||
const renderItem = ({ item }: { item: Profile }) => (
|
||||
<View style={styles.profileItem}>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.profileContent,
|
||||
item.isActive && {
|
||||
backgroundColor: `${currentTheme.colors.primary}30`,
|
||||
borderColor: currentTheme.colors.primary
|
||||
}
|
||||
]}
|
||||
onPress={() => handleSelectProfile(item.id)}
|
||||
>
|
||||
<View style={styles.avatarContainer}>
|
||||
<MaterialIcons
|
||||
name="account-circle"
|
||||
size={40}
|
||||
color={item.isActive ? currentTheme.colors.primary : currentTheme.colors.text}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.profileInfo}>
|
||||
<Text style={[styles.profileName, { color: currentTheme.colors.text }]}>
|
||||
{item.name}
|
||||
</Text>
|
||||
{item.isActive && (
|
||||
<Text style={[styles.activeLabel, { color: currentTheme.colors.primary }]}>
|
||||
Active
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
{!item.isActive && (
|
||||
<TouchableOpacity
|
||||
style={styles.deleteButton}
|
||||
onPress={() => handleDeleteProfile(item.id)}
|
||||
>
|
||||
<MaterialIcons name="delete" size={24} color={currentTheme.colors.error} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||
<StatusBar barStyle="light-content" backgroundColor="transparent" translucent />
|
||||
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity
|
||||
onPress={handleBack}
|
||||
style={styles.backButton}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<MaterialIcons
|
||||
name="arrow-back"
|
||||
size={24}
|
||||
color={currentTheme.colors.text}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<Text
|
||||
style={[
|
||||
styles.headerTitle,
|
||||
{ color: currentTheme.colors.text },
|
||||
]}
|
||||
>
|
||||
Profiles
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.content}>
|
||||
<FlatList
|
||||
data={profiles}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={(item) => item.id}
|
||||
contentContainerStyle={styles.listContent}
|
||||
ListHeaderComponent={
|
||||
<Text style={[styles.sectionTitle, { color: currentTheme.colors.textMuted }]}>
|
||||
MANAGE PROFILES
|
||||
</Text>
|
||||
}
|
||||
ListFooterComponent={
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.addButton,
|
||||
{ backgroundColor: currentTheme.colors.elevation2 }
|
||||
]}
|
||||
onPress={() => setShowAddModal(true)}
|
||||
>
|
||||
<MaterialIcons name="add" size={24} color={currentTheme.colors.primary} />
|
||||
<Text style={[styles.addButtonText, { color: currentTheme.colors.text }]}>
|
||||
Add New Profile
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Modal for adding a new profile */}
|
||||
<Modal
|
||||
visible={showAddModal}
|
||||
transparent
|
||||
animationType="fade"
|
||||
onRequestClose={() => setShowAddModal(false)}
|
||||
>
|
||||
<View style={styles.modalOverlay}>
|
||||
<View style={[styles.modalContent, { backgroundColor: currentTheme.colors.elevation2 }]}>
|
||||
<Text style={[styles.modalTitle, { color: currentTheme.colors.text }]}>
|
||||
Create New Profile
|
||||
</Text>
|
||||
|
||||
<TextInput
|
||||
style={[
|
||||
styles.input,
|
||||
{
|
||||
backgroundColor: `${currentTheme.colors.textMuted}20`,
|
||||
color: currentTheme.colors.text,
|
||||
borderColor: currentTheme.colors.border
|
||||
}
|
||||
]}
|
||||
placeholder="Profile Name"
|
||||
placeholderTextColor={currentTheme.colors.textMuted}
|
||||
value={newProfileName}
|
||||
onChangeText={setNewProfileName}
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
<View style={styles.modalButtons}>
|
||||
<TouchableOpacity
|
||||
style={[styles.modalButton, styles.cancelButton]}
|
||||
onPress={() => {
|
||||
setNewProfileName('');
|
||||
setShowAddModal(false);
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: currentTheme.colors.textMuted }}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.modalButton,
|
||||
styles.createButton,
|
||||
{ backgroundColor: currentTheme.colors.primary }
|
||||
]}
|
||||
onPress={handleAddProfile}
|
||||
>
|
||||
<Text style={{ color: '#fff' }}>Create</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 16,
|
||||
paddingTop: Platform.OS === 'android' ? ANDROID_STATUSBAR_HEIGHT + 16 : 16,
|
||||
paddingBottom: 8,
|
||||
},
|
||||
backButton: {
|
||||
padding: 8,
|
||||
marginRight: 16,
|
||||
borderRadius: 20,
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: '600',
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 13,
|
||||
fontWeight: '600',
|
||||
marginTop: 24,
|
||||
marginBottom: 12,
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
listContent: {
|
||||
paddingBottom: 24,
|
||||
},
|
||||
profileItem: {
|
||||
marginBottom: 12,
|
||||
},
|
||||
profileContent: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: 12,
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255,255,255,0.1)',
|
||||
},
|
||||
avatarContainer: {
|
||||
marginRight: 16,
|
||||
},
|
||||
profileInfo: {
|
||||
flex: 1,
|
||||
},
|
||||
profileName: {
|
||||
fontSize: 16,
|
||||
fontWeight: '500',
|
||||
},
|
||||
activeLabel: {
|
||||
fontSize: 12,
|
||||
marginTop: 4,
|
||||
fontWeight: '500',
|
||||
},
|
||||
deleteButton: {
|
||||
padding: 8,
|
||||
},
|
||||
addButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: 16,
|
||||
borderRadius: 12,
|
||||
marginTop: 12,
|
||||
},
|
||||
addButtonText: {
|
||||
fontSize: 16,
|
||||
fontWeight: '500',
|
||||
marginLeft: 8,
|
||||
},
|
||||
modalOverlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0,0,0,0.7)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: 24,
|
||||
},
|
||||
modalContent: {
|
||||
width: '100%',
|
||||
borderRadius: 16,
|
||||
padding: 24,
|
||||
},
|
||||
modalTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
marginBottom: 24,
|
||||
textAlign: 'center',
|
||||
},
|
||||
input: {
|
||||
width: '100%',
|
||||
height: 50,
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 16,
|
||||
marginBottom: 24,
|
||||
borderWidth: 1,
|
||||
},
|
||||
modalButtons: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
modalButton: {
|
||||
flex: 1,
|
||||
height: 44,
|
||||
borderRadius: 8,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
cancelButton: {
|
||||
marginRight: 8,
|
||||
},
|
||||
createButton: {
|
||||
marginLeft: 8,
|
||||
},
|
||||
});
|
||||
|
||||
export default ProfilesScreen;
|
||||
|
|
@ -125,10 +125,27 @@ const SettingsScreen: React.FC = () => {
|
|||
const { settings, updateSetting } = useSettings();
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
const { lastUpdate } = useCatalogContext();
|
||||
const { isAuthenticated, userProfile } = useTraktContext();
|
||||
const { isAuthenticated, userProfile, refreshAuthStatus } = useTraktContext();
|
||||
const { currentTheme } = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
// Add a useEffect to check authentication status on focus
|
||||
useEffect(() => {
|
||||
// This will reload the Trakt auth status whenever the settings screen is focused
|
||||
const unsubscribe = navigation.addListener('focus', () => {
|
||||
// Force a re-render when returning to this screen
|
||||
// This will reflect the updated isAuthenticated state from the TraktContext
|
||||
// Refresh auth status
|
||||
if (isAuthenticated || userProfile) {
|
||||
// Just to be cautious, log the current state
|
||||
console.log('SettingsScreen focused, refreshing auth status. Current state:', { isAuthenticated, userProfile: userProfile?.username });
|
||||
}
|
||||
refreshAuthStatus();
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, [navigation, isAuthenticated, userProfile, refreshAuthStatus]);
|
||||
|
||||
// States for dynamic content
|
||||
const [addonCount, setAddonCount] = useState<number>(0);
|
||||
const [catalogCount, setCatalogCount] = useState<number>(0);
|
||||
|
|
@ -268,6 +285,81 @@ const SettingsScreen: React.FC = () => {
|
|||
/>
|
||||
</SettingsCard>
|
||||
|
||||
<SettingsCard title="Profiles">
|
||||
{isAuthenticated ? (
|
||||
<SettingItem
|
||||
title="Manage Profiles"
|
||||
description="Create and switch between profiles"
|
||||
icon="people"
|
||||
renderControl={ChevronRight}
|
||||
onPress={() => navigation.navigate('ProfilesSettings')}
|
||||
isLast={true}
|
||||
/>
|
||||
) : (
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.profileLockContainer,
|
||||
{
|
||||
backgroundColor: `${currentTheme.colors.primary}10`,
|
||||
borderWidth: 1,
|
||||
borderColor: `${currentTheme.colors.primary}30`
|
||||
}
|
||||
]}
|
||||
activeOpacity={1}
|
||||
>
|
||||
<View style={styles.profileLockContent}>
|
||||
<MaterialIcons name="lock-outline" size={24} color={currentTheme.colors.primary} />
|
||||
<View style={styles.profileLockTextContainer}>
|
||||
<Text style={[styles.profileLockTitle, { color: currentTheme.colors.text }]}>
|
||||
Sign in to use Profiles
|
||||
</Text>
|
||||
<Text style={[styles.profileLockDescription, { color: currentTheme.colors.textMuted }]}>
|
||||
Create multiple profiles for different users and preferences
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.profileBenefits}>
|
||||
<View style={styles.benefitCol}>
|
||||
<View style={styles.benefitItem}>
|
||||
<MaterialIcons name="check-circle" size={16} color={currentTheme.colors.primary} />
|
||||
<Text style={[styles.benefitText, { color: currentTheme.colors.textMuted }]}>
|
||||
Separate watchlists
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.benefitItem}>
|
||||
<MaterialIcons name="check-circle" size={16} color={currentTheme.colors.primary} />
|
||||
<Text style={[styles.benefitText, { color: currentTheme.colors.textMuted }]}>
|
||||
Content preferences
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.benefitCol}>
|
||||
<View style={styles.benefitItem}>
|
||||
<MaterialIcons name="check-circle" size={16} color={currentTheme.colors.primary} />
|
||||
<Text style={[styles.benefitText, { color: currentTheme.colors.textMuted }]}>
|
||||
Personalized recommendations
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.benefitItem}>
|
||||
<MaterialIcons name="check-circle" size={16} color={currentTheme.colors.primary} />
|
||||
<Text style={[styles.benefitText, { color: currentTheme.colors.textMuted }]}>
|
||||
Individual viewing history
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={[styles.loginButton, { backgroundColor: currentTheme.colors.primary }]}
|
||||
activeOpacity={0.7}
|
||||
onPress={() => navigation.navigate('TraktSettings')}
|
||||
>
|
||||
<Text style={styles.loginButtonText}>Connect with Trakt</Text>
|
||||
<MaterialIcons name="arrow-forward" size={18} color="#FFFFFF" style={styles.loginButtonIcon} />
|
||||
</TouchableOpacity>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</SettingsCard>
|
||||
|
||||
<SettingsCard title="Appearance">
|
||||
<SettingItem
|
||||
title="Theme"
|
||||
|
|
@ -567,6 +659,62 @@ const styles = StyleSheet.create({
|
|||
fontSize: 14,
|
||||
fontWeight: '500',
|
||||
},
|
||||
profileLockContainer: {
|
||||
padding: 16,
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden',
|
||||
marginVertical: 8,
|
||||
},
|
||||
profileLockContent: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
profileLockTextContainer: {
|
||||
flex: 1,
|
||||
marginHorizontal: 12,
|
||||
},
|
||||
profileLockTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
marginBottom: 4,
|
||||
},
|
||||
profileLockDescription: {
|
||||
fontSize: 14,
|
||||
opacity: 0.8,
|
||||
},
|
||||
profileBenefits: {
|
||||
flexDirection: 'row',
|
||||
marginTop: 16,
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
benefitCol: {
|
||||
flex: 1,
|
||||
},
|
||||
benefitItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: 8,
|
||||
},
|
||||
benefitText: {
|
||||
fontSize: 14,
|
||||
marginLeft: 8,
|
||||
},
|
||||
loginButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: 8,
|
||||
paddingVertical: 12,
|
||||
marginTop: 16,
|
||||
},
|
||||
loginButtonText: {
|
||||
color: '#FFFFFF',
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
loginButtonIcon: {
|
||||
marginLeft: 8,
|
||||
},
|
||||
});
|
||||
|
||||
export default SettingsScreen;
|
||||
|
|
@ -94,7 +94,19 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
.then(success => {
|
||||
if (success) {
|
||||
logger.log('[TraktSettingsScreen] Token exchange successful');
|
||||
checkAuthStatus();
|
||||
checkAuthStatus().then(() => {
|
||||
// Show success message
|
||||
Alert.alert(
|
||||
'Successfully Connected',
|
||||
'Your Trakt account has been connected successfully.',
|
||||
[
|
||||
{
|
||||
text: 'OK',
|
||||
onPress: () => navigation.goBack()
|
||||
}
|
||||
]
|
||||
);
|
||||
});
|
||||
} else {
|
||||
logger.error('[TraktSettingsScreen] Token exchange failed');
|
||||
Alert.alert('Authentication Error', 'Failed to complete authentication with Trakt.');
|
||||
|
|
@ -116,7 +128,7 @@ const TraktSettingsScreen: React.FC = () => {
|
|||
setIsExchangingCode(false);
|
||||
}
|
||||
}
|
||||
}, [response, checkAuthStatus, request?.codeVerifier]);
|
||||
}, [response, checkAuthStatus, request?.codeVerifier, navigation]);
|
||||
|
||||
const handleSignIn = () => {
|
||||
promptAsync(); // Trigger the authentication flow
|
||||
|
|
|
|||
Loading…
Reference in a new issue