import React, { useCallback, useEffect, useState } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, SafeAreaView, ScrollView, StatusBar, Platform, Image, } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { makeRedirectUri, useAuthRequest, ResponseType, CodeChallengeMethod } from 'expo-auth-session'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import { SimklService } from '../services/simklService'; import { useSettings } from '../hooks/useSettings'; import { logger } from '../utils/logger'; import { useTheme } from '../contexts/ThemeContext'; import { useSimklIntegration } from '../hooks/useSimklIntegration'; import { useTraktIntegration } from '../hooks/useTraktIntegration'; import CustomAlert from '../components/CustomAlert'; const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0; // Simkl configuration const SIMKL_CLIENT_ID = process.env.EXPO_PUBLIC_SIMKL_CLIENT_ID as string; const SIMKL_REDIRECT_URI = process.env.EXPO_PUBLIC_SIMKL_REDIRECT_URI || 'nuvio://auth/simkl'; const discovery = { authorizationEndpoint: 'https://simkl.com/oauth/authorize', tokenEndpoint: 'https://api.simkl.com/oauth/token', }; // For use with deep linking const redirectUri = makeRedirectUri({ scheme: 'nuvio', path: 'auth/simkl', }); const simklService = SimklService.getInstance(); const SimklSettingsScreen: React.FC = () => { const { settings } = useSettings(); const isDarkMode = settings.enableDarkMode; const navigation = useNavigation(); const [alertVisible, setAlertVisible] = useState(false); const [alertTitle, setAlertTitle] = useState(''); const [alertMessage, setAlertMessage] = useState(''); const { currentTheme } = useTheme(); const { isAuthenticated, isLoading, checkAuthStatus, refreshAuthStatus, userSettings, userStats } = useSimklIntegration(); const { isAuthenticated: isTraktAuthenticated } = useTraktIntegration(); const [isExchangingCode, setIsExchangingCode] = useState(false); const openAlert = (title: string, message: string) => { setAlertTitle(title); setAlertMessage(message); setAlertVisible(true); }; // Setup expo-auth-session hook const [request, response, promptAsync] = useAuthRequest( { clientId: SIMKL_CLIENT_ID, scopes: [], // Simkl doesn't strictly use scopes for basic access redirectUri: SIMKL_REDIRECT_URI, // Must match what is set in Simkl Dashboard responseType: ResponseType.Code, // codeChallengeMethod: CodeChallengeMethod.S256, // Simkl might not verify PKCE, but standard compliant }, discovery ); useEffect(() => { checkAuthStatus(); }, [checkAuthStatus]); // Handle the response from the auth request useEffect(() => { if (response) { if (response.type === 'success') { const { code } = response.params; setIsExchangingCode(true); logger.log('[SimklSettingsScreen] Auth code received, exchanging...'); simklService.exchangeCodeForToken(code) .then(success => { if (success) { refreshAuthStatus(); openAlert('Success', 'Connected to Simkl successfully!'); } else { openAlert('Error', 'Failed to connect to Simkl.'); } }) .catch(err => { logger.error('[SimklSettingsScreen] Token exchange error:', err); openAlert('Error', 'An error occurred during connection.'); }) .finally(() => setIsExchangingCode(false)); } else if (response.type === 'error') { openAlert('Error', 'Authentication error: ' + (response.error?.message || 'Unknown')); } } }, [response, refreshAuthStatus]); const handleSignIn = () => { if (!SIMKL_CLIENT_ID) { openAlert('Configuration Error', 'Simkl Client ID is missing in environment variables.'); return; } if (isTraktAuthenticated) { openAlert('Conflict', 'You cannot connect to Simkl while Trakt is connected. Please disconnect Trakt first.'); return; } promptAsync(); }; const handleSignOut = async () => { await simklService.logout(); refreshAuthStatus(); openAlert('Signed Out', 'You have disconnected from Simkl.'); }; return ( navigation.goBack()} style={styles.backButton} > Settings Simkl Integration {isLoading ? ( ) : isAuthenticated ? ( {userSettings?.user?.avatar ? ( ) : ( )} {userSettings?.user && ( {userSettings.user.name} )} {userSettings?.account?.type && ( {userSettings.account.type} Account )} Your watched items are syncing with Simkl. {userStats && ( {userStats.movies?.completed?.count || 0} Movies {(userStats.tv?.watching?.count || 0) + (userStats.tv?.completed?.count || 0)} TV Shows {userStats.anime?.completed?.count || 0} Anime {Math.round(((userStats.total_mins || 0) + (userStats.movies?.total_mins || 0) + (userStats.tv?.total_mins || 0) + (userStats.anime?.total_mins || 0)) / 60)}h Watched )} Disconnect ) : ( Connect Simkl Sync your watch history and track what you're watching. {isExchangingCode ? ( ) : ( Sign In with Simkl )} )} Nuvio is not affiliated with Simkl. setAlertVisible(false)} actions={[{ label: 'OK', onPress: () => setAlertVisible(false) }]} /> ); }; const styles = StyleSheet.create({ container: { flex: 1, }, header: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, paddingTop: Platform.OS === 'android' ? ANDROID_STATUSBAR_HEIGHT + 8 : 8, }, backButton: { flexDirection: 'row', alignItems: 'center', padding: 8, }, backText: { fontSize: 17, marginLeft: 8, }, headerTitle: { fontSize: 34, fontWeight: 'bold', paddingHorizontal: 16, marginBottom: 24, }, scrollView: { flex: 1, }, scrollContent: { paddingHorizontal: 16, paddingBottom: 32, }, card: { borderRadius: 12, overflow: 'hidden', padding: 20, marginBottom: 16, elevation: 2, shadowColor: '#000', shadowOpacity: 0.1, shadowRadius: 4, shadowOffset: { width: 0, height: 2 }, }, loadingContainer: { padding: 40, alignItems: 'center', }, signInContainer: { alignItems: 'center', paddingVertical: 20, }, signInTitle: { fontSize: 20, fontWeight: '600', marginBottom: 8, }, signInDescription: { textAlign: 'center', marginBottom: 20, fontSize: 15, }, profileContainer: { alignItems: 'stretch', paddingVertical: 8, }, profileHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 12, }, avatar: { width: 44, height: 44, borderRadius: 22, marginRight: 12, backgroundColor: '#00000010', }, avatarPlaceholder: { width: 44, height: 44, borderRadius: 22, alignItems: 'center', justifyContent: 'center', marginRight: 12, }, profileText: { flex: 1, }, statusTitle: { fontSize: 18, fontWeight: '700', marginBottom: 2, }, accountType: { fontSize: 13, fontWeight: '500', marginBottom: 8, }, statusDesc: { fontSize: 14, marginBottom: 8, }, statsGrid: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 16, marginVertical: 12, borderTopWidth: 1, borderBottomWidth: 1, }, statItem: { alignItems: 'center', }, statValue: { fontSize: 17, fontWeight: '700', marginBottom: 4, }, statLabel: { fontSize: 12, fontWeight: '500', }, button: { width: '100%', height: 48, borderRadius: 8, alignItems: 'center', justifyContent: 'center', }, buttonText: { fontSize: 16, fontWeight: '600', color: 'white', }, disclaimer: { fontSize: 12, textAlign: 'center', marginTop: 20, marginBottom: 8, }, logoSection: { alignItems: 'center', justifyContent: 'center', paddingVertical: 20, marginTop: 16, marginBottom: 8, }, logo: { width: 150, height: 30, }, logoContainer: { alignItems: 'center', justifyContent: 'center', paddingVertical: 28, marginBottom: 24, borderRadius: 12, elevation: 2, shadowColor: '#000', shadowOpacity: 0.1, shadowRadius: 4, shadowOffset: { width: 0, height: 2 }, }, }); export default SimklSettingsScreen;