primeiro commit

This commit is contained in:
2026-04-15 12:46:50 +01:00
commit fa1accd4bb
961 changed files with 124535 additions and 0 deletions

14
app/(tabs)/_layout.tsx Normal file
View 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>
);
}

View 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 palavrapasse</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
View 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 é bemsucedido, 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 palavrapasse</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
View 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
View 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
View 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}> 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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

Binary file not shown.

View File

@@ -0,0 +1,2 @@
#Tue Mar 17 15:42:21 WET 2026
gradle.version=8.9

View File

58
app/build.gradle Normal file
View 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

Binary file not shown.

View File

@@ -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
View File

@@ -0,0 +1 @@
1:659566714595:android:e2dba99e1738528d966fd6

View File

@@ -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

Binary file not shown.

View 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
}

View File

@@ -0,0 +1,2 @@
#- File Locator -
listingFile=../../../apk/debug/output-metadata.json

View File

@@ -0,0 +1,2 @@
#- File Locator -
listingFile=../../apk/debug/output-metadata.json

View File

@@ -0,0 +1,2 @@
appMetadataVersion=1.1
androidGradlePluginVersion=8.2.0

View File

@@ -0,0 +1,2 @@
appMetadataVersion=1.1
androidGradlePluginVersion=9.1.1

View File

@@ -0,0 +1,10 @@
{
"version": 3,
"artifactType": {
"type": "COMPATIBLE_SCREEN_MANIFEST",
"kind": "Directory"
},
"applicationId": "com.fluxup.app",
"variantName": "debug",
"elements": []
}

View File

@@ -0,0 +1,10 @@
{
"version": 3,
"artifactType": {
"type": "COMPATIBLE_SCREEN_MANIFEST",
"kind": "Directory"
},
"applicationId": "com.fluxup.app",
"variantName": "debug",
"elements": []
}

View File

@@ -0,0 +1 @@
8

View File

@@ -0,0 +1 @@
[{"key":"androidx/arch/core/executor/ArchTaskExecutor.class","name":"androidx/arch/core/executor/ArchTaskExecutor.class","size":2950,"crc":-173664277},{"key":"androidx/arch/core/executor/DefaultTaskExecutor$1.class","name":"androidx/arch/core/executor/DefaultTaskExecutor$1.class","size":1445,"crc":-1550744660},{"key":"androidx/arch/core/executor/DefaultTaskExecutor$Api28Impl.class","name":"androidx/arch/core/executor/DefaultTaskExecutor$Api28Impl.class","size":834,"crc":1774766260},{"key":"androidx/arch/core/executor/DefaultTaskExecutor.class","name":"androidx/arch/core/executor/DefaultTaskExecutor.class","size":3293,"crc":-1256626107},{"key":"androidx/arch/core/executor/TaskExecutor.class","name":"androidx/arch/core/executor/TaskExecutor.class","size":1053,"crc":1399542030},{"key":"META-INF/androidx.arch.core_core-runtime.version","name":"META-INF/androidx.arch.core_core-runtime.version","size":67,"crc":1307063212}]

Some files were not shown because too many files have changed in this diff Show More