primeiro commit

This commit is contained in:
2026-04-16 10:40:04 +01:00
parent fa1accd4bb
commit 4d5a034272
85 changed files with 16129 additions and 14452 deletions

View File

@@ -2,6 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="true"
@@ -23,6 +24,7 @@
<activity android:name=".RegisterActivity" />
<activity android:name=".ForgotPasswordActivity" />
<activity android:name=".MainActivity" />
<activity android:name=".SettingsActivity" />
</application>

View File

@@ -9,14 +9,43 @@ import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.google.firebase.FirebaseApp;
import com.google.firebase.appcheck.FirebaseAppCheck;
import com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory;
import com.google.firebase.appcheck.playintegrity.PlayIntegrityAppCheckProviderFactory;
public class LoginActivity extends AppCompatActivity {
private EditText etEmail, etPassword, etUsuario;
private EditText etEmail, etPassword;
private Button btnLogin;
private TextView tvGoToRegister, tvForgotPassword;
@Override
protected void onCreate(Bundle savedInstanceState) {
// Inicializar Firebase e App Check antes de qualquer outra chamada
FirebaseApp.initializeApp(this);
FirebaseAppCheck firebaseAppCheck = FirebaseAppCheck.getInstance();
if (com.fluxup.app.BuildConfig.DEBUG) {
// Modo Desenvolvimento: Usa Debug Provider para gerar token no Logcat
firebaseAppCheck.installAppCheckProviderFactory(
DebugAppCheckProviderFactory.getInstance());
android.util.Log.d("FLUXUP_DEBUG", "App Check: Debug Provider instalado. Procure pelo token no Logcat.");
} else {
// Modo Produção: Usa Play Integrity
firebaseAppCheck.installAppCheckProviderFactory(
PlayIntegrityAppCheckProviderFactory.getInstance());
}
// Aplicar preferências de tema antes do super.onCreate
android.content.SharedPreferences prefs = getSharedPreferences("FluxupSettings", android.content.Context.MODE_PRIVATE);
boolean isDarkMode = prefs.getBoolean("darkMode", false);
if (isDarkMode) {
androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode(androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES);
} else {
androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode(androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO);
}
super.onCreate(savedInstanceState);
// Verificar se o utilizador já está logado
@@ -31,7 +60,7 @@ public class LoginActivity extends AppCompatActivity {
etEmail = findViewById(R.id.etEmail);
etPassword = findViewById(R.id.etPassword);
etUsuario = findViewById(R.id.etUsuario);
btnLogin = findViewById(R.id.btnLogin);
tvGoToRegister = findViewById(R.id.tvGoToRegister);
tvForgotPassword = findViewById(R.id.tvForgotPassword);
@@ -54,11 +83,9 @@ public class LoginActivity extends AppCompatActivity {
return;
}
// Validar utilizador se estiver visível/preenchido
String user = etUsuario.getText().toString();
if (!user.isEmpty() && !user.matches("[a-zA-ZáàâãéèêíïóôõöúçÁÀÂÃÉÈÊÍÏÓÔÕÖÚÇ ]+")) {
Toast.makeText(LoginActivity.this, "O nome de utilizador deve conter apenas letras", Toast.LENGTH_SHORT).show();
// Verificar Ligação à Internet
if (!ConnectivityUtils.isNetworkAvailable(LoginActivity.this)) {
Toast.makeText(LoginActivity.this, "Sem ligação à internet", Toast.LENGTH_LONG).show();
return;
}
@@ -73,7 +100,8 @@ public class LoginActivity extends AppCompatActivity {
@Override
public void onError(String error) {
Toast.makeText(LoginActivity.this, "Erro: " + error, Toast.LENGTH_LONG).show();
// Mostrar erro amigável ao utilizador
Toast.makeText(LoginActivity.this, error, Toast.LENGTH_LONG).show();
}
});
});

View File

@@ -9,6 +9,24 @@ public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// Aplicar preferências de tema e idioma
android.content.SharedPreferences prefs = getSharedPreferences("FluxupSettings", android.content.Context.MODE_PRIVATE);
// Tema
if (prefs.getBoolean("darkMode", false)) {
androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode(androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES);
} else {
androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode(androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO);
}
// Idioma
String lang = prefs.getString("language", "pt");
java.util.Locale locale = new java.util.Locale(lang);
java.util.Locale.setDefault(locale);
android.content.res.Configuration config = new android.content.res.Configuration();
config.setLocale(locale);
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

View File

@@ -1,9 +1,12 @@
package com.fluxup.app;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
@@ -13,7 +16,14 @@ public class ProfileFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_profile, container, false);
View view = inflater.inflate(R.layout.fragment_profile, container, false);
ImageButton btnSettings = view.findViewById(R.id.btnSettings);
btnSettings.setOnClickListener(v -> {
Intent intent = new Intent(getActivity(), SettingsActivity.class);
startActivity(intent);
});
return view;
}
}

