import React, { useCallback, useEffect, useState } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, SafeAreaView, ScrollView, StatusBar, Platform, Switch, Image, } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import FastImage from '@d11/react-native-fast-image'; import { MalAuth } from '../services/mal/MalAuth'; import { MalApiService } from '../services/mal/MalApi'; import { MalSync } from '../services/mal/MalSync'; import { mmkvStorage } from '../services/mmkvStorage'; import { MalUser } from '../types/mal'; import { useTheme } from '../contexts/ThemeContext'; import { colors } from '../styles'; import CustomAlert from '../components/CustomAlert'; import { useTranslation } from 'react-i18next'; const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0; const MalSettingsScreen: React.FC = () => { const { t } = useTranslation(); const navigation = useNavigation(); const { currentTheme } = useTheme(); const [isLoading, setIsLoading] = useState(true); const [isAuthenticated, setIsAuthenticated] = useState(false); const [userProfile, setUserProfile] = useState(null); const [syncEnabled, setSyncEnabled] = useState(mmkvStorage.getBoolean('mal_enabled') ?? true); const [autoUpdateEnabled, setAutoUpdateEnabled] = useState(mmkvStorage.getBoolean('mal_auto_update') ?? true); const [autoAddEnabled, setAutoAddEnabled] = useState(mmkvStorage.getBoolean('mal_auto_add') ?? true); const [autoLibrarySyncEnabled, setAutoLibrarySyncEnabled] = useState(mmkvStorage.getBoolean('mal_auto_sync_to_library') ?? false); const [includeNsfwEnabled, setIncludeNsfwEnabled] = useState(mmkvStorage.getBoolean('mal_include_nsfw') ?? true); const [alertVisible, setAlertVisible] = useState(false); const [alertTitle, setAlertTitle] = useState(''); const [alertMessage, setAlertMessage] = useState(''); const [alertActions, setAlertActions] = useState void }>>([]); const openAlert = (title: string, message: string, actions?: any[]) => { setAlertTitle(title); setAlertMessage(message); setAlertActions(actions || [{ label: t('common.ok'), onPress: () => setAlertVisible(false) }]); setAlertVisible(true); }; const checkAuthStatus = useCallback(async () => { setIsLoading(true); try { // Initialize Auth (loads from storage) const token = MalAuth.getToken(); if (token && !MalAuth.isTokenExpired(token)) { setIsAuthenticated(true); // Fetch Profile const profile = await MalApiService.getUserInfo(); setUserProfile(profile); } else if (token && MalAuth.isTokenExpired(token)) { // Try refresh const refreshed = await MalAuth.refreshToken(); if (refreshed) { setIsAuthenticated(true); const profile = await MalApiService.getUserInfo(); setUserProfile(profile); } else { setIsAuthenticated(false); setUserProfile(null); } } else { setIsAuthenticated(false); setUserProfile(null); } } catch (error) { console.error('[MalSettings] Auth check failed', error); setIsAuthenticated(false); } finally { setIsLoading(false); } }, []); useEffect(() => { checkAuthStatus(); }, [checkAuthStatus]); const handleSignIn = async () => { setIsLoading(true); try { const result = await MalAuth.login(); if (result === true) { await checkAuthStatus(); openAlert(t('common.success'), t('mal.connected')); } else { const errorMessage = typeof result === 'string' ? result : t('mal.error_connect'); openAlert(t('common.error'), errorMessage); } } catch (e: any) { console.error(e); openAlert(t('common.error'), t('mal.error_sign_in', { message: e.message || t('common.unknown') })); } finally { setIsLoading(false); } }; const handleSignOut = () => { openAlert(t('mal.sign_out'), t('mal.sign_out_confirm'), [ { label: t('common.cancel'), onPress: () => setAlertVisible(false) }, { label: t('mal.sign_out'), onPress: () => { MalAuth.clearToken(); setIsAuthenticated(false); setUserProfile(null); setAlertVisible(false); } } ]); }; const toggleSync = (val: boolean) => { setSyncEnabled(val); mmkvStorage.setBoolean('mal_enabled', val); }; const toggleAutoUpdate = (val: boolean) => { setAutoUpdateEnabled(val); mmkvStorage.setBoolean('mal_auto_update', val); }; const toggleAutoAdd = (val: boolean) => { setAutoAddEnabled(val); mmkvStorage.setBoolean('mal_auto_add', val); }; const toggleAutoLibrarySync = (val: boolean) => { setAutoLibrarySyncEnabled(val); mmkvStorage.setBoolean('mal_auto_sync_to_library', val); }; const toggleIncludeNsfw = (val: boolean) => { setIncludeNsfwEnabled(val); mmkvStorage.setBoolean('mal_include_nsfw', val); }; return ( navigation.goBack()} style={styles.backButton} > {t('common.settings')} {t('mal.title')} {isLoading ? ( ) : isAuthenticated && userProfile ? ( {userProfile.picture ? ( ) : ( {userProfile.name.charAt(0)} )} {userProfile.name} ID: {userProfile.id} {userProfile.location && ( {userProfile.location} )} {userProfile.birthday && ( {userProfile.birthday} )} {userProfile.anime_statistics && ( {userProfile.anime_statistics.num_items} {t('mal.stat_total')} {userProfile.anime_statistics.num_days_watched.toFixed(1)} {t('mal.stat_days')} {userProfile.anime_statistics.mean_score.toFixed(1)} {t('mal.stat_mean')} {t('mal.watching')} {userProfile.anime_statistics.num_items_watching} {t('mal.completed')} {userProfile.anime_statistics.num_items_completed} {t('mal.on_hold')} {userProfile.anime_statistics.num_items_on_hold} {t('mal.dropped')} {userProfile.anime_statistics.num_items_dropped} )} { setIsLoading(true); try { const synced = await MalSync.syncMalToLibrary(); if (synced) { openAlert(t('mal.sync_complete'), t('mal.sync_complete_msg')); } else { openAlert(t('mal.sync_failed'), t('mal.sync_failed_msg')); } } catch { openAlert(t('mal.sync_failed'), t('mal.sync_failed_msg')); } finally { setIsLoading(false); } }} > {t('mal.sync_button')} {t('mal.sign_out')} ) : ( {t('mal.connect_title')} {t('mal.connect_desc')} {t('mal.sign_in')} )} {isAuthenticated && ( {t('mal.sync_settings')} {t('mal.enable_sync')} {t('mal.enable_sync_desc')} {t('mal.auto_episode')} {t('mal.auto_episode_desc')} {t('mal.auto_add')} {t('mal.auto_add_desc')} {t('mal.auto_sync_library')} {t('mal.auto_sync_library_desc')} {t('mal.include_nsfw')} {t('mal.include_nsfw_desc')} )} setAlertVisible(false)} actions={alertActions} /> ); }; 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', marginBottom: 16, elevation: 2, }, loadingContainer: { padding: 40, alignItems: 'center' }, signInContainer: { padding: 24, alignItems: 'center' }, signInTitle: { fontSize: 20, fontWeight: '600', marginBottom: 8 }, signInDescription: { fontSize: 15, textAlign: 'center', marginBottom: 24 }, button: { width: '100%', height: 44, borderRadius: 8, alignItems: 'center', justifyContent: 'center', marginTop: 8, }, 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: 24, color: 'white', fontWeight: 'bold' }, profileInfo: { marginLeft: 16, flex: 1 }, profileName: { fontSize: 18, fontWeight: '600' }, profileDetailRow: { flexDirection: 'row', alignItems: 'center', marginTop: 2 }, profileDetailText: { fontSize: 12, marginLeft: 4 }, statsContainer: { marginTop: 20 }, statsRow: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 16 }, statBox: { alignItems: 'center', flex: 1 }, statValue: { fontSize: 18, fontWeight: 'bold' }, statLabel: { fontSize: 12, marginTop: 2 }, statGrid: { flexDirection: 'row', flexWrap: 'wrap', borderTopWidth: 1, paddingTop: 16, gap: 12 }, statGridItem: { flexDirection: 'row', alignItems: 'center', width: '45%', marginBottom: 8 }, statusDot: { width: 8, height: 8, borderRadius: 4, marginRight: 8 }, statGridLabel: { fontSize: 13, flex: 1 }, statGridValue: { fontSize: 13, fontWeight: '600' }, actionButtonsRow: { flexDirection: 'row', marginTop: 20 }, smallButton: { height: 36, borderRadius: 18, alignItems: 'center', justifyContent: 'center', flexDirection: 'row', }, signOutButton: { marginTop: 20 }, settingsSection: { padding: 20 }, sectionTitle: { fontSize: 18, fontWeight: '600', marginBottom: 16 }, settingItem: { marginBottom: 16 }, settingContent: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, settingTextContainer: { flex: 1, marginRight: 16 }, settingLabel: { fontSize: 15, fontWeight: '500', marginBottom: 4 }, settingDescription: { fontSize: 14 }, noteContainer: { flexDirection: 'row', alignItems: 'center', padding: 12, borderRadius: 8, borderWidth: 1, marginBottom: 20, marginTop: -8, }, noteText: { fontSize: 13, marginLeft: 8, flex: 1, lineHeight: 18, }, }); export default MalSettingsScreen;