import React, { useState } from 'react'; import { BackHandler, Platform, Pressable, ScrollView, StyleSheet, Text, View, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { GoonWordmark } from '../components/GoonWordmark'; import { markAccepted } from '../lib/agegate'; import { theme } from '../theme'; interface Props { onAccept: () => void; } /** First-launch (and post-ToS-bump) gate. Blocks access to the app until user * affirms they're 18+ AND accepts the Disclaimer/Terms. * * Decline = exit app (Android: BackHandler.exitApp; iOS: cannot programmatically * close, so we show a "please uninstall" sentinel — covered by `declined` state). * * The body text is intentionally short and non-legalese; the canonical text is * in DISCLAIMER.md and linked via "Read full disclaimer". */ export function AgeGateScreen({ onAccept }: Props) { const [ageOk, setAgeOk] = useState(false); const [tosOk, setTosOk] = useState(false); const [declined, setDeclined] = useState(false); const canContinue = ageOk && tosOk; const onDecline = () => { if (Platform.OS === 'android') { BackHandler.exitApp(); } else { setDeclined(true); } }; const onContinue = async () => { await markAccepted(); onAccept(); }; if (declined) { return ( Access denied You must accept the Disclaimer and confirm you are at least 18 years old to use Goon. Please close and uninstall the app. ); } return ( Adult content · Self-hosted only Before you continue Goon is an aggregator for adult-content scene metadata. It indexes information from third-party sources and links to those sources for playback. The app itself contains no media. You must affirm • You are at least 18 years old, or the age of majority in your jurisdiction (whichever is greater). • Adult content is legal to access and store metadata about where you live. • You will use this app only on a device under your control. Do not share access with minors. • You understand that scene metadata, thumbnails, and links are sourced from third parties and may be inaccurate or unavailable. The full Disclaimer and Terms are shipped in the project repository (DISCLAIMER.md). By continuing, you accept those terms. setAgeOk((v) => !v)} label="I am at least 18 years old (or the age of majority in my jurisdiction)." /> setTosOk((v) => !v)} label="I have read and accept the Disclaimer and Terms." /> Decline Continue ); } function CheckRow({ checked, onToggle, label, }: { checked: boolean; onToggle: () => void; label: string; }) { return ( {checked ? : null} {label} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: theme.bg, }, scroll: { paddingHorizontal: 24, paddingTop: 32, paddingBottom: 48, }, declinedBox: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 32, }, title: { color: theme.fg, fontSize: 36, fontWeight: '700', letterSpacing: 1, }, subtitle: { color: theme.muted, fontSize: 14, marginTop: 4, marginBottom: 28, letterSpacing: 0.5, }, section: { marginBottom: 22, }, heading: { color: theme.fg, fontSize: 16, fontWeight: '600', marginBottom: 8, }, body: { color: theme.fg, fontSize: 14, lineHeight: 21, }, bullet: { color: theme.fg, fontSize: 14, lineHeight: 21, marginBottom: 6, }, fineprint: { color: theme.muted, fontSize: 12, lineHeight: 17, marginBottom: 24, marginTop: 4, fontStyle: 'italic', }, checks: { marginBottom: 24, gap: 14, }, checkRow: { flexDirection: 'row', alignItems: 'flex-start', gap: 12, }, checkbox: { width: 22, height: 22, borderRadius: 4, borderWidth: 2, borderColor: theme.border, backgroundColor: theme.bgElevated, justifyContent: 'center', alignItems: 'center', marginTop: 1, }, checkboxOn: { borderColor: theme.accent, backgroundColor: theme.accent, }, checkboxMark: { color: '#fff', fontSize: 14, fontWeight: '800', lineHeight: 14, }, checkLabel: { flex: 1, color: theme.fg, fontSize: 14, lineHeight: 20, }, actions: { flexDirection: 'row', gap: 12, marginTop: 8, }, btn: { flex: 1, paddingVertical: 14, borderRadius: 8, alignItems: 'center', }, btnDecline: { backgroundColor: theme.bgElevated, borderWidth: 1, borderColor: theme.border, }, btnDeclineText: { color: theme.muted, fontSize: 15, fontWeight: '600', }, btnContinue: { backgroundColor: theme.accent, }, btnDisabled: { backgroundColor: theme.bgElevated, borderWidth: 1, borderColor: theme.border, }, btnContinueText: { color: '#fff', fontSize: 15, fontWeight: '700', }, btnContinueTextDisabled: { color: theme.mutedDim, }, });