primeiro commit
This commit is contained in:
BIN
.gradle/9.3.1/checksums/checksums.lock
Normal file
BIN
.gradle/9.3.1/checksums/checksums.lock
Normal file
Binary file not shown.
BIN
.gradle/9.3.1/checksums/md5-checksums.bin
Normal file
BIN
.gradle/9.3.1/checksums/md5-checksums.bin
Normal file
Binary file not shown.
BIN
.gradle/9.3.1/checksums/sha1-checksums.bin
Normal file
BIN
.gradle/9.3.1/checksums/sha1-checksums.bin
Normal file
Binary file not shown.
BIN
.gradle/9.3.1/executionHistory/executionHistory.bin
Normal file
BIN
.gradle/9.3.1/executionHistory/executionHistory.bin
Normal file
Binary file not shown.
BIN
.gradle/9.3.1/executionHistory/executionHistory.lock
Normal file
BIN
.gradle/9.3.1/executionHistory/executionHistory.lock
Normal file
Binary file not shown.
BIN
.gradle/9.3.1/fileChanges/last-build.bin
Normal file
BIN
.gradle/9.3.1/fileChanges/last-build.bin
Normal file
Binary file not shown.
BIN
.gradle/9.3.1/fileHashes/fileHashes.bin
Normal file
BIN
.gradle/9.3.1/fileHashes/fileHashes.bin
Normal file
Binary file not shown.
BIN
.gradle/9.3.1/fileHashes/fileHashes.lock
Normal file
BIN
.gradle/9.3.1/fileHashes/fileHashes.lock
Normal file
Binary file not shown.
BIN
.gradle/9.3.1/fileHashes/resourceHashesCache.bin
Normal file
BIN
.gradle/9.3.1/fileHashes/resourceHashesCache.bin
Normal file
Binary file not shown.
0
.gradle/9.3.1/gc.properties
Normal file
0
.gradle/9.3.1/gc.properties
Normal file
BIN
.gradle/buildOutputCleanup/buildOutputCleanup.lock
Normal file
BIN
.gradle/buildOutputCleanup/buildOutputCleanup.lock
Normal file
Binary file not shown.
2
.gradle/buildOutputCleanup/cache.properties
Normal file
2
.gradle/buildOutputCleanup/cache.properties
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#Wed Mar 25 11:05:18 WET 2026
|
||||||
|
gradle.version=9.3.1
|
||||||
BIN
.gradle/buildOutputCleanup/outputFiles.bin
Normal file
BIN
.gradle/buildOutputCleanup/outputFiles.bin
Normal file
Binary file not shown.
2
.gradle/config.properties
Normal file
2
.gradle/config.properties
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#Wed Mar 18 09:24:04 WET 2026
|
||||||
|
java.home=/Applications/Android Studio.app/Contents/jbr/Contents/Home
|
||||||
BIN
.gradle/file-system.probe
Normal file
BIN
.gradle/file-system.probe
Normal file
Binary file not shown.
0
.gradle/vcs-1/gc.properties
Normal file
0
.gradle/vcs-1/gc.properties
Normal file
3
.idea/.gitignore
generated
vendored
Normal file
3
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
1
.idea/.name
generated
Normal file
1
.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Fluxup
|
||||||
6
.idea/AndroidProjectSystem.xml
generated
Normal file
6
.idea/AndroidProjectSystem.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AndroidProjectSystem">
|
||||||
|
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
1610
.idea/caches/deviceStreaming.xml
generated
Normal file
1610
.idea/caches/deviceStreaming.xml
generated
Normal file
File diff suppressed because it is too large
Load Diff
6
.idea/compiler.xml
generated
Normal file
6
.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<bytecodeTargetLevel target="21" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
11
.idea/deploymentTargetSelector.xml
generated
Normal file
11
.idea/deploymentTargetSelector.xml
generated
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="deploymentTargetSelector">
|
||||||
|
<selectionStates>
|
||||||
|
<SelectionState runConfigName="app">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
<DialogSelection />
|
||||||
|
</SelectionState>
|
||||||
|
</selectionStates>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
13
.idea/deviceManager.xml
generated
Normal file
13
.idea/deviceManager.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DeviceTable">
|
||||||
|
<option name="columnSorters">
|
||||||
|
<list>
|
||||||
|
<ColumnSorterState>
|
||||||
|
<option name="column" value="Name" />
|
||||||
|
<option name="order" value="ASCENDING" />
|
||||||
|
</ColumnSorterState>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
19
.idea/gradle.xml
generated
Normal file
19
.idea/gradle.xml
generated
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||||
|
<component name="GradleSettings">
|
||||||
|
<option name="linkedExternalProjectsSettings">
|
||||||
|
<GradleProjectSettings>
|
||||||
|
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||||
|
<option name="modules">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
<option value="$PROJECT_DIR$/app" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</GradleProjectSettings>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
10
.idea/migrations.xml
generated
Normal file
10
.idea/migrations.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectMigrations">
|
||||||
|
<option name="MigrateToGradleLocalJavaHome">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
9
.idea/misc.xml
generated
Normal file
9
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectType">
|
||||||
|
<option name="id" value="Android" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
17
.idea/runConfigurations.xml
generated
Normal file
17
.idea/runConfigurations.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RunConfigurationProducerService">
|
||||||
|
<option name="ignoredProducers">
|
||||||
|
<set>
|
||||||
|
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||||
|
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/studiobot.xml
generated
Normal file
6
.idea/studiobot.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="StudioBotProjectSettings">
|
||||||
|
<option name="shareContext" value="OptedIn" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"java.configuration.updateBuildConfiguration": "interactive"
|
||||||
|
}
|
||||||
14
app/(tabs)/_layout.tsx
Normal file
14
app/(tabs)/_layout.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Tabs } from 'expo-router';
|
||||||
|
|
||||||
|
export default function TabLayout() {
|
||||||
|
return (
|
||||||
|
<Tabs
|
||||||
|
initialRouteName="inicio" // Define qual o nome do ficheiro que abre primeiro
|
||||||
|
screenOptions={{ headerShown: false }}
|
||||||
|
>
|
||||||
|
<Tabs.Screen name="index" options={{ title: 'Login' }} />
|
||||||
|
<Tabs.Screen name="inicio" options={{ title: 'Início' }} />
|
||||||
|
<Tabs.Screen name="registo" options={{ title: 'Registo' }} />
|
||||||
|
</Tabs>
|
||||||
|
);
|
||||||
|
}
|
||||||
123
app/(tabs)/esqueciPalavraPasse.tsx
Normal file
123
app/(tabs)/esqueciPalavraPasse.tsx
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert, ActivityIndicator, SafeAreaView, KeyboardAvoidingView, Platform } from 'react-native';
|
||||||
|
import { useRouter } from 'expo-router';
|
||||||
|
import { Colors } from '../../src/components/common/theme';
|
||||||
|
|
||||||
|
export default function EsqueciPalavraPasse() {
|
||||||
|
const router = useRouter();
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
if (!email) {
|
||||||
|
Alert.alert('Erro', 'Por favor insira o seu email');
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
// simulação de envio de instruções
|
||||||
|
setTimeout(() => {
|
||||||
|
setLoading(false);
|
||||||
|
Alert.alert('Sucesso', 'Um email com instruções foi enviado.');
|
||||||
|
// o TypeScript não reconhece "/inicio" nas rotas tipadas; forçamos o cast
|
||||||
|
router.replace('/inicio' as any);
|
||||||
|
}, 1500);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.safe}>
|
||||||
|
<KeyboardAvoidingView
|
||||||
|
style={styles.container}
|
||||||
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
|
>
|
||||||
|
<Text style={styles.title}>Fluxup</Text>
|
||||||
|
<Text style={styles.subtitle}>Recuperar palavra‑passe</Text>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Email"
|
||||||
|
placeholderTextColor="#999"
|
||||||
|
keyboardType="email-address"
|
||||||
|
autoCapitalize="none"
|
||||||
|
value={email}
|
||||||
|
onChangeText={setEmail}
|
||||||
|
editable={!loading}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.button, loading && styles.buttonDisabled]}
|
||||||
|
onPress={handleReset}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? <ActivityIndicator color="#fff" /> : <Text style={styles.buttonText}>Enviar</Text>}
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity onPress={() => router.back()}>
|
||||||
|
<Text style={styles.linkText}>Voltar</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</KeyboardAvoidingView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const primary = Colors.light.tint;
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
safe: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: Colors.light.background,
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: 20,
|
||||||
|
backgroundColor: Colors.light.background,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 32,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
marginBottom: 8,
|
||||||
|
color: primary,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.light.icon,
|
||||||
|
marginBottom: 24,
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
width: '100%',
|
||||||
|
height: 50,
|
||||||
|
borderRadius: 8,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#ddd',
|
||||||
|
paddingHorizontal: 15,
|
||||||
|
marginBottom: 15,
|
||||||
|
fontSize: 16,
|
||||||
|
backgroundColor: '#f9f9f9',
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
width: '100%',
|
||||||
|
height: 50,
|
||||||
|
borderRadius: 8,
|
||||||
|
backgroundColor: primary,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 10,
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
buttonDisabled: {
|
||||||
|
backgroundColor: '#ccc',
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
linkText: {
|
||||||
|
color: primary,
|
||||||
|
fontSize: 14,
|
||||||
|
marginTop: 10,
|
||||||
|
},
|
||||||
|
});
|
||||||
219
app/(tabs)/index.tsx
Normal file
219
app/(tabs)/index.tsx
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
TouchableOpacity,
|
||||||
|
StyleSheet,
|
||||||
|
Alert,
|
||||||
|
ActivityIndicator,
|
||||||
|
SafeAreaView,
|
||||||
|
KeyboardAvoidingView,
|
||||||
|
Platform,
|
||||||
|
} from 'react-native';
|
||||||
|
import { useRouter } from 'expo-router';
|
||||||
|
import { usuariosService } from '../../src/lib/supabase';
|
||||||
|
import { Colors } from '../../src/components/common/theme';
|
||||||
|
import { useColorScheme } from '../../src/hooks/use-color-scheme';
|
||||||
|
import { Lock, Mail } from 'lucide-react-native';
|
||||||
|
import { Input } from '../../src/components/common/input';
|
||||||
|
import { Button } from '../../src/components/common/button';
|
||||||
|
|
||||||
|
export default function LoginScreen() {
|
||||||
|
const router = useRouter();
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [palavraPasse, setPalavraPasse] = useState('');
|
||||||
|
const [idUsuario, setIdUsuario] = useState('');
|
||||||
|
const [usuario, setUsuario] = useState('');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const colorScheme = useColorScheme();
|
||||||
|
const theme = colorScheme === 'dark' ? 'dark' : 'light';
|
||||||
|
const primary = Colors[theme].primary;
|
||||||
|
|
||||||
|
const handleLogin = async () => {
|
||||||
|
// Validação dos campos
|
||||||
|
if (!email || !palavraPasse || !idUsuario || !usuario) {
|
||||||
|
Alert.alert('Erro', 'Por favor, preencha todos os campos');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validação de email
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
if (!emailRegex.test(email)) {
|
||||||
|
Alert.alert('Erro', 'Email inválido');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// login no Supabase
|
||||||
|
const { data, error } = await usuariosService.login(email, palavraPasse);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
Alert.alert('Erro', 'Email ou palavra passe incorretos');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
Alert.alert('Sucesso', `Bem-vindo, ${(data as any).usuario || 'usuário'}!`);
|
||||||
|
// quando o login é bem‑sucedido, vai para a página inicial
|
||||||
|
router.replace('/inicio');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
Alert.alert('Erro', 'Erro ao conectar ao servidor');
|
||||||
|
console.error(err);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={[styles.safe, { backgroundColor: Colors[theme].background }]}>
|
||||||
|
<KeyboardAvoidingView
|
||||||
|
style={[styles.container, { backgroundColor: Colors[theme].background }]}
|
||||||
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
|
>
|
||||||
|
<Text style={[styles.title, { color: primary } ]}>Fluxup</Text>
|
||||||
|
<Text style={[styles.subtitle, { color: Colors[theme].textMuted } ]}>Entre na sua conta</Text>
|
||||||
|
|
||||||
|
{/* Campo de Email */}
|
||||||
|
<View style={[styles.inputContainer, { backgroundColor: Colors[theme].card, borderColor: Colors[theme].border }] }>
|
||||||
|
<Mail size={20} color={Colors[theme].icon} style={styles.icon} />
|
||||||
|
<Input
|
||||||
|
placeholder="Email"
|
||||||
|
value={email}
|
||||||
|
onChangeText={setEmail}
|
||||||
|
keyboardType="email-address"
|
||||||
|
editable={!loading}
|
||||||
|
style={styles.input}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Campo de Palavra Passe */}
|
||||||
|
<View style={[styles.inputContainer, { backgroundColor: Colors[theme].card, borderColor: Colors[theme].border }] }>
|
||||||
|
<Lock size={20} color={Colors[theme].icon} style={styles.icon} />
|
||||||
|
<Input
|
||||||
|
placeholder="Palavra Passe"
|
||||||
|
value={palavraPasse}
|
||||||
|
onChangeText={setPalavraPasse}
|
||||||
|
secureTextEntry
|
||||||
|
editable={!loading}
|
||||||
|
style={styles.input}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Campo de ID Usuário */}
|
||||||
|
<Input
|
||||||
|
placeholder="ID Usuário"
|
||||||
|
value={idUsuario}
|
||||||
|
onChangeText={setIdUsuario}
|
||||||
|
editable={!loading}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Campo de Usuário */}
|
||||||
|
<Input
|
||||||
|
placeholder="Usuário"
|
||||||
|
value={usuario}
|
||||||
|
onChangeText={setUsuario}
|
||||||
|
editable={!loading}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Botão de Login */}
|
||||||
|
<Button
|
||||||
|
title="Entrar"
|
||||||
|
onPress={handleLogin}
|
||||||
|
isLoading={loading}
|
||||||
|
disabled={loading}
|
||||||
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
|
containerStyle={styles.button}
|
||||||
|
textStyle={styles.buttonText}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View style={styles.footerLinks}>
|
||||||
|
{/* Botão de Registo */}
|
||||||
|
<TouchableOpacity onPress={() => router.push('/registo')}>
|
||||||
|
<Text style={[styles.linkText, { color: primary }]}>
|
||||||
|
Não tem conta? <Text style={[styles.linkBold, { color: primary } ]}>Registe-se</Text>
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* Botão de Esqueci Palavra Passe */}
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => router.push('/esqueciPalavraPasse')}
|
||||||
|
>
|
||||||
|
<Text style={[styles.linkText, { color: primary }]}>Esqueci a palavra‑passe</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</KeyboardAvoidingView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
safe: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: 20,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 32,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontSize: 16,
|
||||||
|
marginBottom: 24,
|
||||||
|
},
|
||||||
|
inputContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: '100%',
|
||||||
|
borderRadius: 8,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#ddd',
|
||||||
|
marginBottom: 15,
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
marginRight: 10,
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
flex: 1,
|
||||||
|
height: 50,
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
width: '100%',
|
||||||
|
height: 50,
|
||||||
|
borderRadius: 8,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 20,
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
buttonDisabled: {
|
||||||
|
backgroundColor: '#ccc',
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
linkText: {
|
||||||
|
fontSize: 14,
|
||||||
|
marginTop: 10,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
linkBold: {
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
footerLinks: {
|
||||||
|
marginTop: 10,
|
||||||
|
},
|
||||||
|
});
|
||||||
349
app/(tabs)/inicio.tsx
Normal file
349
app/(tabs)/inicio.tsx
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
TouchableOpacity,
|
||||||
|
StyleSheet,
|
||||||
|
SafeAreaView,
|
||||||
|
ScrollView,
|
||||||
|
Alert,
|
||||||
|
} from 'react-native';
|
||||||
|
import { useRouter } from 'expo-router';
|
||||||
|
import {
|
||||||
|
Home,
|
||||||
|
FileText,
|
||||||
|
BarChart2,
|
||||||
|
Settings,
|
||||||
|
Star,
|
||||||
|
} from 'lucide-react-native';
|
||||||
|
|
||||||
|
const PURPLE = '#6a00fa';
|
||||||
|
const CARD_BG = '#fff';
|
||||||
|
|
||||||
|
export default function InicioScreen() {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// --- authentication guard (placeholder) ---
|
||||||
|
useEffect(() => {
|
||||||
|
const isAuthenticated = true; // trocar por verificação real
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
router.replace('/login');
|
||||||
|
}
|
||||||
|
}, [router]);
|
||||||
|
|
||||||
|
// --- checklist state ---
|
||||||
|
const [tasks, setTasks] = useState([
|
||||||
|
{ id: 1, label: 'Estudar para intermédio', done: false },
|
||||||
|
{ id: 2, label: 'Ler artigo técnico', done: false },
|
||||||
|
{ id: 3, label: 'Fazer exercícios', done: false },
|
||||||
|
]);
|
||||||
|
const toggleTask = (id: number) => {
|
||||||
|
setTasks((prev) =>
|
||||||
|
prev.map((t) => (t.id === id ? { ...t, done: !t.done } : t))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- habits rating ---
|
||||||
|
const [rating, setRating] = useState(4);
|
||||||
|
|
||||||
|
// --- pomodoro timer ---
|
||||||
|
const [secondsLeft, setSecondsLeft] = useState(25 * 60);
|
||||||
|
const [running, setRunning] = useState(false);
|
||||||
|
const intervalRef = useRef<number | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (running) {
|
||||||
|
const id = setInterval(() => {
|
||||||
|
setSecondsLeft((s) => {
|
||||||
|
if (s <= 1) {
|
||||||
|
if (intervalRef.current !== null) {
|
||||||
|
clearInterval(intervalRef.current);
|
||||||
|
}
|
||||||
|
setRunning(false);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return s - 1;
|
||||||
|
});
|
||||||
|
}, 1000) as unknown as number;
|
||||||
|
intervalRef.current = id;
|
||||||
|
} else {
|
||||||
|
if (intervalRef.current !== null) {
|
||||||
|
clearInterval(intervalRef.current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
if (intervalRef.current !== null) {
|
||||||
|
clearInterval(intervalRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [running]);
|
||||||
|
|
||||||
|
const startTimer = () => {
|
||||||
|
if (!running) {
|
||||||
|
setSecondsLeft(25 * 60);
|
||||||
|
setRunning(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatTime = (sec: number) => {
|
||||||
|
const m = Math.floor(sec / 60)
|
||||||
|
.toString()
|
||||||
|
.padStart(2, '0');
|
||||||
|
const s = (sec % 60).toString().padStart(2, '0');
|
||||||
|
return `${m}:${s}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.safe}>
|
||||||
|
<ScrollView contentContainerStyle={styles.scroll}>
|
||||||
|
{/* header */}
|
||||||
|
<View style={styles.header}>
|
||||||
|
<View>
|
||||||
|
<Text style={styles.greeting}>Hoje é um bom dia</Text>
|
||||||
|
<Text style={styles.subtitle}>
|
||||||
|
Olá, Matheus! Preparado para o seu dia de produtividade?
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.waveEmoji}>👋</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* desafios do dia card */}
|
||||||
|
<View style={styles.card}>
|
||||||
|
<Text style={styles.cardTitle}>Desafios do Dia</Text>
|
||||||
|
{tasks.map((t) => (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={t.id}
|
||||||
|
style={styles.checkItem}
|
||||||
|
onPress={() => toggleTask(t.id)}
|
||||||
|
>
|
||||||
|
<Text style={[styles.checkBox, t.done && styles.done]}>✓</Text>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.checkLabel,
|
||||||
|
t.done && styles.checkLabelDone,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{t.label}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.addButton}
|
||||||
|
onPress={() => Alert.alert('Adicionar','implementação futura')}
|
||||||
|
>
|
||||||
|
<Text style={styles.addButtonText}>+ Adicionar desafios diários</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* hábitos card */}
|
||||||
|
<View style={styles.card}>
|
||||||
|
<Text style={styles.cardTitle}>Hábitos</Text>
|
||||||
|
<View style={styles.ratingRow}>
|
||||||
|
{Array.from({ length: 5 }).map((_, i) => (
|
||||||
|
<TouchableOpacity key={i} onPress={() => setRating(i + 1)} activeOpacity={0.7}>
|
||||||
|
<Star
|
||||||
|
size={20}
|
||||||
|
stroke={i < rating ? '#f5c518' : '#ccc'}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
<View style={styles.habitsRow}>
|
||||||
|
<TouchableOpacity style={[styles.habitButton, { backgroundColor: '#A0E9FD' }]}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<Text>📘</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={[styles.habitButton, { backgroundColor: '#B2F59C' }]}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<Text>🧘</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={[styles.habitButton, { backgroundColor: '#FFD59C' }]}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<Text>🏋️♂️</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={[styles.habitButton, styles.habitAdd]}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<Text>+</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* modo foco card */}
|
||||||
|
<View style={styles.card}>
|
||||||
|
<Text style={styles.cardTitle}>Modo Foco</Text>
|
||||||
|
<View style={styles.focusRow}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.timerBlock}
|
||||||
|
onPress={startTimer}
|
||||||
|
>
|
||||||
|
<Text style={styles.timerText}>{formatTime(secondsLeft)}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Text style={styles.focusText}>
|
||||||
|
Produtividade é o segredo do sucesso
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
{/* bottom navigation */}
|
||||||
|
<View style={styles.bottomNav}>
|
||||||
|
<TouchableOpacity style={styles.navItem} disabled>
|
||||||
|
<Home size={24} stroke={PURPLE} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={styles.navItem}>
|
||||||
|
<FileText size={24} stroke="#666" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={styles.navItem}>
|
||||||
|
<BarChart2 size={24} stroke="#666" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={styles.navItem}>
|
||||||
|
<Settings size={24} stroke="#666" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
safe: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: PURPLE,
|
||||||
|
},
|
||||||
|
scroll: {
|
||||||
|
padding: 20,
|
||||||
|
paddingBottom: 80,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
greeting: {
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: '#fff',
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: '#eee',
|
||||||
|
marginTop: 4,
|
||||||
|
},
|
||||||
|
waveEmoji: {
|
||||||
|
fontSize: 28,
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
backgroundColor: CARD_BG,
|
||||||
|
borderRadius: 24,
|
||||||
|
padding: 16,
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
cardTitle: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '600',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
checkItem: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
checkBox: {
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#666',
|
||||||
|
marginRight: 8,
|
||||||
|
textAlign: 'center',
|
||||||
|
lineHeight: 24,
|
||||||
|
},
|
||||||
|
done: {
|
||||||
|
backgroundColor: '#6a0fa0',
|
||||||
|
color: '#fff',
|
||||||
|
borderColor: '#6a0fa0',
|
||||||
|
},
|
||||||
|
checkLabel: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#333',
|
||||||
|
},
|
||||||
|
checkLabelDone: {
|
||||||
|
textDecorationLine: 'line-through',
|
||||||
|
color: '#999',
|
||||||
|
},
|
||||||
|
addButton: {
|
||||||
|
marginTop: 12,
|
||||||
|
backgroundColor: PURPLE,
|
||||||
|
padding: 12,
|
||||||
|
borderRadius: 8,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
addButtonText: {
|
||||||
|
color: '#fff',
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
ratingRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
habitsRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
habitButton: {
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
borderRadius: 8,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
habitAdd: {
|
||||||
|
borderWidth: 1,
|
||||||
|
borderStyle: 'dashed',
|
||||||
|
borderColor: '#666',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
|
focusRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
timerBlock: {
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
backgroundColor: '#3b44f6',
|
||||||
|
borderRadius: 12,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
timerText: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
focusText: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: 14,
|
||||||
|
color: '#333',
|
||||||
|
},
|
||||||
|
bottomNav: {
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: 60,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-around',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: CARD_BG,
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderColor: '#ddd',
|
||||||
|
},
|
||||||
|
navItem: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
});
|
||||||
29
app/(tabs)/modal.tsx
Normal file
29
app/(tabs)/modal.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { Link } from 'expo-router';
|
||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
|
import { ThemedText } from '../../src/components/common/themed-text';
|
||||||
|
import { ThemedView } from '../../src/components/common/themed-view';
|
||||||
|
|
||||||
|
export default function ModalScreen() {
|
||||||
|
return (
|
||||||
|
<ThemedView style={styles.container}>
|
||||||
|
<ThemedText type="title">This is a modal</ThemedText>
|
||||||
|
<Link href="/" dismissTo style={styles.link}>
|
||||||
|
<ThemedText type="link">Go to home screen</ThemedText>
|
||||||
|
</Link>
|
||||||
|
</ThemedView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
padding: 20,
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
marginTop: 15,
|
||||||
|
paddingVertical: 15,
|
||||||
|
},
|
||||||
|
});
|
||||||
300
app/(tabs)/registo.tsx
Normal file
300
app/(tabs)/registo.tsx
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
TouchableOpacity,
|
||||||
|
StyleSheet,
|
||||||
|
Alert,
|
||||||
|
ActivityIndicator,
|
||||||
|
KeyboardAvoidingView,
|
||||||
|
Platform,
|
||||||
|
ScrollView,
|
||||||
|
SafeAreaView,
|
||||||
|
} from 'react-native';
|
||||||
|
import { useRouter, Link } from 'expo-router';
|
||||||
|
import { User, Mail, Lock, Phone, ArrowLeft, CheckCircle } from 'lucide-react-native';
|
||||||
|
import { Colors } from '../../src/components/common/theme';
|
||||||
|
|
||||||
|
// import { supabase } from '../lib/supabase';
|
||||||
|
|
||||||
|
export default function RegisterScreen() {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const [username, setUsername] = useState('');
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [phone, setPhone] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [confirmPassword, setConfirmPassword] = useState('');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
async function handleRegister() {
|
||||||
|
// 1. Validação simples antes de enviar
|
||||||
|
if (password !== confirmPassword) {
|
||||||
|
Alert.alert('Erro', 'As palavras-passe não coincidem.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (username.length < 3) {
|
||||||
|
Alert.alert('Erro', 'O nome de utilizador deve ter pelo menos 3 caracteres.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
// Simulação de registo
|
||||||
|
setTimeout(() => {
|
||||||
|
setLoading(false);
|
||||||
|
Alert.alert('Sucesso', 'Conta criada com sucesso! A entrar...');
|
||||||
|
// em vez de voltar ao login, encaminhamos directamente para a tela inicial
|
||||||
|
router.replace('/inicio' as any);
|
||||||
|
}, 1500);
|
||||||
|
|
||||||
|
/* LÓGICA REAL DO SUPABASE (Para depois):
|
||||||
|
|
||||||
|
// 1. Criar utilizador na Auth
|
||||||
|
const { data: authData, error: authError } = await supabase.auth.signUp({
|
||||||
|
email: email,
|
||||||
|
password: password,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (authError) {
|
||||||
|
Alert.alert('Erro', authError.message);
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Guardar dados extra na tabela 'profiles'
|
||||||
|
if (authData.user) {
|
||||||
|
const { error: profileError } = await supabase
|
||||||
|
.from('profiles')
|
||||||
|
.insert([{
|
||||||
|
id: authData.user.id,
|
||||||
|
username: username,
|
||||||
|
phone_number: phone,
|
||||||
|
full_name: username, // Podes mudar isto depois
|
||||||
|
updated_at: new Date(),
|
||||||
|
}]);
|
||||||
|
|
||||||
|
if (profileError) {
|
||||||
|
Alert.alert('Aviso', 'Conta criada, mas houve um erro ao salvar o perfil.');
|
||||||
|
} else {
|
||||||
|
Alert.alert('Sucesso', 'Verifica o teu email para confirmar a conta!');
|
||||||
|
router.replace('/inicio');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={styles.safe}>
|
||||||
|
<KeyboardAvoidingView
|
||||||
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
|
style={styles.container}
|
||||||
|
>
|
||||||
|
<ScrollView contentContainerStyle={styles.scrollContent}>
|
||||||
|
|
||||||
|
{/* Botão Voltar */}
|
||||||
|
<TouchableOpacity onPress={() => router.back()} style={styles.backButton}>
|
||||||
|
<ArrowLeft size={24} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<View style={styles.header}>
|
||||||
|
<Text style={styles.title}>Fluxup</Text>
|
||||||
|
<Text style={styles.subtitle}>Registar nova conta</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.form}>
|
||||||
|
|
||||||
|
{/* Nome de Utilizador */}
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<User size={20} />
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Nome de utilizador"
|
||||||
|
placeholderTextColor="#999"
|
||||||
|
value={username}
|
||||||
|
onChangeText={setUsername}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Email */}
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<Mail size={20} />
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Email"
|
||||||
|
placeholderTextColor="#999"
|
||||||
|
value={email}
|
||||||
|
onChangeText={setEmail}
|
||||||
|
keyboardType="email-address"
|
||||||
|
autoCapitalize="none"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Telemóvel */}
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<Phone size={20} />
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Número de telemóvel"
|
||||||
|
placeholderTextColor="#999"
|
||||||
|
value={phone}
|
||||||
|
onChangeText={setPhone}
|
||||||
|
keyboardType="phone-pad"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Password */}
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<Lock size={20} />
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Palavra-passe"
|
||||||
|
placeholderTextColor="#999"
|
||||||
|
value={password}
|
||||||
|
onChangeText={setPassword}
|
||||||
|
secureTextEntry
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Confirmar Password */}
|
||||||
|
<View style={[
|
||||||
|
styles.inputContainer,
|
||||||
|
// Muda a cor da borda se as senhas não baterem (e se já tiver escrito algo)
|
||||||
|
confirmPassword.length > 0 && password !== confirmPassword ? { borderColor: '#EF4444' } : {}
|
||||||
|
]}>
|
||||||
|
<Lock size={20} />
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Confirmar palavra-passe"
|
||||||
|
placeholderTextColor="#999"
|
||||||
|
value={confirmPassword}
|
||||||
|
onChangeText={setConfirmPassword}
|
||||||
|
secureTextEntry
|
||||||
|
/>
|
||||||
|
{/* Ícone de verificação verde se as senhas baterem */}
|
||||||
|
{password.length > 0 && password === confirmPassword && (
|
||||||
|
<CheckCircle size={20} />
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.button}
|
||||||
|
onPress={handleRegister}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<ActivityIndicator color="#fff" />
|
||||||
|
) : (
|
||||||
|
<Text style={styles.buttonText}>Registar</Text>
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<View style={styles.footer}>
|
||||||
|
<Text style={styles.footerText}>Já tens conta? </Text>
|
||||||
|
<Link href="/" asChild>
|
||||||
|
<TouchableOpacity>
|
||||||
|
<Text style={styles.linkText}>Entrar</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</Link>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</KeyboardAvoidingView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const primary = Colors.light.tint;
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
safe: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: Colors.light.background,
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: Colors.light.background,
|
||||||
|
},
|
||||||
|
scrollContent: {
|
||||||
|
flexGrow: 1,
|
||||||
|
padding: 24,
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
backButton: {
|
||||||
|
marginTop: 20,
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
marginBottom: 30,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 32,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: primary,
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.light.icon,
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
inputContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: 12,
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
height: 56,
|
||||||
|
marginBottom: 16,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#E5E7EB',
|
||||||
|
},
|
||||||
|
inputIcon: {
|
||||||
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
flex: 1,
|
||||||
|
height: '100%',
|
||||||
|
color: Colors.light.text,
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
backgroundColor: primary,
|
||||||
|
height: 56,
|
||||||
|
borderRadius: 12,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 10,
|
||||||
|
shadowColor: primary,
|
||||||
|
shadowOffset: { width: 0, height: 4 },
|
||||||
|
shadowOpacity: 0.3,
|
||||||
|
shadowRadius: 8,
|
||||||
|
elevation: 4,
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginTop: 24,
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
footerText: {
|
||||||
|
color: Colors.light.icon,
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
linkText: {
|
||||||
|
color: primary,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
});
|
||||||
BIN
app/.DS_Store
vendored
Normal file
BIN
app/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
app/.gradle/8.9/checksums/checksums.lock
Normal file
BIN
app/.gradle/8.9/checksums/checksums.lock
Normal file
Binary file not shown.
BIN
app/.gradle/8.9/fileChanges/last-build.bin
Normal file
BIN
app/.gradle/8.9/fileChanges/last-build.bin
Normal file
Binary file not shown.
BIN
app/.gradle/8.9/fileHashes/fileHashes.lock
Normal file
BIN
app/.gradle/8.9/fileHashes/fileHashes.lock
Normal file
Binary file not shown.
0
app/.gradle/8.9/gc.properties
Normal file
0
app/.gradle/8.9/gc.properties
Normal file
BIN
app/.gradle/buildOutputCleanup/buildOutputCleanup.lock
Normal file
BIN
app/.gradle/buildOutputCleanup/buildOutputCleanup.lock
Normal file
Binary file not shown.
2
app/.gradle/buildOutputCleanup/cache.properties
Normal file
2
app/.gradle/buildOutputCleanup/cache.properties
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#Tue Mar 17 15:42:21 WET 2026
|
||||||
|
gradle.version=8.9
|
||||||
0
app/.gradle/vcs-1/gc.properties
Normal file
0
app/.gradle/vcs-1/gc.properties
Normal file
58
app/build.gradle
Normal file
58
app/build.gradle
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
plugins {
|
||||||
|
id 'com.android.application'
|
||||||
|
id 'com.google.gms.google-services'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace 'com.fluxup.app'
|
||||||
|
compileSdk 35
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "com.fluxup.app"
|
||||||
|
minSdk 24
|
||||||
|
targetSdk 35
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
|
implementation 'com.google.android.material:material:1.9.0'
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
|
implementation 'androidx.navigation:navigation-fragment:2.6.0'
|
||||||
|
implementation 'androidx.navigation:navigation-ui:2.6.0'
|
||||||
|
|
||||||
|
// Align Kotlin versions
|
||||||
|
implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0')
|
||||||
|
|
||||||
|
// Supabase dependencies (Placeholder for actual SDK or Retrofit)
|
||||||
|
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||||
|
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
|
||||||
|
|
||||||
|
// Firebase
|
||||||
|
implementation platform('com.google.firebase:firebase-bom:32.7.0')
|
||||||
|
implementation 'com.google.firebase:firebase-auth'
|
||||||
|
implementation 'com.google.firebase:firebase-firestore'
|
||||||
|
implementation 'androidx.credentials:credentials:1.6.0'
|
||||||
|
implementation 'androidx.credentials:credentials-play-services-auth:1.6.0'
|
||||||
|
implementation 'com.google.android.libraries.identity.googleid:googleid:1.2.0'
|
||||||
|
|
||||||
|
testImplementation 'junit:junit:4.13.2'
|
||||||
|
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||||
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||||
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
}
|
||||||
BIN
app/build/.DS_Store
vendored
Normal file
BIN
app/build/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="gcm_defaultSenderId" translatable="false">659566714595</string>
|
||||||
|
<string name="google_api_key" translatable="false">AIzaSyDzpQM5ewUlGCb1gROzSxBXJYavyDrm1kk</string>
|
||||||
|
<string name="google_app_id" translatable="false">1:659566714595:android:e2dba99e1738528d966fd6</string>
|
||||||
|
<string name="google_crash_reporting_api_key" translatable="false">AIzaSyDzpQM5ewUlGCb1gROzSxBXJYavyDrm1kk</string>
|
||||||
|
<string name="google_storage_bucket" translatable="false">fluxupp.firebasestorage.app</string>
|
||||||
|
<string name="project_id" translatable="false">fluxupp</string>
|
||||||
|
</resources>
|
||||||
1
app/build/gmpAppId.txt
Normal file
1
app/build/gmpAppId.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
1:659566714595:android:e2dba99e1738528d966fd6
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
com.fluxup.app-pngs-0 /Users/230407/Desktop/FluxupP/app/build/generated/res/pngs/debug
|
||||||
|
com.fluxup.app-res-1 /Users/230407/Desktop/FluxupP/app/build/generated/res/processDebugGoogleServices
|
||||||
|
com.fluxup.app-resValues-2 /Users/230407/Desktop/FluxupP/app/build/generated/res/resValues/debug
|
||||||
|
com.fluxup.app-updated_navigation_xml-3 /Users/230407/Desktop/FluxupP/app/build/generated/updated_navigation_xml/debug
|
||||||
|
com.fluxup.app-packageDebugResources-4 /Users/230407/Desktop/FluxupP/app/build/intermediates/incremental/debug/packageDebugResources/merged.dir
|
||||||
|
com.fluxup.app-packageDebugResources-5 /Users/230407/Desktop/FluxupP/app/build/intermediates/incremental/debug/packageDebugResources/stripped.dir
|
||||||
|
com.fluxup.app-debug-6 /Users/230407/Desktop/FluxupP/app/build/intermediates/merged_res/debug/mergeDebugResources
|
||||||
|
com.fluxup.app-debug-7 /Users/230407/Desktop/FluxupP/app/src/debug/res
|
||||||
|
com.fluxup.app-main-8 /Users/230407/Desktop/FluxupP/app/src/main/res
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
BIN
app/build/intermediates/apk/debug/app-debug.apk
Normal file
BIN
app/build/intermediates/apk/debug/app-debug.apk
Normal file
Binary file not shown.
21
app/build/intermediates/apk/debug/output-metadata.json
Normal file
21
app/build/intermediates/apk/debug/output-metadata.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"artifactType": {
|
||||||
|
"type": "APK",
|
||||||
|
"kind": "Directory"
|
||||||
|
},
|
||||||
|
"applicationId": "com.fluxup.app",
|
||||||
|
"variantName": "debug",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "SINGLE",
|
||||||
|
"filters": [],
|
||||||
|
"attributes": [],
|
||||||
|
"versionCode": 1,
|
||||||
|
"versionName": "1.0",
|
||||||
|
"outputFile": "app-debug.apk"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"elementType": "File",
|
||||||
|
"minSdkVersionForDexing": 24
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#- File Locator -
|
||||||
|
listingFile=../../../apk/debug/output-metadata.json
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#- File Locator -
|
||||||
|
listingFile=../../apk/debug/output-metadata.json
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
appMetadataVersion=1.1
|
||||||
|
androidGradlePluginVersion=8.2.0
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
appMetadataVersion=1.1
|
||||||
|
androidGradlePluginVersion=9.1.1
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"artifactType": {
|
||||||
|
"type": "COMPATIBLE_SCREEN_MANIFEST",
|
||||||
|
"kind": "Directory"
|
||||||
|
},
|
||||||
|
"applicationId": "com.fluxup.app",
|
||||||
|
"variantName": "debug",
|
||||||
|
"elements": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"artifactType": {
|
||||||
|
"type": "COMPATIBLE_SCREEN_MANIFEST",
|
||||||
|
"kind": "Directory"
|
||||||
|
},
|
||||||
|
"applicationId": "com.fluxup.app",
|
||||||
|
"variantName": "debug",
|
||||||
|
"elements": []
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user