View File

@@ -2,6 +2,7 @@ package com.fluxup.app;
import android.content.Context;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.firestore.FirebaseFirestore;
import java.util.HashMap;
@@ -13,45 +14,107 @@ public class UsuariosService {
private static final FirebaseFirestore mFirestore = FirebaseFirestore.getInstance();
public static void login(Context context, String email, String password, ServiceCallback<Usuario> callback) {
android.util.Log.d("FLUXUP_SERVICE", "A tentar iniciar sessão para: " + email);
mAuth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
android.util.Log.d("FLUXUP_SERVICE", "Auth bem-sucedido para: " + email);
FirebaseUser user = mAuth.getCurrentUser();
if (user != null) {
// Buscar detalhes no Firestore
Usuario defaultUsuario = new Usuario(user.getUid(), "", user.getEmail(), "", "");
defaultUsuario.id = user.getUid();
mFirestore.collection("users").document(user.getUid()).get()
.addOnSuccessListener(documentSnapshot -> {
android.util.Log.d("FLUXUP_SERVICE", "Dados recuperados do Firestore com sucesso.");
Usuario usuario = documentSnapshot.toObject(Usuario.class);
if (usuario == null) {
// Fallback if document not found
usuario = new Usuario(user.getUid(), "", user.getEmail(), "", "");
if (usuario != null) {
callback.onSuccess(usuario);
} else {
android.util.Log.w("FLUXUP_SERVICE", "Documento do utilizador não encontrado no Firestore.");
callback.onSuccess(defaultUsuario);
}
callback.onSuccess(usuario);
})
.addOnFailureListener(e -> callback.onError(e.getMessage()));
.addOnFailureListener(e -> {
android.util.Log.e("FLUXUP_SERVICE", "Erro ao carregar do Firestore: " + e.getMessage());
callback.onSuccess(defaultUsuario);
});
}
} else {
callback.onError(task.getException() != null ? task.getException().getMessage() : "Erro ao entrar");
android.util.Log.e("FLUXUP_SERVICE", "Falha no Auth: " + (task.getException() != null ? task.getException().getMessage() : "Desconhecido"));
String errorMsg = "Erro ao entrar";
if (task.getException() instanceof FirebaseAuthException) {
String errorCode = ((FirebaseAuthException) task.getException()).getErrorCode();
switch (errorCode) {
case "ERROR_INVALID_EMAIL":
case "ERROR_USER_NOT_FOUND":
errorMsg = "Utilizador não encontrado";
break;
case "ERROR_WRONG_PASSWORD":
errorMsg = "Palavra-passe incorreta";
break;
case "ERROR_USER_DISABLED":
errorMsg = "Este utilizador foi desativado";
break;
case "ERROR_TOO_MANY_REQUESTS":
errorMsg = "Demasiadas tentativas. Tente mais tarde";
break;
case "ERROR_INVALID_CREDENTIAL":
errorMsg = "Credenciais inválidas ou expiradas";
break;
default:
errorMsg = "Erro na autenticação: " + task.getException().getLocalizedMessage();
break;
}
} else if (task.getException() != null) {
errorMsg = task.getException().getLocalizedMessage();
}
callback.onError(errorMsg);
}
});
}
public static void registar(Context context, Usuario usuario, ServiceCallback<Usuario> callback) {
android.util.Log.d("FLUXUP_SERVICE", "A tentar registar utilizador: " + usuario.email);
mAuth.createUserWithEmailAndPassword(usuario.email, usuario.palavra_passe)
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
android.util.Log.d("FLUXUP_SERVICE", "Utilizador criado no Auth com sucesso: " + usuario.email);
FirebaseUser firebaseUser = mAuth.getCurrentUser();
if (firebaseUser != null) {
usuario.id_usuario = firebaseUser.getUid();
// Não salvar a senha no Firestore por segurança
String uid = firebaseUser.getUid();
usuario.id_usuario = uid;
usuario.id = uid;
String tempPass = usuario.palavra_passe;
usuario.palavra_passe = null;
mFirestore.collection("users").document(firebaseUser.getUid()).set(usuario)
.addOnSuccessListener(aVoid -> callback.onSuccess(usuario))
.addOnFailureListener(e -> callback.onError(e.getMessage()));
android.util.Log.d("FLUXUP_SERVICE", "A guardar utilizador no Firestore (Coleção: users, ID: " + uid + ")...");
mFirestore.collection("users").document(uid).set(usuario)
.addOnSuccessListener(aVoid -> {
android.util.Log.d("FLUXUP_SERVICE", "Utilizador guardado no Firestore com sucesso.");
usuario.palavra_passe = tempPass;
callback.onSuccess(usuario);
})
.addOnFailureListener(e -> {
android.util.Log.e("FLUXUP_SERVICE", "ERRO NO FIRESTORE: " + e.getMessage());
callback.onError("Erro ao guardar na base de dados: " + e.getMessage());
});
}
} else {
callback.onError(task.getException() != null ? task.getException().getMessage() : "Erro ao criar conta");
android.util.Log.e("FLUXUP_SERVICE", "Falha ao criar conta no Auth: " + (task.getException() != null ? task.getException().getMessage() : "Desconhecido"));
String errorMsg = "Erro ao criar conta";
if (task.getException() instanceof FirebaseAuthException) {
String errorCode = ((FirebaseAuthException) task.getException()).getErrorCode();
if (errorCode.equals("ERROR_EMAIL_ALREADY_IN_USE")) {
errorMsg = "Este email já está a ser utilizado";
} else {
errorMsg = task.getException().getLocalizedMessage();
}
} else if (task.getException() != null) {
errorMsg = task.getException().getLocalizedMessage();
}
callback.onError(errorMsg);
}
});
}

