import React, { useCallback, useEffect, useState } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, Image, SafeAreaView, ScrollView, StatusBar, Platform, } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { makeRedirectUri, useAuthRequest, ResponseType, Prompt, CodeChallengeMethod } from 'expo-auth-session'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import { traktService, TraktUser } from '../services/traktService'; import { useSettings } from '../hooks/useSettings'; import { logger } from '../utils/logger'; import TraktIcon from '../../assets/rating-icons/trakt.svg'; import { useTheme } from '../contexts/ThemeContext'; const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0; // Trakt configuration const TRAKT_CLIENT_ID = 'd7271f7dd57d8aeff63e99408610091a6b1ceac3b3a541d1031a48f429b7942c'; const discovery = { authorizationEndpoint: 'https://trakt.tv/oauth/authorize', tokenEndpoint: 'https://api.trakt.tv/oauth/token', }; // For use with deep linking const redirectUri = makeRedirectUri({ scheme: 'stremioexpo', path: 'auth/trakt', }); const TraktSettingsScreen: React.FC = () => { const { settings } = useSettings(); const isDarkMode = settings.enableDarkMode; const navigation = useNavigation(); const [isLoading, setIsLoading] = useState(true); const [isAuthenticated, setIsAuthenticated] = useState(false); const [userProfile, setUserProfile] = useState(null); const { currentTheme } = useTheme(); const checkAuthStatus = useCallback(async () => { setIsLoading(true); try { const authenticated = await traktService.isAuthenticated(); setIsAuthenticated(authenticated); if (authenticated) { const profile = await traktService.getUserProfile(); setUserProfile(profile); } else { setUserProfile(null); } } catch (error) { logger.error('[TraktSettingsScreen] Error checking auth status:', error); } finally { setIsLoading(false); } }, []); useEffect(() => { checkAuthStatus(); }, [checkAuthStatus]); // Setup expo-auth-session hook with PKCE const [request, response, promptAsync] = useAuthRequest( { clientId: TRAKT_CLIENT_ID, scopes: [], redirectUri: redirectUri, responseType: ResponseType.Code, usePKCE: true, codeChallengeMethod: CodeChallengeMethod.S256, }, discovery ); const [isExchangingCode, setIsExchangingCode] = useState(false); // Handle the response from the auth request useEffect(() => { if (response) { setIsExchangingCode(true); if (response.type === 'success' && request?.codeVerifier) { const { code } = response.params; logger.log('[TraktSettingsScreen] Auth code received:', code); traktService.exchangeCodeForToken(code, request.codeVerifier) .then(success => { if (success) { logger.log('[TraktSettingsScreen] Token exchange successful'); 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.'); } }) .catch(error => { logger.error('[TraktSettingsScreen] Token exchange error:', error); Alert.alert('Authentication Error', 'An error occurred during authentication.'); }) .finally(() => { setIsExchangingCode(false); }); } else if (response.type === 'error') { logger.error('[TraktSettingsScreen] Authentication error:', response.error); Alert.alert('Authentication Error', response.error?.message || 'An error occurred during authentication.'); setIsExchangingCode(false); } else { logger.log('[TraktSettingsScreen] Auth response type:', response.type); setIsExchangingCode(false); } } }, [response, checkAuthStatus, request?.codeVerifier, navigation]); const handleSignIn = () => { promptAsync(); // Trigger the authentication flow }; const handleSignOut = async () => { Alert.alert( 'Sign Out', 'Are you sure you want to sign out of your Trakt account?', [ { text: 'Cancel', style: 'cancel' }, { text: 'Sign Out', style: 'destructive', onPress: async () => { setIsLoading(true); try { await traktService.logout(); setIsAuthenticated(false); setUserProfile(null); } catch (error) { logger.error('[TraktSettingsScreen] Error signing out:', error); Alert.alert('Error', 'Failed to sign out of Trakt.'); } finally { setIsLoading(false); } } } ] ); }; return ( navigation.goBack()} style={styles.backButton} > Trakt Settings {isLoading ? ( ) : isAuthenticated && userProfile ? ( {userProfile.avatar ? ( ) : ( {userProfile.name?.charAt(0) || userProfile.username.charAt(0)} )} {userProfile.name || userProfile.username} @{userProfile.username} {userProfile.vip && ( VIP )} Joined {new Date(userProfile.joined_at).toLocaleDateString()} Sign Out ) : ( Connect with Trakt Sync your watch history, watchlist, and collection with Trakt.tv {isExchangingCode ? ( ) : ( Sign In with Trakt )} )} {isAuthenticated && ( Sync Settings Auto-sync playback progress Coming soon Import watched history Coming soon Sync Now (Coming Soon) )} ); }; const styles = StyleSheet.create({ container: { flex: 1, }, header: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 16, paddingTop: Platform.OS === 'android' ? ANDROID_STATUSBAR_HEIGHT + 16 : 16, }, backButton: { padding: 4, }, headerTitle: { fontSize: 22, fontWeight: '600', marginLeft: 16, }, scrollView: { flex: 1, }, scrollContent: { paddingHorizontal: 16, paddingBottom: 32, }, card: { borderRadius: 12, overflow: 'hidden', marginBottom: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 2, }, loadingContainer: { padding: 40, alignItems: 'center', justifyContent: 'center', }, signInContainer: { padding: 24, alignItems: 'center', }, traktLogo: { width: 120, height: 120, marginBottom: 20, }, signInTitle: { fontSize: 20, fontWeight: '600', marginBottom: 8, textAlign: 'center', }, signInDescription: { fontSize: 15, textAlign: 'center', marginBottom: 24, paddingHorizontal: 20, }, button: { width: '100%', height: 44, borderRadius: 8, alignItems: 'center', justifyContent: 'center', marginTop: 8, }, signOutButton: { marginTop: 20, }, buttonText: { fontSize: 16, fontWeight: '500', color: 'white', }, profileContainer: { padding: 20, }, profileHeader: { flexDirection: 'row', alignItems: 'center', }, avatar: { width: 64, height: 64, borderRadius: 32, }, avatarPlaceholder: { width: 64, height: 64, borderRadius: 32, alignItems: 'center', justifyContent: 'center', }, avatarText: { fontSize: 26, fontWeight: 'bold', color: 'white', }, profileInfo: { marginLeft: 16, flex: 1, }, profileName: { fontSize: 18, fontWeight: '600', marginBottom: 4, }, profileUsername: { fontSize: 14, }, vipBadge: { marginTop: 4, paddingHorizontal: 8, paddingVertical: 2, backgroundColor: '#FFD700', borderRadius: 4, alignSelf: 'flex-start', }, vipText: { fontSize: 10, fontWeight: 'bold', color: '#000', }, statsContainer: { marginTop: 16, paddingTop: 16, borderTopWidth: 0.5, borderTopColor: 'rgba(150,150,150,0.2)', }, joinedDate: { fontSize: 14, }, settingsSection: { padding: 20, }, sectionTitle: { fontSize: 18, fontWeight: '600', marginBottom: 16, }, settingItem: { marginBottom: 16, }, settingLabel: { fontSize: 15, fontWeight: '500', marginBottom: 4, }, settingDescription: { fontSize: 14, }, }); export default TraktSettingsScreen;