feat: update mobile app screens

- Enhance browse tab with improved navigation
- Update home tab with new features
- Refactor profile tab
- Update paywall screen styling
This commit is contained in:
Millian Lamiaux
2026-03-14 20:44:10 +01:00
parent 64cdb75b39
commit 8c8dbebd17
4 changed files with 946 additions and 895 deletions

View File

@@ -3,7 +3,7 @@
* Premium subscription purchase flow
*/
import React from 'react'
import React, { useMemo } from 'react'
import {
View,
StyleSheet,
@@ -18,7 +18,8 @@ import Ionicons from '@expo/vector-icons/Ionicons'
import { useTranslation } from 'react-i18next'
import { useHaptics, usePurchases } from '@/src/shared/hooks'
import { BRAND, darkColors } from '@/src/shared/theme'
import { useThemeColors, BRAND, GRADIENTS } from '@/src/shared/theme'
import type { ThemeColors } from '@/src/shared/theme/types'
import { SPACING } from '@/src/shared/constants/spacing'
import { RADIUS } from '@/src/shared/constants/borderRadius'
@@ -39,6 +40,18 @@ const PREMIUM_FEATURES = [
// COMPONENTS
// ═══════════════════════════════════════════════════════════════════════════
interface PlanCardStyles {
planCard: object
planCardPressed: object
savingsBadge: object
savingsText: object
planInfo: object
planTitle: object
planPeriod: object
planPrice: object
checkmark: object
}
function PlanCard({
title,
price,
@@ -46,6 +59,8 @@ function PlanCard({
savings,
isSelected,
onPress,
colors,
styles,
}: {
title: string
price: string
@@ -53,6 +68,8 @@ function PlanCard({
savings?: string
isSelected: boolean
onPress: () => void
colors: ThemeColors
styles: PlanCardStyles
}) {
const haptics = useHaptics()
@@ -66,8 +83,12 @@ function PlanCard({
onPress={handlePress}
style={({ pressed }) => [
styles.planCard,
isSelected && styles.planCardSelected,
isSelected && { borderColor: BRAND.PRIMARY },
pressed && styles.planCardPressed,
{
backgroundColor: colors.bg.surface,
borderColor: isSelected ? BRAND.PRIMARY : colors.border.glass,
},
]}
>
{savings && (
@@ -76,10 +97,16 @@ function PlanCard({
</View>
)}
<View style={styles.planInfo}>
<Text style={styles.planTitle}>{title}</Text>
<Text style={styles.planPeriod}>{period}</Text>
<Text style={[styles.planTitle, { color: colors.text.primary }]}>
{title}
</Text>
<Text style={[styles.planPeriod, { color: colors.text.tertiary }]}>
{period}
</Text>
</View>
<Text style={styles.planPrice}>{price}</Text>
<Text style={[styles.planPrice, { color: BRAND.PRIMARY }]}>
{price}
</Text>
{isSelected && (
<View style={styles.checkmark}>
<Ionicons name="checkmark-circle" size={24} color={BRAND.PRIMARY} />
@@ -98,6 +125,25 @@ export default function PaywallScreen() {
const router = useRouter()
const insets = useSafeAreaInsets()
const haptics = useHaptics()
const colors = useThemeColors()
const styles = useMemo(() => createStyles(colors), [colors])
// Extract plan card styles for the child component
const planCardStyles = useMemo(
() => ({
planCard: styles.planCard,
planCardPressed: styles.planCardPressed,
savingsBadge: styles.savingsBadge,
savingsText: styles.savingsText,
planInfo: styles.planInfo,
planTitle: styles.planTitle,
planPeriod: styles.planPeriod,
planPrice: styles.planPrice,
checkmark: styles.checkmark,
}),
[styles],
)
const {
monthlyPackage,
annualPackage,
@@ -148,15 +194,9 @@ export default function PaywallScreen() {
return (
<View style={[styles.container, { paddingTop: insets.top }]}>
{/* Background Gradient */}
<LinearGradient
colors={['#1a1a2e', '#16213e', '#0f0f1a']}
style={styles.gradient}
/>
{/* Close Button */}
<Pressable style={styles.closeButton} onPress={handleClose}>
<Ionicons name="close" size={28} color={darkColors.text.secondary} />
<Pressable style={[styles.closeButton, { top: insets.top + SPACING[2] }]} onPress={handleClose}>
<Ionicons name="close" size={28} color={colors.text.secondary} />
</Pressable>
<ScrollView
@@ -177,10 +217,10 @@ export default function PaywallScreen() {
<View style={styles.featuresGrid}>
{PREMIUM_FEATURES.map((feature) => (
<View key={feature.key} style={styles.featureItem}>
<View style={styles.featureIcon}>
<View style={[styles.featureIcon, { backgroundColor: colors.glass.tinted.backgroundColor }]}>
<Ionicons name={feature.icon as any} size={22} color={BRAND.PRIMARY} />
</View>
<Text style={styles.featureText}>
<Text style={[styles.featureText, { color: colors.text.secondary }]}>
{t(`paywall.features.${feature.key}`)}
</Text>
</View>
@@ -196,6 +236,8 @@ export default function PaywallScreen() {
savings={t('paywall.save50')}
isSelected={selectedPlan === 'annual'}
onPress={() => setSelectedPlan('annual')}
colors={colors}
styles={planCardStyles}
/>
<PlanCard
title={t('paywall.monthly')}
@@ -203,12 +245,14 @@ export default function PaywallScreen() {
period={t('paywall.perMonth')}
isSelected={selectedPlan === 'monthly'}
onPress={() => setSelectedPlan('monthly')}
colors={colors}
styles={planCardStyles}
/>
</View>
{/* Price Note */}
{selectedPlan === 'annual' && (
<Text style={styles.priceNote}>
<Text style={[styles.priceNote, { color: colors.text.tertiary }]}>
{t('paywall.equivalent', { price: annualMonthlyEquivalent })}
</Text>
)}
@@ -220,12 +264,12 @@ export default function PaywallScreen() {
disabled={isLoading}
>
<LinearGradient
colors={[BRAND.PRIMARY, '#FF8A5B']}
colors={GRADIENTS.CTA}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={styles.ctaGradient}
>
<Text style={styles.ctaText}>
<Text style={[styles.ctaText, { color: colors.text.primary }]}>
{isLoading ? t('paywall.processing') : t('paywall.subscribe')}
</Text>
</LinearGradient>
@@ -234,10 +278,14 @@ export default function PaywallScreen() {
{/* Restore & Terms */}
<View style={styles.footer}>
<Pressable onPress={handleRestore}>
<Text style={styles.restoreText}>{t('paywall.restore')}</Text>
<Text style={[styles.restoreText, { color: colors.text.tertiary }]}>
{t('paywall.restore')}
</Text>
</Pressable>
<Text style={styles.termsText}>{t('paywall.terms')}</Text>
<Text style={[styles.termsText, { color: colors.text.tertiary }]}>
{t('paywall.terms')}
</Text>
</View>
</ScrollView>
</View>
@@ -248,162 +296,147 @@ export default function PaywallScreen() {
// STYLES
// ═══════════════════════════════════════════════════════════════════════════
const styles = StyleSheet.create({
container: {
flex: 1,
},
gradient: {
...StyleSheet.absoluteFillObject,
},
closeButton: {
position: 'absolute',
top: SPACING[4],
right: SPACING[4],
width: 44,
height: 44,
alignItems: 'center',
justifyContent: 'center',
zIndex: 10,
},
scrollView: {
flex: 1,
},
scrollContent: {
paddingHorizontal: SPACING[5],
paddingTop: SPACING[8],
},
header: {
alignItems: 'center',
},
title: {
fontSize: 32,
fontWeight: '700',
color: '#FFF',
textAlign: 'center',
},
subtitle: {
fontSize: 16,
color: darkColors.text.secondary,
textAlign: 'center',
marginTop: SPACING[2],
},
featuresGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
marginTop: SPACING[6],
marginHorizontal: -SPACING[2],
},
featureItem: {
width: '33%',
alignItems: 'center',
paddingVertical: SPACING[3],
},
featureIcon: {
width: 48,
height: 48,
borderRadius: 24,
backgroundColor: 'rgba(255, 107, 53, 0.15)',
alignItems: 'center',
justifyContent: 'center',
marginBottom: SPACING[2],
},
featureText: {
fontSize: 13,
color: darkColors.text.secondary,
textAlign: 'center',
},
plansContainer: {
marginTop: SPACING[6],
gap: SPACING[3],
},
planCard: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'rgba(255, 255, 255, 0.08)',
borderRadius: RADIUS.LG,
padding: SPACING[4],
borderWidth: 2,
borderColor: 'transparent',
},
planCardSelected: {
borderColor: BRAND.PRIMARY,
backgroundColor: 'rgba(255, 107, 53, 0.1)',
},
planCardPressed: {
opacity: 0.8,
},
savingsBadge: {
position: 'absolute',
top: -8,
right: SPACING[3],
backgroundColor: BRAND.PRIMARY,
paddingHorizontal: SPACING[2],
paddingVertical: 2,
borderRadius: RADIUS.SM,
},
savingsText: {
fontSize: 10,
fontWeight: '700',
color: '#FFF',
},
planInfo: {
flex: 1,
},
planTitle: {
fontSize: 16,
fontWeight: '600',
color: darkColors.text.primary,
},
planPeriod: {
fontSize: 13,
color: darkColors.text.tertiary,
marginTop: 2,
},
planPrice: {
fontSize: 20,
fontWeight: '700',
color: BRAND.PRIMARY,
},
checkmark: {
marginLeft: SPACING[2],
},
priceNote: {
fontSize: 13,
color: darkColors.text.tertiary,
textAlign: 'center',
marginTop: SPACING[3],
},
ctaButton: {
borderRadius: RADIUS.LG,
overflow: 'hidden',
marginTop: SPACING[6],
},
ctaButtonDisabled: {
opacity: 0.6,
},
ctaGradient: {
paddingVertical: SPACING[4],
alignItems: 'center',
},
ctaText: {
fontSize: 17,
fontWeight: '600',
color: '#FFF',
},
footer: {
marginTop: SPACING[5],
alignItems: 'center',
gap: SPACING[4],
},
restoreText: {
fontSize: 14,
color: darkColors.text.tertiary,
},
termsText: {
fontSize: 11,
color: darkColors.text.tertiary,
textAlign: 'center',
lineHeight: 18,
paddingHorizontal: SPACING[4],
},
})
function createStyles(colors: ThemeColors) {
return StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.bg.base,
},
closeButton: {
position: 'absolute',
top: SPACING[4],
right: SPACING[4],
width: 44,
height: 44,
alignItems: 'center',
justifyContent: 'center',
zIndex: 10,
},
scrollView: {
flex: 1,
},
scrollContent: {
paddingHorizontal: SPACING[5],
paddingTop: SPACING[8],
},
header: {
alignItems: 'center',
},
title: {
fontSize: 32,
fontWeight: '700',
color: colors.text.primary,
textAlign: 'center',
},
subtitle: {
fontSize: 16,
color: colors.text.secondary,
textAlign: 'center',
marginTop: SPACING[2],
},
featuresGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
marginTop: SPACING[6],
marginHorizontal: -SPACING[2],
},
featureItem: {
width: '33%',
alignItems: 'center',
paddingVertical: SPACING[3],
},
featureIcon: {
width: 48,
height: 48,
borderRadius: 24,
alignItems: 'center',
justifyContent: 'center',
marginBottom: SPACING[2],
},
featureText: {
fontSize: 13,
textAlign: 'center',
},
plansContainer: {
marginTop: SPACING[6],
gap: SPACING[3],
},
planCard: {
flexDirection: 'row',
alignItems: 'center',
borderRadius: RADIUS.LG,
padding: SPACING[4],
borderWidth: 2,
},
planCardPressed: {
opacity: 0.8,
},
savingsBadge: {
position: 'absolute',
top: -8,
right: SPACING[3],
backgroundColor: BRAND.PRIMARY,
paddingHorizontal: SPACING[2],
paddingVertical: 2,
borderRadius: RADIUS.SM,
},
savingsText: {
fontSize: 10,
fontWeight: '700',
color: colors.text.primary,
},
planInfo: {
flex: 1,
},
planTitle: {
fontSize: 16,
fontWeight: '600',
},
planPeriod: {
fontSize: 13,
marginTop: 2,
},
planPrice: {
fontSize: 20,
fontWeight: '700',
},
checkmark: {
marginLeft: SPACING[2],
},
priceNote: {
fontSize: 13,
textAlign: 'center',
marginTop: SPACING[3],
},
ctaButton: {
borderRadius: RADIUS.LG,
overflow: 'hidden',
marginTop: SPACING[6],
},
ctaButtonDisabled: {
opacity: 0.6,
},
ctaGradient: {
paddingVertical: SPACING[4],
alignItems: 'center',
},
ctaText: {
fontSize: 17,
fontWeight: '600',
},
footer: {
marginTop: SPACING[5],
alignItems: 'center',
gap: SPACING[4],
},
restoreText: {
fontSize: 14,
},
termsText: {
fontSize: 11,
textAlign: 'center',
lineHeight: 18,
paddingHorizontal: SPACING[4],
},
})
}