View File

@@ -45,14 +45,6 @@
android:layout_marginBottom="15dp"
android:inputType="textPassword"/>
<EditText
android:id="@+id/etUsuario"
android:layout_width="match_parent"
android:layout_height="50dp"
android:hint="Usuário"
android:background="@drawable/edit_text_bg"
android:paddingHorizontal="15dp"
android:layout_marginBottom="20dp"/>
<Button
android:id="@+id/btnLogin"

View File

@@ -18,13 +18,15 @@
android:paddingTop="20dp"
android:paddingBottom="20dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
<ImageButton
android:id="@+id/btnSettings"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="top|end"
android:layout_marginEnd="16dp"
android:layout_marginTop="8dp"
android:src="@android:drawable/ic_menu_manage"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_settings"
app:tint="@color/white" />
<LinearLayout

View File

@@ -2,4 +2,21 @@
<string name="app_name">Fluxup</string>
<string name="greeting">Hoje é um bom dia</string>
<string name="subtitle">Olá, Matheus! Preparado para o seu dia de produtividade?</string>
<!-- Settings Strings -->
<string name="settings">Configurações</string>
<string name="preferences">Preferências</string>
<string name="dark_mode">Modo Escuro</string>
<string name="account">Conta</string>
<string name="username">Utilizador</string>
<string name="email">Email</string>
<string name="change_password">Alterar palavra-passe</string>
<string name="privacy">Privacidade</string>
<string name="private_account">Conta Privada</string>
<string name="notifications">Notificações</string>
<string name="app_section">App</string>
<string name="language">Idioma</string>
<string name="logout">Terminar sessão</string>
<string name="back">Voltar</string>
</resources>