diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 17e3f01..337b292 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -22,7 +22,9 @@ "Bash(source:*)", "Bash(npm install:*)", "Bash(npx tsc:*)", - "Bash(npx expo:*)" + "Bash(npx expo:*)", + "Bash(find /var/home/felipecn/DEV/MeowSpool/mobile -name .env* -o -name *.env*)", + "Bash(grep -r \"useURL\\\\|useDeepLinking\\\\|Linking.parse\\\\|useLinkTo\" /var/home/felipecn/DEV/MeowSpool/mobile/app --include=*.tsx)" ] } } diff --git a/mobile/app/(auth)/login.tsx b/mobile/app/(auth)/login.tsx index 0c40805..1febd0a 100644 --- a/mobile/app/(auth)/login.tsx +++ b/mobile/app/(auth)/login.tsx @@ -7,7 +7,6 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { Ionicons } from '@expo/vector-icons'; import * as Google from 'expo-auth-session/providers/google'; -import * as WebBrowser from 'expo-web-browser'; import { Screen } from '@presentation/components/layout/Screen'; import { Input } from '@presentation/components/ui/Input'; import { Button } from '@presentation/components/ui/Button'; @@ -16,9 +15,6 @@ import { loginUseCase, googleLoginUseCase } from '@infrastructure/container'; import { useAuthStore } from '@store/authStore'; import { CatIcon } from '@presentation/components/icons/CatIcon'; -// Necessário para fechar o browser após redirect OAuth no Android/iOS -WebBrowser.maybeCompleteAuthSession(); - const schema = z.object({ email: z.string().email('E-mail inválido'), password: z.string().min(1, 'Senha obrigatória'), diff --git a/mobile/app/oauthredirect.tsx b/mobile/app/oauthredirect.tsx new file mode 100644 index 0000000..3936f84 --- /dev/null +++ b/mobile/app/oauthredirect.tsx @@ -0,0 +1,11 @@ +import * as WebBrowser from 'expo-web-browser'; + +// Esta tela é o destino do redirect OAuth (meowspool://oauthredirect?code=...). +// maybeCompleteAuthSession() detecta os parâmetros da URL, sinaliza o +// expo-auth-session para fechar o browser e entregar o resultado ao hook +// useAuthRequest em login.tsx. +WebBrowser.maybeCompleteAuthSession(); + +export default function OAuthRedirect(): null { + return null; +} diff --git a/mobile/plugins/withGoogleOAuth.js b/mobile/plugins/withGoogleOAuth.js index 894239a..e0ae555 100644 --- a/mobile/plugins/withGoogleOAuth.js +++ b/mobile/plugins/withGoogleOAuth.js @@ -2,16 +2,15 @@ * Plugin local: injeta o intent filter do Google OAuth no AndroidManifest.xml * durante o `expo prebuild`. * - * Sem esse intent filter, o Android não sabe que o MeowSpool deve interceptar - * o redirect do Google OAuth (scheme com.googleusercontent.apps.*), fazendo o - * Chrome tratar a URL como pesquisa. - * - * O scheme é o reverse do Android OAuth 2.0 client ID registrado no Google Cloud Console. + * expo-auth-session v5+ usa ${Application.applicationId}:/oauthredirect como + * redirect URI nativo (ex: com.meowspool.app:/oauthredirect). + * Sem esse intent filter, o Android não intercepta o redirect e o Chrome + * trata a URL como pesquisa, redirecionando para o google.com. */ -const { withAndroidManifest } = require('@expo/config-plugins'); +const { withAndroidManifest } = require("@expo/config-plugins"); -const GOOGLE_REVERSE_CLIENT_ID = - 'com.googleusercontent.apps.724520558909-bg5a7e4u24jmis8lgg41ucs0kv0nfp1v'; +// Scheme gerado por expo-auth-session v7: applicationId do app +const APP_SCHEME = "com.meowspool.app"; module.exports = function withGoogleOAuth(config) { return withAndroidManifest(config, (config) => { @@ -19,29 +18,29 @@ module.exports = function withGoogleOAuth(config) { const activities = manifest.manifest.application?.[0]?.activity ?? []; const mainActivity = activities.find( - (a) => a.$?.['android:name'] === '.MainActivity', + (a) => a.$?.["android:name"] === ".MainActivity", ); if (!mainActivity) return config; // Evita duplicar o intent filter em rebuilds consecutivos - const alreadyAdded = (mainActivity['intent-filter'] ?? []).some((filter) => + const alreadyAdded = (mainActivity["intent-filter"] ?? []).some((filter) => (filter.data ?? []).some( - (d) => d.$?.['android:scheme'] === GOOGLE_REVERSE_CLIENT_ID, + (d) => d.$?.["android:scheme"] === APP_SCHEME, ), ); if (alreadyAdded) return config; - mainActivity['intent-filter'] = [ - ...(mainActivity['intent-filter'] ?? []), + mainActivity["intent-filter"] = [ + ...(mainActivity["intent-filter"] ?? []), { - action: [{ $: { 'android:name': 'android.intent.action.VIEW' } }], + action: [{ $: { "android:name": "android.intent.action.VIEW" } }], category: [ - { $: { 'android:name': 'android.intent.category.DEFAULT' } }, - { $: { 'android:name': 'android.intent.category.BROWSABLE' } }, + { $: { "android:name": "android.intent.category.DEFAULT" } }, + { $: { "android:name": "android.intent.category.BROWSABLE" } }, ], - data: [{ $: { 'android:scheme': GOOGLE_REVERSE_CLIENT_ID } }], + data: [{ $: { "android:scheme": APP_SCHEME } }], }, ]; diff --git a/mobile/src/presentation/components/layout/Screen.tsx b/mobile/src/presentation/components/layout/Screen.tsx index 1da8c4b..b193194 100644 --- a/mobile/src/presentation/components/layout/Screen.tsx +++ b/mobile/src/presentation/components/layout/Screen.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { - SafeAreaView, ScrollView, KeyboardAvoidingView, View, @@ -8,6 +7,7 @@ import { StyleSheet, type ViewStyle, } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; import { colors, spacing } from '@shared/theme'; interface ScreenProps {