diff --git a/app.json b/app.json new file mode 100644 index 0000000..743a26a --- /dev/null +++ b/app.json @@ -0,0 +1,10 @@ +{ + "expo": { + "extra": { + "eas": { + "projectId": "807ca0af-e9bd-40e8-a658-f3382072c724" + } + }, + "owner": "jjbrunton-apps" + } +} diff --git a/apps/mobile/android/app/build.gradle b/apps/mobile/android/app/build.gradle index a25b7fa..a37a7eb 100644 --- a/apps/mobile/android/app/build.gradle +++ b/apps/mobile/android/app/build.gradle @@ -94,7 +94,7 @@ android { applicationId 'com.sauci.app' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 3 + versionCode 1 versionName "1.0.0" } signingConfigs { diff --git a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp index e91bfd8..a97edd8 100644 Binary files a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp and b/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp index dd7418b..7fb057d 100644 Binary files a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp and b/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp index 658f2c1..cf7a6da 100644 Binary files a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and b/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp index e202285..60af425 100644 Binary files a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp and b/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp index 9c62968..2f241ff 100644 Binary files a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp and b/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp index 9bfe4a5..2606d3c 100644 Binary files a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and b/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp index 5f78705..282629c 100644 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and b/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp index 69e29f7..e83a632 100644 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp and b/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp index e249752..8f28e36 100644 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and b/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp index cf1b62e..9f8b66d 100644 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and b/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp index 6288d36..9cb3660 100644 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp and b/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp index 4bccefd..6312379 100644 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and b/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp index 6bcfbcf..56aec77 100644 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and b/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp index 4802994..f110bfc 100644 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp and b/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp index 9abd05e..783f21f 100644 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and b/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/apps/mobile/app.json b/apps/mobile/app.json index 27146df..de8a716 100644 --- a/apps/mobile/app.json +++ b/apps/mobile/app.json @@ -26,7 +26,7 @@ }, "android": { "adaptiveIcon": { - "foregroundImage": "./assets/splash-icon.png", + "foregroundImage": "./assets/adaptive-icon-foreground.png", "backgroundColor": "#1a1a2e" }, "package": "com.sauci.app", diff --git a/apps/mobile/app/(app)/_layout.tsx b/apps/mobile/app/(app)/_layout.tsx index 226ec61..6e94418 100755 --- a/apps/mobile/app/(app)/_layout.tsx +++ b/apps/mobile/app/(app)/_layout.tsx @@ -149,12 +149,10 @@ export default function AppLayout() { router.push('/(app)/swipe'); break; case 'dares': - // TODO: Navigate to dares screen - console.log('Dares pressed - not yet implemented'); + router.push('/(app)/dares'); break; case 'quiz': - // TODO: Navigate to quiz screen - console.log('Quiz pressed - not yet implemented'); + router.push('/(app)/quiz'); break; } }, [router, closeRadialMenu]); @@ -609,6 +607,18 @@ export default function AppLayout() { href: null, // Hide from tab bar }} /> + + {/* Radial Menu Overlay */} diff --git a/apps/mobile/app/(app)/dares.tsx b/apps/mobile/app/(app)/dares.tsx new file mode 100644 index 0000000..e0fa202 --- /dev/null +++ b/apps/mobile/app/(app)/dares.tsx @@ -0,0 +1,170 @@ +import { View, Text, StyleSheet, Platform } from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +import Animated, { FadeInDown, FadeIn } from "react-native-reanimated"; +import { GradientBackground } from "../../src/components/ui"; +import { colors, spacing, radius, typography } from "../../src/theme"; + +const ACCENT_COLOR = colors.premium.gold; + +export default function DaresScreen() { + return ( + + + + {/* Icon */} + + + + + {/* Title section */} + ADVENTURE + Dares + + {/* Decorative separator */} + + + + + + + {/* Coming soon badge */} + + ARRIVING SOON + + + {/* Description */} + + Push your boundaries together with tantalising challenges designed to ignite passion and create unforgettable moments. + + + {/* Feature hints */} + + + + Provocative challenges + + + + Multiple intensity levels + + + + Designed for two + + + + {/* Bottom teaser */} + Are you ready to play? + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: spacing.lg, + paddingTop: Platform.OS === 'ios' ? 60 : 40, + paddingBottom: Platform.OS === 'ios' ? 120 : 100, + }, + content: { + width: '100%', + alignItems: 'center', + paddingHorizontal: spacing.md, + }, + iconContainer: { + width: 72, + height: 72, + borderRadius: 36, + backgroundColor: 'rgba(212, 175, 55, 0.1)', + borderWidth: 1, + borderColor: 'rgba(212, 175, 55, 0.2)', + justifyContent: 'center', + alignItems: 'center', + marginBottom: spacing.xl, + }, + label: { + ...typography.caption1, + fontWeight: '600', + letterSpacing: 3, + color: ACCENT_COLOR, + textAlign: 'center', + marginBottom: spacing.xs, + }, + title: { + ...typography.largeTitle, + color: colors.text, + textAlign: 'center', + }, + separator: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + marginVertical: spacing.lg, + width: 140, + }, + separatorLine: { + flex: 1, + height: 1, + backgroundColor: 'rgba(212, 175, 55, 0.3)', + }, + separatorDiamond: { + width: 6, + height: 6, + backgroundColor: ACCENT_COLOR, + transform: [{ rotate: '45deg' }], + marginHorizontal: spacing.md, + opacity: 0.6, + }, + badge: { + backgroundColor: 'rgba(212, 175, 55, 0.1)', + paddingHorizontal: spacing.lg, + paddingVertical: spacing.sm, + borderRadius: radius.full, + borderWidth: 1, + borderColor: 'rgba(212, 175, 55, 0.2)', + marginBottom: spacing.xl, + }, + badgeText: { + ...typography.caption2, + fontWeight: '600', + letterSpacing: 2, + color: ACCENT_COLOR, + }, + description: { + ...typography.body, + color: colors.textSecondary, + textAlign: 'center', + marginBottom: spacing.xl, + paddingHorizontal: spacing.md, + }, + features: { + marginBottom: spacing.xl, + }, + featureItem: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + marginBottom: spacing.sm, + }, + featureText: { + ...typography.subhead, + color: colors.text, + marginLeft: spacing.sm, + }, + teaser: { + ...typography.footnote, + fontStyle: 'italic', + color: colors.textTertiary, + textAlign: 'center', + }, +}); diff --git a/apps/mobile/app/(app)/quiz.tsx b/apps/mobile/app/(app)/quiz.tsx new file mode 100644 index 0000000..82cdad0 --- /dev/null +++ b/apps/mobile/app/(app)/quiz.tsx @@ -0,0 +1,170 @@ +import { View, Text, StyleSheet, Platform } from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +import Animated, { FadeInDown, FadeIn } from "react-native-reanimated"; +import { GradientBackground } from "../../src/components/ui"; +import { colors, spacing, radius, typography } from "../../src/theme"; + +const ACCENT_COLOR = colors.premium.rose; + +export default function QuizScreen() { + return ( + + + + {/* Icon */} + + + + + {/* Title section */} + INTIMACY + Quiz + + {/* Decorative separator */} + + + + + + + {/* Coming soon badge */} + + ARRIVING SOON + + + {/* Description */} + + Discover the depths of your connection through carefully crafted questions that reveal how well you truly know each other. + + + {/* Feature hints */} + + + + Uncover hidden desires + + + + Deepen your bond + + + + Track your journey + + + + {/* Bottom teaser */} + Something special is being crafted for you + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: spacing.lg, + paddingTop: Platform.OS === 'ios' ? 60 : 40, + paddingBottom: Platform.OS === 'ios' ? 120 : 100, + }, + content: { + width: '100%', + alignItems: 'center', + paddingHorizontal: spacing.md, + }, + iconContainer: { + width: 72, + height: 72, + borderRadius: 36, + backgroundColor: 'rgba(232, 164, 174, 0.1)', + borderWidth: 1, + borderColor: 'rgba(232, 164, 174, 0.2)', + justifyContent: 'center', + alignItems: 'center', + marginBottom: spacing.xl, + }, + label: { + ...typography.caption1, + fontWeight: '600', + letterSpacing: 3, + color: ACCENT_COLOR, + textAlign: 'center', + marginBottom: spacing.xs, + }, + title: { + ...typography.largeTitle, + color: colors.text, + textAlign: 'center', + }, + separator: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + marginVertical: spacing.lg, + width: 140, + }, + separatorLine: { + flex: 1, + height: 1, + backgroundColor: 'rgba(232, 164, 174, 0.3)', + }, + separatorDiamond: { + width: 6, + height: 6, + backgroundColor: ACCENT_COLOR, + transform: [{ rotate: '45deg' }], + marginHorizontal: spacing.md, + opacity: 0.6, + }, + badge: { + backgroundColor: 'rgba(232, 164, 174, 0.1)', + paddingHorizontal: spacing.lg, + paddingVertical: spacing.sm, + borderRadius: radius.full, + borderWidth: 1, + borderColor: 'rgba(232, 164, 174, 0.2)', + marginBottom: spacing.xl, + }, + badgeText: { + ...typography.caption2, + fontWeight: '600', + letterSpacing: 2, + color: ACCENT_COLOR, + }, + description: { + ...typography.body, + color: colors.textSecondary, + textAlign: 'center', + marginBottom: spacing.xl, + paddingHorizontal: spacing.md, + }, + features: { + marginBottom: spacing.xl, + }, + featureItem: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + marginBottom: spacing.sm, + }, + featureText: { + ...typography.subhead, + color: colors.text, + marginLeft: spacing.sm, + }, + teaser: { + ...typography.footnote, + fontStyle: 'italic', + color: colors.textTertiary, + textAlign: 'center', + }, +}); diff --git a/apps/mobile/app/(app)/swipe.tsx b/apps/mobile/app/(app)/swipe.tsx index 7502d5b..5471840 100755 --- a/apps/mobile/app/(app)/swipe.tsx +++ b/apps/mobile/app/(app)/swipe.tsx @@ -270,34 +270,68 @@ export default function SwipeScreen() { // Show "pair with partner" prompt when user doesn't have a partner yet if (!partner) { + const PAIR_ACCENT = colors.premium.rose; return ( - + - + + + + {/* Title section */} + {couple ? "ALMOST THERE" : "CONNECT"} + {couple ? "Waiting" : "Pair Up"} + + {/* Decorative separator */} + + + + + + + {/* Status badge */} + - - - - {couple ? "Waiting for your partner" : "Pair with your partner"} - - + {couple ? "INVITE SENT" : "MADE FOR TWO"} + + + {/* Description */} + {couple ? "Share your invite code so they can join you. Once paired, you'll both answer questions and discover what you agree on!" : "Sauci is made for two! Connect with your partner to start answering questions together and discover what you have in common." } + + {/* Feature hints */} + + + + Private and secure + + + + Discover hidden desires + + + + Chat about your matches + + + + {/* Bottom teaser */} + {couple ? "Your partner is just a code away" : "Begin your intimate journey together"} + router.push("/pairing")} - style={{ marginTop: spacing.xl }} - size="lg" + style={{ marginTop: spacing.lg }} > {couple ? "View Invite Code" : "Pair Now"} @@ -309,27 +343,62 @@ export default function SwipeScreen() { // Show "waiting for partner" message when user is too far ahead if (isBlocked && gapInfo) { + const WAITING_ACCENT = colors.premium.rose; return ( - + - + + + + {/* Title section */} + PATIENCE + Waiting + + {/* Decorative separator */} + + + + + + + {/* Status badge */} + - - - - Waiting for your partner - - - You've answered {gapInfo.unanswered} questions that your partner hasn't seen yet. Give them some time to catch up! + {gapInfo.unanswered} UNANSWERED + + + {/* Description */} + + You're ahead by {gapInfo.unanswered} questions. Give your partner some time to catch up so you can discover matches together. + + {/* Feature hints */} + + + + Matches happen together + + + + We'll notify your partner + + + + Check back anytime + + + + {/* Bottom teaser */} + Good things come to those who wait + + - - + {/* Icon */} + + - No packs enabled - - Enable at least one question pack to start playing. + + {/* Title section */} + GET STARTED + Choose Packs + + {/* Decorative separator */} + + + + + + + {/* Status badge */} + + NO PACKS ENABLED + + + {/* Description */} + + Select the question packs that interest you and your partner. Each pack explores different aspects of your relationship. + + {/* Feature hints */} + + + + From playful to passionate + + + + Safe space to explore + + + + New packs added regularly + + + + {/* Bottom teaser */} + Your journey of discovery awaits + router.push("/packs")} style={{ marginTop: spacing.lg }} @@ -372,20 +483,62 @@ export default function SwipeScreen() { } if (currentIndex >= questions.length) { + const CAUGHT_UP_ACCENT = colors.premium.rose; return ( - + - - + {/* Icon */} + + - All caught up! - - Check back later for more questions or try a different pack. + + {/* Title section */} + COMPLETE + All Caught Up + + {/* Decorative separator */} + + + + + + + {/* Status badge */} + + YOU'RE AHEAD + + + {/* Description */} + + You've answered all available questions. New questions are added regularly, or explore different packs. + + {/* Feature hints */} + + + + New questions weekly + + + + Try different packs + + + + Chat about your matches + + + + {/* Bottom teaser */} + More ways to connect are on the way + { } } +/** + * Get Expo push token with retry logic for Android. + * On Android, Firebase may not be initialized immediately on app start, + * so we retry a few times with increasing delays. + */ +async function getExpoPushTokenWithRetry( + projectId: string, + maxRetries = 5 +): Promise { + console.log(`[Push] Getting token for project: ${projectId}, platform: ${Platform.OS}`); + + for (let attempt = 0; attempt < maxRetries; attempt++) { + try { + console.log(`[Push] Attempt ${attempt + 1}/${maxRetries} to get push token...`); + const tokenData = await Notifications.getExpoPushTokenAsync({ projectId }); + console.log(`[Push] Successfully got token: ${tokenData.data}`); + return tokenData.data; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + console.log(`[Push] Attempt ${attempt + 1} failed: ${errorMessage}`); + + // Retry for any Firebase-related errors on Android + const isFirebaseError = + error instanceof Error && + (error.message.includes("Firebase") || + error.message.includes("FCM") || + error.message.includes("Google Play")); + + if (Platform.OS === "android" && attempt < maxRetries - 1) { + const delay = (attempt + 1) * 1000; + console.log(`[Push] Retrying in ${delay}ms...`); + await new Promise((resolve) => setTimeout(resolve, delay)); + continue; + } + + throw error; + } + } + return null; +} + /** * Check if current device needs to register for push notifications. * This handles the case where a user onboarded on one device (e.g., iOS) @@ -110,32 +151,44 @@ export async function clearPushToken(userId: string): Promise { * Returns true if a new token was registered. */ export async function checkAndRegisterPushToken(userId: string): Promise { + console.log(`[Push] checkAndRegisterPushToken called for user: ${userId}`); + if (Platform.OS === "web") { + console.log("[Push] Skipping - web platform"); return false; } try { // First check if permissions are already granted const { status } = await Notifications.getPermissionsAsync(); + console.log(`[Push] Current permission status: ${status}`); // If permissions not granted, we need to prompt the user // But only do this if we don't already have a token for this device if (status !== "granted") { - // Request permissions + console.log("[Push] Requesting permissions..."); const { status: newStatus } = await Notifications.requestPermissionsAsync(); + console.log(`[Push] New permission status: ${newStatus}`); if (newStatus !== "granted") { + console.log("[Push] Permission denied"); return false; } } // Get the current device's push token const projectId = Constants.expoConfig?.extra?.eas?.projectId; + console.log(`[Push] Project ID: ${projectId}`); if (!projectId) { + console.log("[Push] No project ID configured"); return false; } - const tokenData = await Notifications.getExpoPushTokenAsync({ projectId }); - const currentToken = tokenData.data; + const currentToken = await getExpoPushTokenWithRetry(projectId); + console.log(`[Push] Current token: ${currentToken}`); + if (!currentToken) { + console.log("[Push] Failed to get token"); + return false; + } // Fetch the stored token from the database const { data: profile } = await supabase @@ -144,16 +197,20 @@ export async function checkAndRegisterPushToken(userId: string): Promise= 16.28.0", + "appVersionSource": "remote" + }, + "build": { + "development": { + "developmentClient": true, + "distribution": "internal" + }, + "preview": { + "distribution": "internal" + }, + "production": { + "autoIncrement": true + } + }, + "submit": { + "production": {} + } +}