diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index bab3514..d9c03e7 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -4,10 +4,11 @@ import { Alert, ScrollView, StyleSheet, - Text, TouchableOpacity, View, } from "react-native"; +import { AppText as Text } from "@/src/components/common/AppText"; +import { useDynamicFontSize } from "@/src/hooks/useDynamicFontSize"; import { sampleCourse } from "@/src/data/sampleCourse"; import { useAppStore } from "@/src/store"; @@ -17,6 +18,7 @@ import { CourseCardSkeleton } from "@/src/components/mobile/CourseCardSkeleton"; export default function HomeScreen() { const router = useRouter(); const { isLoading, setLoading } = useAppStore(); + const { scale } = useDynamicFontSize(); const fetchHomeData = () => { setLoading(true); @@ -74,7 +76,7 @@ export default function HomeScreen() { > {/* Header */} - ? + ? Welcome to TeachLink Share and consume knowledge on the go @@ -95,7 +97,7 @@ export default function HomeScreen() { accessibilityHint="Opens the course viewer with a sample lesson" > - ? + ? Start Learning Open course viewer @@ -111,14 +113,14 @@ export default function HomeScreen() { accessibilityHint="Navigates to the search screen" > - ? + ? Search Find courses and lessons - {">"} + {">"} diff --git a/app/settings.tsx b/app/settings.tsx index c929676..08e9c70 100644 --- a/app/settings.tsx +++ b/app/settings.tsx @@ -1,6 +1,7 @@ import { useAppStore } from '@/src/store'; import React from 'react'; -import { Switch, Text, View } from 'react-native'; +import { Switch, View } from 'react-native'; +import { AppText } from '@/src/components/common/AppText'; export default function SettingsScreen() { const { theme, setTheme } = useAppStore(); @@ -8,14 +9,20 @@ export default function SettingsScreen() { return ( - + Settings - + - + Dark Mode - + setTheme(value ? 'dark' : 'light')} diff --git a/components/themed-text.tsx b/components/themed-text.tsx index d79d0a1..9ff5878 100644 --- a/components/themed-text.tsx +++ b/components/themed-text.tsx @@ -1,6 +1,7 @@ import { StyleSheet, Text, type TextProps } from 'react-native'; import { useThemeColor } from '@/hooks/use-theme-color'; +import { useDynamicFontSize } from '@/src/hooks/useDynamicFontSize'; export type ThemedTextProps = TextProps & { lightColor?: string; @@ -16,18 +17,33 @@ export function ThemedText({ ...rest }: ThemedTextProps) { const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text'); + const { scale } = useDynamicFontSize(); + + const getVariantStyle = () => { + switch (type) { + case 'title': return styles.title; + case 'subtitle': return styles.subtitle; + case 'defaultSemiBold': return styles.defaultSemiBold; + case 'link': return styles.link; + default: return styles.default; + } + }; + + const variantStyle = getVariantStyle(); + const scaledStyle = { + ...variantStyle, + fontSize: scale(variantStyle.fontSize || 16), + lineHeight: variantStyle.lineHeight ? scale(variantStyle.lineHeight) : undefined, + }; return ( ); diff --git a/src/components/common/AppText.tsx b/src/components/common/AppText.tsx new file mode 100644 index 0000000..c629b9c --- /dev/null +++ b/src/components/common/AppText.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { Text as RNText, TextProps, StyleSheet } from 'react-native'; +import { useDynamicFontSize } from '../../hooks/useDynamicFontSize'; + +interface AppTextProps extends TextProps { + /** + * If true, the font size will NOT be scaled. + * Useful for elements that should remain at a fixed size regardless of system settings. + */ + fixed?: boolean; +} + +/** + * A wrapper around React Native's Text component that uses the useDynamicFontSize hook + * to ensure consistent scaling across the application. + */ +export const AppText: React.FC = ({ style, fixed = false, ...props }) => { + const { scale } = useDynamicFontSize(); + + // We flatten the style to easily extract and modify the fontSize + const flattenedStyle = StyleSheet.flatten(style) || {}; + + const dynamicStyle = { ...flattenedStyle }; + + if (!fixed && flattenedStyle.fontSize) { + dynamicStyle.fontSize = scale(flattenedStyle.fontSize); + + // Also scale lineHeight if it exists to maintain proportions + if (flattenedStyle.lineHeight) { + dynamicStyle.lineHeight = scale(flattenedStyle.lineHeight); + } + } + + return ( + + ); +}; diff --git a/src/components/common/PrimaryButton.tsx b/src/components/common/PrimaryButton.tsx index fe32fae..05960c6 100644 --- a/src/components/common/PrimaryButton.tsx +++ b/src/components/common/PrimaryButton.tsx @@ -9,6 +9,7 @@ import { StyleSheet, } from 'react-native'; import { LinearGradient } from 'expo-linear-gradient'; +import { useDynamicFontSize } from '../../hooks/useDynamicFontSize'; /** * Props for the PrimaryButton component @@ -50,12 +51,28 @@ export default function PrimaryButton({ accessibilityLabel, }: PrimaryButtonProps) { const isDisabled = loading || disabled; + const { scale } = useDynamicFontSize(); const buttonLabel = accessibilityLabel ?? title; const sizeConfig = { - small: { paddingHorizontal: 12, paddingVertical: 8, borderRadius: 8, fontSize: 14 }, - medium: { paddingHorizontal: 24, paddingVertical: 12, borderRadius: 12, fontSize: 16 }, - large: { paddingHorizontal: 32, paddingVertical: 16, borderRadius: 12, fontSize: 18 }, + small: { + paddingHorizontal: scale(12), + paddingVertical: scale(8), + borderRadius: 8, + fontSize: scale(14) + }, + medium: { + paddingHorizontal: scale(24), + paddingVertical: scale(12), + borderRadius: 12, + fontSize: scale(16) + }, + large: { + paddingHorizontal: scale(32), + paddingVertical: scale(16), + borderRadius: 12, + fontSize: scale(18) + }, }; const config = sizeConfig[size]; @@ -92,6 +109,7 @@ export default function PrimaryButton({ <> {icon} {icon} {icon} ( initialViewMode || "lesson", ); @@ -393,7 +395,7 @@ export default function MobileCourseViewer({ {/* Progress Bar */} - + diff --git a/src/components/mobile/MobileFormInput.tsx b/src/components/mobile/MobileFormInput.tsx index cd0d74a..a363e1e 100644 --- a/src/components/mobile/MobileFormInput.tsx +++ b/src/components/mobile/MobileFormInput.tsx @@ -8,6 +8,8 @@ import { StyleSheet, } from 'react-native'; import { Eye, EyeOff, AlertCircle } from 'lucide-react-native'; +import { AppText as Text } from '../common/AppText'; +import { useDynamicFontSize } from '../../hooks/useDynamicFontSize'; /** * Props for the MobileFormInput component @@ -48,6 +50,7 @@ export const MobileFormInput: React.FC = ({ }) => { const [isFocused, setIsFocused] = useState(false); const [showPassword, setShowPassword] = useState(false); + const { scale } = useDynamicFontSize(); const isPassword = secureTextEntry === true; const borderColor = error @@ -63,12 +66,12 @@ export const MobileFormInput: React.FC = ({ return ( - + {label} - {required && *} + {required && *} {hint && !error && ( - + {hint} )} @@ -80,7 +83,7 @@ export const MobileFormInput: React.FC = ({ { borderColor, backgroundColor: isDark ? '#1e293b' : '#fff', - minHeight: multiline ? 100 : 52, + minHeight: multiline ? scale(100) : scale(52), }, ]} > @@ -93,9 +96,10 @@ export const MobileFormInput: React.FC = ({ styles.input, { color: isDark ? '#f1f5f9' : '#1e293b', - paddingLeft: leftIcon ? 0 : 16, + paddingLeft: leftIcon ? 0 : scale(16), textAlignVertical: multiline ? 'top' : 'center', - paddingTop: multiline ? 14 : 0, + paddingTop: multiline ? scale(14) : 0, + fontSize: scale(15), }, ]} placeholder={placeholder} @@ -116,9 +120,9 @@ export const MobileFormInput: React.FC = ({ style={styles.rightIcon} > {showPassword ? ( - + ) : ( - + )} )} @@ -126,8 +130,8 @@ export const MobileFormInput: React.FC = ({ {error && ( - - {error} + + {error} )} diff --git a/src/components/mobile/MobileHeader.tsx b/src/components/mobile/MobileHeader.tsx index 1ba9672..c4694a7 100644 --- a/src/components/mobile/MobileHeader.tsx +++ b/src/components/mobile/MobileHeader.tsx @@ -2,9 +2,11 @@ import { DrawerNavigationProp } from '@react-navigation/drawer'; import { useNavigation } from '@react-navigation/native'; import { ArrowLeft, Bell, Menu } from 'lucide-react-native'; import React from 'react'; -import { Text, TouchableOpacity, View } from 'react-native'; +import { TouchableOpacity, View } from 'react-native'; import { usePendingRequests } from '../../hooks/usePendingRequests'; import { useSafeArea } from '../../hooks/useSafeArea'; +import { AppText } from '../common/AppText'; +import { useDynamicFontSize } from '../../hooks/useDynamicFontSize'; /** * Props for the MobileHeader component @@ -22,6 +24,7 @@ export const MobileHeader = ({ title, showBack = false, rightAction }: MobileHea const { top } = useSafeArea(); const navigation = useNavigation>(); const pendingCount = usePendingRequests(); + const { scale } = useDynamicFontSize(); return ( - + ) : ( - + )} - {title} + + {title} + @@ -60,13 +68,16 @@ export const MobileHeader = ({ title, showBack = false, rightAction }: MobileHea accessibilityRole="button" accessibilityLabel="View notifications" > - + {pendingCount > 0 && ( - + {pendingCount > 99 ? '99+' : pendingCount} - + )} diff --git a/src/components/mobile/MobileProfile.tsx b/src/components/mobile/MobileProfile.tsx index da88553..53584cd 100644 --- a/src/components/mobile/MobileProfile.tsx +++ b/src/components/mobile/MobileProfile.tsx @@ -1,7 +1,6 @@ import React, { useState } from 'react'; import { View, - Text, ScrollView, TouchableOpacity, Image, @@ -9,6 +8,7 @@ import { SafeAreaView, ActivityIndicator, } from 'react-native'; +import { AppText as Text } from '../common/AppText'; import { LinearGradient } from 'expo-linear-gradient'; import { Skeleton } from '../ui/Skeleton'; import { @@ -236,12 +236,15 @@ interface MobileProfileProps { isLoading?: boolean; } +import { useDynamicFontSize } from '../../hooks/useDynamicFontSize'; + export const MobileProfile: React.FC = ({ userId: _userId, isDark = false, isLoading = false, }) => { const [profile, setProfile] = useState(MOCK_PROFILE); + const { scale } = useDynamicFontSize(); if (isLoading) { const bg = isDark ? '#0f172a' : '#f8fafc'; diff --git a/src/components/mobile/MobileSearch.tsx b/src/components/mobile/MobileSearch.tsx index 6feaf3f..b5bbd20 100644 --- a/src/components/mobile/MobileSearch.tsx +++ b/src/components/mobile/MobileSearch.tsx @@ -9,6 +9,8 @@ import { KeyboardAvoidingView, Platform, } from 'react-native'; +import { AppText as Text } from '../common/AppText'; +import { useDynamicFontSize } from '../../hooks/useDynamicFontSize'; import { Search, SlidersHorizontal } from 'lucide-react-native'; import { VoiceSearch } from './VoiceSearch'; import { SearchHistory } from './SearchHistory'; @@ -94,6 +96,7 @@ export function MobileSearch({ const [filterValues, setFilterValues] = useState({}); const [results, setResults] = useState([]); const [hasSearched, setHasSearched] = useState(false); + const { scale } = useDynamicFontSize(); const suggestions = useMemo(() => { const q = query.trim().toLowerCase(); @@ -167,9 +170,9 @@ export function MobileSearch({ > - + setFilterSheetVisible(true)} style={[styles.filterBtn, Object.keys(filterValues).length > 0 && styles.filterBtnActive]} > - 0 ? '#fff' : '#6B7280'} /> + 0 ? '#fff' : '#6B7280'} /> @@ -209,7 +212,7 @@ export function MobileSearch({ style={styles.suggestItem} onPress={() => handleSelectSuggestion(s)} > - + {s} ))} diff --git a/src/components/mobile/MobileSettings.tsx b/src/components/mobile/MobileSettings.tsx index 9f16842..189b9ea 100644 --- a/src/components/mobile/MobileSettings.tsx +++ b/src/components/mobile/MobileSettings.tsx @@ -34,6 +34,9 @@ import { NativeToggle } from './NativeToggle'; import { SettingsPicker, PickerOption } from './SettingsPicker'; import { SettingsSection } from './SettingsSection'; +import { AppText } from '../common/AppText'; +import { useDynamicFontSize } from '../../hooks/useDynamicFontSize'; + // ─── Shared row ──────────────────────────────────────────────────────────────── /** @@ -66,6 +69,8 @@ function SettingRow({ destructive = false, }: SettingRowProps) { const Row = onPress ? TouchableOpacity : View; + const { scale } = useDynamicFontSize(); + return ( - {label} - + {description ? ( - + {description} - + ) : null} - {right ?? (onPress ? : null)} + {right ?? (onPress ? : null)} ); } @@ -207,7 +216,7 @@ export function MobileSettings({ return ( {/* ── Account ─────────────────────────────────────────── */} @@ -242,13 +251,13 @@ export function MobileSettings({ /> } + icon={} label="Change Password" onPress={onChangePassword} /> } + icon={} label="Linked Accounts" onPress={onLinkedAccounts} /> @@ -497,7 +506,7 @@ export function MobileSettings({ } + icon={} label="Sign Out" onPress={handleSignOut} destructive diff --git a/src/components/mobile/SettingsSection.tsx b/src/components/mobile/SettingsSection.tsx index 477f88f..06db799 100644 --- a/src/components/mobile/SettingsSection.tsx +++ b/src/components/mobile/SettingsSection.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { View, Text } from 'react-native'; +import { View } from 'react-native'; +import { AppText } from '../common/AppText'; interface SettingsSectionProps { /** Section header label displayed above the card (uppercase). */ @@ -27,9 +28,12 @@ export function SettingsSection({ title, footer, children }: SettingsSectionProp return ( {title ? ( - + {title} - + ) : null} @@ -44,9 +48,12 @@ export function SettingsSection({ title, footer, children }: SettingsSectionProp {footer ? ( - + {footer} - + ) : null} ); diff --git a/src/hooks/useDynamicFontSize.ts b/src/hooks/useDynamicFontSize.ts index 3efca39..426d9f7 100644 --- a/src/hooks/useDynamicFontSize.ts +++ b/src/hooks/useDynamicFontSize.ts @@ -1,25 +1,17 @@ -import { useState, useEffect } from 'react'; -import { PixelRatio } from 'react-native'; +import { useWindowDimensions } from 'react-native'; /** * Hook to track and react to dynamic font scaling changes. - * Returns the current font scale factor. + * Returns the current font scale factor and a scaling function. + * Uses useWindowDimensions to automatically update when system settings change. */ export const useDynamicFontSize = () => { - const [fontScale, setFontScale] = useState(PixelRatio.getFontScale()); - - useEffect(() => { - // Note: PixelRatio.getFontScale() doesn't have an event listener in basic RN, - // but we can check it on mount or when the window dimensions change. - // In most cases, users don't change this while the app is running. - const currentScale = PixelRatio.getFontScale(); - if (currentScale !== fontScale) { - setFontScale(currentScale); - } - }, []); + const { fontScale } = useWindowDimensions(); /** * Scales a given size based on the current font scale. + * Useful for font sizes, but also for padding, margin, etc. + * that should scale with the text. */ const scale = (size: number) => size * fontScale; diff --git a/src/pages/mobile/MobileLogin.tsx b/src/pages/mobile/MobileLogin.tsx index 7ac6778..5f11a56 100644 --- a/src/pages/mobile/MobileLogin.tsx +++ b/src/pages/mobile/MobileLogin.tsx @@ -26,6 +26,7 @@ import { AlertCircle, } from 'lucide-react-native'; import { useBiometricAuth } from '../../hooks/useBiometricAuth'; +import { useDynamicFontSize } from '../../hooks/useDynamicFontSize'; import { BiometricInlineButton, BiometricPrompt } from '../../components/mobile/BiometricPrompt'; import mobileAuthService, { AuthResult } from '../../services/mobileAuth'; import * as secureStorage from '../../services/secureStorage'; @@ -70,6 +71,9 @@ export const MobileLogin: React.FC = ({ clearError: clearBiometricError, } = useBiometricAuth(); + const { scale } = useDynamicFontSize(); + const styles = createStyles(scale, isDark); + // ── Load remembered email on mount ─────────────────────────────────────── useEffect(() => { async function loadRemembered() { @@ -186,10 +190,10 @@ export const MobileLogin: React.FC = ({ end={{ x: 1, y: 1 }} style={styles.logoGradient} > - + - TeachLink - + TeachLink + Sign in to continue learning @@ -199,22 +203,23 @@ export const MobileLogin: React.FC = ({ {/* Error banner */} {error && ( - - {error} + + {error} )} {/* Email */} - Email + Email - + { setEmail(v); setError(null); }} @@ -234,10 +239,10 @@ export const MobileLogin: React.FC = ({ {/* Password */} - Password + Password {onForgotPassword && ( - + Forgot password? @@ -249,8 +254,9 @@ export const MobileLogin: React.FC = ({ { borderColor: passwordBorder, backgroundColor: isDark ? '#0f172a' : '#f8fafc' }, ]} > - + = ({ hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }} > {showPassword ? ( - + ) : ( - + )} @@ -286,7 +292,7 @@ export const MobileLogin: React.FC = ({ thumbColor="#fff" ios_backgroundColor={borderColor} /> - + Remember me @@ -311,8 +317,8 @@ export const MobileLogin: React.FC = ({ ) : ( <> - - Sign In + + Sign In )} @@ -323,7 +329,7 @@ export const MobileLogin: React.FC = ({ <> - or + or @@ -342,8 +348,8 @@ export const MobileLogin: React.FC = ({ onPress={() => handleSocialLogin('google')} activeOpacity={0.7} > - - Google + + Google {Platform.OS === 'ios' && ( @@ -352,8 +358,8 @@ export const MobileLogin: React.FC = ({ onPress={() => handleSocialLogin('apple')} activeOpacity={0.7} > - - Apple + + Apple )} @@ -364,11 +370,11 @@ export const MobileLogin: React.FC = ({ {/* ── Register link ── */} {onRegister && ( - + Don't have an account? - + {' '}Sign up @@ -394,7 +400,9 @@ export const MobileLogin: React.FC = ({ // ─── Styles ─────────────────────────────────────────────────────────────────── -const styles = StyleSheet.create({ +// ─── Styles ─────────────────────────────────────────────────────────────────── + +const createStyles = (scale: (size: number) => number, isDark: boolean) => StyleSheet.create({ safe: { flex: 1, }, @@ -403,69 +411,69 @@ const styles = StyleSheet.create({ }, scroll: { flexGrow: 1, - paddingHorizontal: 20, - paddingBottom: 40, + paddingHorizontal: scale(20), + paddingBottom: scale(40), justifyContent: 'center', }, // Header header: { alignItems: 'center', - paddingTop: 32, - paddingBottom: 28, - gap: 10, + paddingTop: scale(32), + paddingBottom: scale(28), + gap: scale(10), }, logoGradient: { - width: 68, - height: 68, - borderRadius: 20, + width: scale(68), + height: scale(68), + borderRadius: scale(20), justifyContent: 'center', alignItems: 'center', shadowColor: '#20afe7', - shadowOffset: { width: 0, height: 8 }, + shadowOffset: { width: 0, height: scale(8) }, shadowOpacity: 0.35, - shadowRadius: 16, + shadowRadius: scale(16), elevation: 10, }, appName: { - fontSize: 28, + fontSize: scale(28), fontWeight: '900', letterSpacing: -0.5, }, tagline: { - fontSize: 14, + fontSize: scale(14), }, // Card card: { - borderRadius: 20, - padding: 22, + borderRadius: scale(20), + padding: scale(22), borderWidth: 1, - gap: 4, + gap: scale(4), shadowColor: '#000', - shadowOffset: { width: 0, height: 4 }, + shadowOffset: { width: 0, height: scale(4) }, shadowOpacity: 0.07, - shadowRadius: 16, + shadowRadius: scale(16), elevation: 4, }, errorBanner: { flexDirection: 'row', alignItems: 'center', - gap: 8, + gap: scale(8), backgroundColor: '#fee2e2', - borderRadius: 10, - paddingHorizontal: 14, - paddingVertical: 10, - marginBottom: 4, + borderRadius: scale(10), + paddingHorizontal: scale(14), + paddingVertical: scale(10), + marginBottom: scale(4), }, errorText: { color: '#dc2626', - fontSize: 13, + fontSize: scale(13), fontWeight: '500', flex: 1, }, // Fields fieldWrap: { - marginBottom: 12, - gap: 6, + marginBottom: scale(12), + gap: scale(6), }, labelRow: { flexDirection: 'row', @@ -473,89 +481,89 @@ const styles = StyleSheet.create({ alignItems: 'center', }, label: { - fontSize: 13, + fontSize: scale(13), fontWeight: '600', }, forgotLink: { - fontSize: 13, + fontSize: scale(13), fontWeight: '600', }, inputRow: { flexDirection: 'row', alignItems: 'center', - gap: 10, + gap: scale(10), borderWidth: 1.5, - borderRadius: 12, - paddingHorizontal: 14, - paddingVertical: Platform.OS === 'ios' ? 13 : 10, + borderRadius: scale(12), + paddingHorizontal: scale(14), + paddingVertical: Platform.OS === 'ios' ? scale(13) : scale(10), }, input: { flex: 1, - fontSize: 15, + fontSize: scale(15), paddingVertical: 0, }, // Remember Me rememberRow: { flexDirection: 'row', alignItems: 'center', - gap: 10, - paddingVertical: 4, - marginBottom: 4, + gap: scale(10), + paddingVertical: scale(4), + marginBottom: scale(4), }, rememberLabel: { - fontSize: 14, + fontSize: scale(14), fontWeight: '500', }, // Login button loginBtn: { - borderRadius: 14, + borderRadius: scale(14), overflow: 'hidden', - marginTop: 6, + marginTop: scale(6), }, loginBtnGradient: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', - gap: 8, - paddingVertical: 15, + gap: scale(8), + paddingVertical: scale(15), }, loginBtnText: { color: '#fff', - fontSize: 16, + fontSize: scale(16), fontWeight: '700', }, // Divider dividerRow: { flexDirection: 'row', alignItems: 'center', - gap: 10, - marginVertical: 8, + gap: scale(10), + marginVertical: scale(8), }, divider: { flex: 1, height: 1, }, dividerText: { - fontSize: 13, + fontSize: scale(13), fontWeight: '500', }, // Alt auth row altRow: { flexDirection: 'row', - gap: 10, + gap: scale(10), }, socialBtn: { flex: 1, flexDirection: 'column', alignItems: 'center', justifyContent: 'center', - gap: 6, - paddingVertical: 14, - borderRadius: 14, + gap: scale(6), + paddingVertical: scale(14), + borderRadius: scale(14), borderWidth: 1.5, }, socialBtnText: { - fontSize: 12, + fontSize: scale(12), fontWeight: '600', }, // Register @@ -563,13 +571,13 @@ const styles = StyleSheet.create({ flexDirection: 'row', justifyContent: 'center', alignItems: 'center', - marginTop: 20, + marginTop: scale(20), }, registerText: { - fontSize: 14, + fontSize: scale(14), }, registerLink: { - fontSize: 14, + fontSize: scale(14), fontWeight: '700', }, }); diff --git a/src/pages/mobile/PaymentHistory.tsx b/src/pages/mobile/PaymentHistory.tsx index 71d02eb..7158270 100644 --- a/src/pages/mobile/PaymentHistory.tsx +++ b/src/pages/mobile/PaymentHistory.tsx @@ -10,6 +10,8 @@ import { ActivityIndicator, RefreshControl, } from 'react-native'; +import { AppText as Text } from '../../components/common/AppText'; +import { useDynamicFontSize } from '../../hooks/useDynamicFontSize'; import { LinearGradient } from 'expo-linear-gradient'; import { ArrowLeft, @@ -73,6 +75,9 @@ const STATUS_CONFIG: Record< }, }; +// ... inside component, use scale(14) for these icons if needed, but since they are constants, +// we'll handle them inside the render. + // ─── Component ──────────────────────────────────────────────────────────────── interface PaymentHistoryProps { @@ -84,6 +89,7 @@ export const PaymentHistory: React.FC = ({ isDark = false, onBack, }) => { + const { scale } = useDynamicFontSize(); const { purchaseHistory, currentTier, @@ -299,6 +305,7 @@ export const PaymentHistory: React.FC = ({ const renderTransaction = (record: PurchaseRecord) => { const statusCfg = STATUS_CONFIG[record.status]; + const statusIcon = React.cloneElement(statusCfg.icon as React.ReactElement, { size: scale(14) }); return ( = ({ {mobilePaymentsService.formatPrice(record.amount, record.currency)} - {statusCfg.icon} + {statusIcon} {statusCfg.label} @@ -392,10 +399,10 @@ export const PaymentHistory: React.FC = ({ {onBack && ( - + )} - + Payment History = ({ {isRestoring ? ( ) : ( - + )} diff --git a/src/pages/mobile/Settings.tsx b/src/pages/mobile/Settings.tsx index d63693f..663abac 100644 --- a/src/pages/mobile/Settings.tsx +++ b/src/pages/mobile/Settings.tsx @@ -1,9 +1,11 @@ import React from 'react'; -import { View, Text, TouchableOpacity, StatusBar } from 'react-native'; +import { View, TouchableOpacity, StatusBar } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { ArrowLeft, Settings as SettingsIcon } from 'lucide-react-native'; import { useAppStore } from '../../store'; import { MobileSettings } from '../../components/mobile/MobileSettings'; +import { AppText } from '../../components/common/AppText'; +import { useDynamicFontSize } from '../../hooks/useDynamicFontSize'; interface SettingsPageProps { /** Callback for the back-navigation button in the header. */ @@ -29,6 +31,7 @@ export default function SettingsPage({ }: SettingsPageProps) { const { theme } = useAppStore(); const isDark = theme === 'dark'; + const { scale } = useDynamicFontSize(); return ( @@ -47,21 +50,27 @@ export default function SettingsPage({ className="w-9 h-9 items-center justify-center rounded-full bg-white dark:bg-gray-800 mr-3" hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }} > - + ) : ( - + )} - + Settings - - + + Manage your account & preferences - + diff --git a/tsconfig.json b/tsconfig.json index 1280642..977bc5c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,8 @@ "strict": true, "skipLibCheck": true, "jsx": "react-native", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, "baseUrl": ".", "lib": ["ES2015", "ES2017", "DOM"], "paths": {