adicionar loja

This commit is contained in:
MeuNome
2026-05-27 12:43:42 +01:00
parent d357a9b220
commit 9c9fae0e32
13 changed files with 332 additions and 68 deletions

View File

@@ -922,6 +922,18 @@
<option name="screenX" value="2208" /> <option name="screenX" value="2208" />
<option name="screenY" value="1840" /> <option name="screenY" value="1840" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
<option name="brand" value="google" />
<option name="codename" value="felix" />
<option name="id" value="felix" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Fold" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2208" />
<option name="screenY" value="1840" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="33" /> <option name="api" value="33" />
<option name="brand" value="google" /> <option name="brand" value="google" />
@@ -1476,6 +1488,18 @@
<option name="screenX" value="1440" /> <option name="screenX" value="1440" />
<option name="screenY" value="3120" /> <option name="screenY" value="3120" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
<option name="brand" value="samsung" />
<option name="codename" value="q4qksx" />
<option name="id" value="q4qksx" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Z Fold4" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1812" />
<option name="screenY" value="2176" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="34" /> <option name="api" value="34" />
<option name="brand" value="samsung" /> <option name="brand" value="samsung" />

View File

@@ -188,7 +188,7 @@ public class FindFriendsActivity extends AppCompatActivity {
resultsContainer.removeAllViews(); resultsContainer.removeAllViews();
for (DocumentSnapshot doc : snapshots.getDocuments()) { for (DocumentSnapshot doc : snapshots.getDocuments()) {
Usuario u = doc.toObject(Usuario.class); Usuario u = doc.toObject(Usuario.class);
if (u != null && !excludedIds.contains(u.id_usuario)) { if (u != null && u.public_profile && !u.incognito && !excludedIds.contains(u.id_usuario)) {
addDiscoverItem(u); addDiscoverItem(u);
} }
} }
@@ -204,7 +204,7 @@ public class FindFriendsActivity extends AppCompatActivity {
resultsContainer.removeAllViews(); resultsContainer.removeAllViews();
for (DocumentSnapshot doc : snapshots.getDocuments()) { for (DocumentSnapshot doc : snapshots.getDocuments()) {
Usuario u = doc.toObject(Usuario.class); Usuario u = doc.toObject(Usuario.class);
if (u != null && !excludedIds.contains(u.id_usuario)) { if (u != null && u.public_profile && !u.incognito && !excludedIds.contains(u.id_usuario)) {
addDiscoverItem(u); addDiscoverItem(u);
} }
} }

View File

@@ -145,6 +145,28 @@ public class FirestoreManager {
addXpLog(uid, amount, type, null); addXpLog(uid, amount, type, null);
} }
/**
* Adiciona moedas ao utilizador e regista a transação.
*/
public void addCoins(String uid, int amount, String reason) {
if (uid == null) return;
// Atualizar saldo
java.util.Map<String, Object> updates = new java.util.HashMap<>();
updates.put("coins", com.google.firebase.firestore.FieldValue.increment(amount));
updateUserStats(uid, updates);
// Registar transação
java.util.Map<String, Object> log = new java.util.HashMap<>();
log.put("userId", uid);
log.put("amount", amount);
log.put("reason", reason);
log.put("created_at", com.google.firebase.firestore.FieldValue.serverTimestamp());
db.collection("coin_logs").add(log)
.addOnSuccessListener(ref -> android.util.Log.d("FLUXUP_DEBUG", "COIN_LOG_INSERT_SUCCESS"))
.addOnFailureListener(e -> android.util.Log.e("FLUXUP_DEBUG", "COIN_LOG_INSERT_FAIL: " + e.getMessage()));
}
/** /**
* Calcula o XP total ganho hoje a partir dos logs. * Calcula o XP total ganho hoje a partir dos logs.
*/ */

View File

@@ -22,10 +22,14 @@ public class FluxupApplication extends Application {
// Aplicar preferências de tema // Aplicar preferências de tema
SharedPreferences prefs = getSharedPreferences("FluxupSettings", Context.MODE_PRIVATE); SharedPreferences prefs = getSharedPreferences("FluxupSettings", Context.MODE_PRIVATE);
if (prefs.getBoolean("darkMode", false)) { boolean darkMode = prefs.getBoolean("dark_mode_enabled", false);
Log.d(TAG, "APP_THEME_IS_DAYNIGHT | dark_mode_enabled=" + darkMode);
if (darkMode) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
Log.d(TAG, "DARK_MODE_APPLIED | mode=MODE_NIGHT_YES");
} else { } else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
Log.d(TAG, "DARK_MODE_APPLIED | mode=MODE_NIGHT_NO");
} }
// Inicializar Firebase // Inicializar Firebase

View File

@@ -385,6 +385,7 @@ public class InicioFragment extends Fragment {
if (user.xp + amount >= threshold) { if (user.xp + amount >= threshold) {
updates.put("level", currentLevel + 1); updates.put("level", currentLevel + 1);
showLevelUpAnimation(currentLevel + 1); showLevelUpAnimation(currentLevel + 1);
FirestoreManager.getInstance().addCoins(uid, 50, "level_up");
} }
FirestoreManager.getInstance().updateUserStats(uid, updates); FirestoreManager.getInstance().updateUserStats(uid, updates);
refreshTodayStats(); refreshTodayStats();
@@ -527,8 +528,9 @@ public class InicioFragment extends Fragment {
// Login reward // Login reward
addXP(10); addXP(10);
FirestoreManager.getInstance().addCoins(user.id_usuario, 10, "daily_login");
if (getContext() != null) { if (getContext() != null) {
Toast.makeText(getContext(), "Recompensa de login: +10 XP!", Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), "Recompensa de login: +10 XP e +10 moedas!", Toast.LENGTH_SHORT).show();
} }
} }
} }
@@ -537,14 +539,15 @@ public class InicioFragment extends Fragment {
rankingListener = com.google.firebase.firestore.FirebaseFirestore.getInstance() rankingListener = com.google.firebase.firestore.FirebaseFirestore.getInstance()
.collection("users") .collection("users")
.orderBy("xp_hoje", com.google.firebase.firestore.Query.Direction.DESCENDING) .orderBy("xp_hoje", com.google.firebase.firestore.Query.Direction.DESCENDING)
.limit(3) .limit(10)
.addSnapshotListener((snapshots, e) -> { .addSnapshotListener((snapshots, e) -> {
if (e != null || snapshots == null) return; if (e != null || snapshots == null) return;
miniRankingContainer.removeAllViews(); miniRankingContainer.removeAllViews();
int rank = 1; int rank = 1;
for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) { for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) {
if (rank > 3) break;
Usuario u = doc.toObject(Usuario.class); Usuario u = doc.toObject(Usuario.class);
if (u != null) { if (u != null && u.public_profile && !u.incognito) {
addMiniRankingItem(rank++, u); addMiniRankingItem(rank++, u);
} }
} }
@@ -743,6 +746,7 @@ public class InicioFragment extends Fragment {
// Adicionar log de XP mesmo em manual para permitir recálculo se necessário // Adicionar log de XP mesmo em manual para permitir recálculo se necessário
FirestoreManager.getInstance().addXpLog(uid, 0, "task_manual_completion"); FirestoreManager.getInstance().addXpLog(uid, 0, "task_manual_completion");
FirestoreManager.getInstance().addCoins(uid, 3, "task_completion");
FirestoreManager.getInstance().updateUserStats(uid, updates, () -> { FirestoreManager.getInstance().updateUserStats(uid, updates, () -> {
if (isAdded()) refreshTodayStats(); if (isAdded()) refreshTodayStats();
@@ -1064,6 +1068,7 @@ public class InicioFragment extends Fragment {
android.util.Log.d("FLUXUP_DEBUG", "XP_LOG_INSERT_START"); android.util.Log.d("FLUXUP_DEBUG", "XP_LOG_INSERT_START");
FirestoreManager.getInstance().addXpLog(userId, finalXp, "focus_task", taskId); FirestoreManager.getInstance().addXpLog(userId, finalXp, "focus_task", taskId);
FirestoreManager.getInstance().addCoins(userId, 5, "focus_session");
FirestoreManager.getInstance().getUser(userId, user -> { FirestoreManager.getInstance().getUser(userId, user -> {
if (user == null) { if (user == null) {
@@ -1087,6 +1092,7 @@ public class InicioFragment extends Fragment {
if (user.xp + finalXp >= threshold) { if (user.xp + finalXp >= threshold) {
updates.put("level", currentLevel + 1); updates.put("level", currentLevel + 1);
showLevelUpAnimation(currentLevel + 1); showLevelUpAnimation(currentLevel + 1);
FirestoreManager.getInstance().addCoins(userId, 50, "level_up");
} }
FirestoreManager.getInstance().updateUserStats(userId, updates, () -> { FirestoreManager.getInstance().updateUserStats(userId, updates, () -> {

View File

@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
android.content.SharedPreferences prefs = getSharedPreferences("FluxupSettings", android.content.Context.MODE_PRIVATE); android.content.SharedPreferences prefs = getSharedPreferences("FluxupSettings", android.content.Context.MODE_PRIVATE);
// Tema // Tema
if (prefs.getBoolean("darkMode", false)) { if (prefs.getBoolean("dark_mode_enabled", false)) {
androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode(androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES); androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode(androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES);
} else { } else {
androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode(androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO); androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode(androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO);

View File

@@ -159,7 +159,7 @@ public class SearchFragment extends Fragment {
List<Usuario> users = new ArrayList<>(); List<Usuario> users = new ArrayList<>();
for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots) { for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots) {
Usuario u = doc.toObject(Usuario.class); Usuario u = doc.toObject(Usuario.class);
if (u != null) users.add(u); if (u != null && u.public_profile && !u.incognito) users.add(u);
} }
userAdapter.updateList(users); userAdapter.updateList(users);
}); });

View File

@@ -6,6 +6,7 @@ import android.content.SharedPreferences;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
@@ -22,13 +23,22 @@ import com.google.firebase.auth.FirebaseAuth;
public class SettingsActivity extends AppCompatActivity { public class SettingsActivity extends AppCompatActivity {
private static final String TAG = "DARK_MODE";
private SharedPreferences sharedPreferences; private SharedPreferences sharedPreferences;
private static final String PREFS_NAME = "FluxupSettings"; private static final String PREFS_NAME = "FluxupSettings";
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sharedPreferences = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); sharedPreferences = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
// Aplicar o modo escuro antes de criar a activity
boolean isDarkMode = sharedPreferences.getBoolean("dark_mode_enabled", false);
Log.d(TAG, "APP_THEME_IS_DAYNIGHT | dark_mode_enabled=" + isDarkMode);
AppCompatDelegate.setDefaultNightMode(isDarkMode ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO);
Log.d(TAG, "DARK_MODE_APPLIED | mode=" + (isDarkMode ? "MODE_NIGHT_YES" : "MODE_NIGHT_NO"));
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings); setContentView(R.layout.activity_settings);
initSettings(); initSettings();
@@ -40,13 +50,33 @@ public class SettingsActivity extends AppCompatActivity {
setupClickable(R.id.settingEditProfile, "Editar Perfil", "Nome, Bio, Avatar", v -> showEditProfileDialog()); setupClickable(R.id.settingEditProfile, "Editar Perfil", "Nome, Bio, Avatar", v -> showEditProfileDialog());
setupClickable(R.id.settingEmail, "Email", FirebaseAuth.getInstance().getCurrentUser().getEmail(), v -> showChangeEmailDialog()); setupClickable(R.id.settingEmail, "Email", FirebaseAuth.getInstance().getCurrentUser().getEmail(), v -> showChangeEmailDialog());
setupClickable(R.id.settingPassword, "Alterar Palavra-passe", "********", v -> resetPassword()); setupClickable(R.id.settingPassword, "Alterar Palavra-passe", "********", v -> resetPassword());
setupSwitch(R.id.settingPublicProfile, "Perfil Público", "public_profile", true); setupSwitch(R.id.settingPublicProfile, "Perfil Público", "public_profile", true, (v, isChecked) -> {
String uid = FirebaseAuth.getInstance().getUid();
if (uid != null) {
java.util.Map<String, Object> updates = new java.util.HashMap<>();
updates.put("public_profile", isChecked);
FirestoreManager.getInstance().updateUserStats(uid, updates);
}
});
// --- APARÊNCIA --- // --- APARÊNCIA ---
setupSwitch(R.id.settingDarkMode, "Modo Escuro", "darkMode", false, (v, isChecked) -> { setupSwitch(R.id.settingDarkMode, "Modo Escuro", "dark_mode_enabled", false, (v, isChecked) -> {
AppCompatDelegate.setDefaultNightMode(isChecked ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO); Log.d(TAG, "DARK_MODE_SWITCH_CHANGED | isChecked=" + isChecked);
// Guardar valor (feito pelo setupSwitch genérico, mas confirmamos aqui)
sharedPreferences.edit().putBoolean("dark_mode_enabled", isChecked).apply();
boolean saved = sharedPreferences.getBoolean("dark_mode_enabled", false);
Log.d(TAG, "DARK_MODE_SAVED_VALUE | saved=" + saved);
// Aplicar o modo globalmente
AppCompatDelegate.setDefaultNightMode(
isChecked ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO
);
Log.d(TAG, "DARK_MODE_APPLIED | mode=" + (isChecked ? "MODE_NIGHT_YES" : "MODE_NIGHT_NO"));
// Recriar a Activity para aplicar o novo tema imediatamente
recreate();
}); });
setupClickable(R.id.settingThemeColor, "Cor do Tema", "Roxo (Padrão)", null);
String themeColorValue = sharedPreferences.getString("theme_color", "purple");
setupClickable(R.id.settingThemeColor, "Cor Principal da App", getColorLabel(themeColorValue), v -> showThemeColorDialog());
// --- NOTIFICAÇÕES --- // --- NOTIFICAÇÕES ---
setupSwitch(R.id.settingDailyReminders, "Lembretes Diários", "daily_reminders", true); setupSwitch(R.id.settingDailyReminders, "Lembretes Diários", "daily_reminders", true);
@@ -58,17 +88,166 @@ public class SettingsActivity extends AppCompatActivity {
setupSwitch(R.id.settingVibration, "Vibração ao Terminar", "vibration_on_finish", true); setupSwitch(R.id.settingVibration, "Vibração ao Terminar", "vibration_on_finish", true);
// --- PRIVACIDADE --- // --- PRIVACIDADE ---
setupSwitch(R.id.settingIncognito, "Modo Incógnito", "incognito", false); setupSwitch(R.id.settingIncognito, "Modo Incógnito", "incognito", false, (v, isChecked) -> {
setupClickable(R.id.settingExportData, "Exportar Dados", "JSON/CSV", null); String uid = FirebaseAuth.getInstance().getUid();
if (uid != null) {
java.util.Map<String, Object> updates = new java.util.HashMap<>();
updates.put("incognito", isChecked);
FirestoreManager.getInstance().updateUserStats(uid, updates);
}
});
setupClickable(R.id.settingExportData, "Exportar Dados", "JSON/CSV", v -> exportUserData());
findViewById(R.id.btnDeleteAccount).setOnClickListener(v -> { findViewById(R.id.btnDeleteAccount).setOnClickListener(v -> {
new AlertDialog.Builder(this) new AlertDialog.Builder(this)
.setTitle("Eliminar Conta") .setTitle("Eliminar Conta")
.setMessage("Tens a certeza? Todos os teus dados serão apagados permanentemente.") .setMessage("Tens a certeza? Todos os teus dados serão apagados permanentemente.")
.setNegativeButton("Cancelar", null) .setNegativeButton("Cancelar", null)
.setPositiveButton("Sim, Eliminar", (dialog, which) -> deleteAccount()) .setPositiveButton("Sim, Eliminar", (dialog, which) -> {
new AlertDialog.Builder(this)
.setTitle("Confirmação Final")
.setMessage("Esta ação é irreversível e apagará TODO o teu histórico e progresso. Tens a certeza absoluta?")
.setNegativeButton("Cancelar", null)
.setPositiveButton("Eliminar Definitivamente", (dialog2, which2) -> deleteAccount())
.show();
})
.show(); .show();
}); });
// Sync initial values from Firestore
String uid = FirebaseAuth.getInstance().getUid();
if (uid != null) {
FirestoreManager.getInstance().getUser(uid, user -> {
if (user != null) {
sharedPreferences.edit()
.putBoolean("public_profile", user.public_profile)
.putBoolean("incognito", user.incognito)
.putInt("focus_duration", user.meta_diaria_foco)
.apply();
updateClickableValue(R.id.settingFocusDuration, user.meta_diaria_foco + " min");
SwitchMaterial swPublic = findViewById(R.id.settingPublicProfile).findViewById(R.id.switchSetting);
if (swPublic != null) swPublic.setChecked(user.public_profile);
SwitchMaterial swIncognito = findViewById(R.id.settingIncognito).findViewById(R.id.switchSetting);
if (swIncognito != null) swIncognito.setChecked(user.incognito);
}
});
}
}
private void updateClickableValue(int id, String value) {
View view = findViewById(id);
if (view != null) {
TextView tvValue = view.findViewById(R.id.tvSettingValue);
if (tvValue != null) tvValue.setText(value);
}
}
private String getColorLabel(String key) {
switch (key) {
case "blue": return "Azul";
case "green": return "Verde";
case "orange": return "Laranja";
case "red": return "Vermelho";
case "purple":
default:
return "Roxo (Padrão)";
}
}
private void showThemeColorDialog() {
String[] colors = {"Roxo (Padrão)", "Azul", "Verde", "Laranja", "Vermelho"};
String[] keys = {"purple", "blue", "green", "orange", "red"};
String currentColor = sharedPreferences.getString("theme_color", "purple");
int checkedItem = 0;
for (int i = 0; i < keys.length; i++) {
if (keys[i].equals(currentColor)) {
checkedItem = i;
break;
}
}
new AlertDialog.Builder(this)
.setTitle("Escolher Cor Principal")
.setSingleChoiceItems(colors, checkedItem, (dialog, which) -> {
String selectedKey = keys[which];
sharedPreferences.edit().putString("theme_color", selectedKey).apply();
updateClickableValue(R.id.settingThemeColor, colors[which]);
Toast.makeText(this, "Cor do tema atualizada!", Toast.LENGTH_SHORT).show();
dialog.dismiss();
})
.setNegativeButton("Cancelar", null)
.show();
}
private void exportUserData() {
String uid = FirebaseAuth.getInstance().getUid();
if (uid == null) {
Toast.makeText(this, "Utilizador não autenticado", Toast.LENGTH_SHORT).show();
return;
}
Toast.makeText(this, "A exportar dados...", Toast.LENGTH_SHORT).show();
FirestoreManager.getInstance().getUser(uid, user -> {
if (user == null) {
Toast.makeText(this, "Erro ao obter dados do utilizador", Toast.LENGTH_SHORT).show();
return;
}
com.google.firebase.firestore.FirebaseFirestore.getInstance()
.collection("tasks")
.whereEqualTo("userId", uid)
.get()
.addOnSuccessListener(queryDocumentSnapshots -> {
try {
org.json.JSONObject exportObj = new org.json.JSONObject();
org.json.JSONObject profileObj = new org.json.JSONObject();
profileObj.put("usuario", user.usuario);
profileObj.put("email", user.email);
profileObj.put("level", user.level);
profileObj.put("xp", user.xp);
profileObj.put("streak", user.streak);
profileObj.put("melhor_streak", user.melhor_streak);
profileObj.put("total_tasks_concluidas", user.total_tasks_concluidas);
profileObj.put("tempo_foco_total_minutos", user.tempo_foco_total);
exportObj.put("perfil", profileObj);
org.json.JSONArray tasksArray = new org.json.JSONArray();
for (com.google.firebase.firestore.DocumentSnapshot doc : queryDocumentSnapshots.getDocuments()) {
org.json.JSONObject taskObj = new org.json.JSONObject();
taskObj.put("id", doc.getId());
taskObj.put("title", doc.getString("title"));
taskObj.put("duration", doc.getLong("duration"));
taskObj.put("completed", doc.getBoolean("completed"));
Long completedDate = doc.getLong("completedDate");
taskObj.put("completedDate", completedDate != null ? completedDate : 0);
tasksArray.put(taskObj);
}
exportObj.put("tarefas", tasksArray);
String jsonString = exportObj.toString(4);
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, jsonString);
sendIntent.setType("text/plain");
Intent shareIntent = Intent.createChooser(sendIntent, "Exportar Dados Fluxup");
startActivity(shareIntent);
} catch (Exception e) {
Toast.makeText(SettingsActivity.this, "Erro ao exportar: " + e.getMessage(), Toast.LENGTH_LONG).show();
}
})
.addOnFailureListener(e -> {
Toast.makeText(SettingsActivity.this, "Erro ao obter tarefas: " + e.getMessage(), Toast.LENGTH_LONG).show();
});
});
} }
private void setupClickable(int id, String title, String value, View.OnClickListener listener) { private void setupClickable(int id, String title, String value, View.OnClickListener listener) {
@@ -149,29 +328,48 @@ public class SettingsActivity extends AppCompatActivity {
private void deleteAccount() { private void deleteAccount() {
String uid = FirebaseAuth.getInstance().getUid(); String uid = FirebaseAuth.getInstance().getUid();
com.google.firebase.firestore.FirebaseFirestore.getInstance().collection("users").document(uid).delete(); com.google.firebase.firestore.FirebaseFirestore.getInstance().collection("users").document(uid).delete()
FirebaseAuth.getInstance().getCurrentUser().delete()
.addOnSuccessListener(aVoid -> { .addOnSuccessListener(aVoid -> {
Intent intent = new Intent(this, LoginActivity.class); FirebaseAuth.getInstance().getCurrentUser().delete()
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); .addOnSuccessListener(aVoid2 -> {
startActivity(intent); Intent intent = new Intent(this, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
})
.addOnFailureListener(e -> {
Toast.makeText(this, "Erro ao eliminar autenticação: " + e.getMessage(), Toast.LENGTH_LONG).show();
});
})
.addOnFailureListener(e -> {
Toast.makeText(this, "Erro ao eliminar dados da conta: " + e.getMessage(), Toast.LENGTH_LONG).show();
}); });
} }
private void showDurationDialog(String key, String title, int current) { private void showDurationDialog(String key, String title, int current) {
EditText input = new EditText(this); View dialogView = getLayoutInflater().inflate(R.layout.dialog_duration_picker, null);
input.setInputType(android.text.InputType.TYPE_CLASS_NUMBER); TextView tvTitle = dialogView.findViewById(R.id.tvDurationTitle);
input.setText(String.valueOf(sharedPreferences.getInt(key, current))); tvTitle.setText(title);
com.google.android.material.textfield.TextInputLayout til = dialogView.findViewById(R.id.tilDuration);
til.setHint("Minutos");
com.google.android.material.textfield.TextInputEditText etDuration = dialogView.findViewById(R.id.etDuration);
etDuration.setText(String.valueOf(sharedPreferences.getInt(key, current)));
new AlertDialog.Builder(this) new AlertDialog.Builder(this)
.setTitle(title) .setView(dialogView)
.setView(input)
.setPositiveButton("Guardar", (dialog, which) -> { .setPositiveButton("Guardar", (dialog, which) -> {
try { try {
int val = Integer.parseInt(input.getText().toString()); String valStr = etDuration.getText().toString();
if (valStr.isEmpty()) return;
int val = Integer.parseInt(valStr);
if (val <= 0) {
Toast.makeText(this, "A duração deve ser superior a zero", Toast.LENGTH_SHORT).show();
return;
}
sharedPreferences.edit().putInt(key, val).apply(); sharedPreferences.edit().putInt(key, val).apply();
updateClickableValue(key.equals("focus_duration") ? R.id.settingFocusDuration : R.id.settingBreakDuration, val + " min");
// Also update in Firestore if user is logged in
String uid = FirebaseAuth.getInstance().getUid(); String uid = FirebaseAuth.getInstance().getUid();
if (uid != null) { if (uid != null) {
java.util.Map<String, Object> updates = new java.util.HashMap<>(); java.util.Map<String, Object> updates = new java.util.HashMap<>();
@@ -179,7 +377,7 @@ public class SettingsActivity extends AppCompatActivity {
FirestoreManager.getInstance().updateUserStats(uid, updates); FirestoreManager.getInstance().updateUserStats(uid, updates);
} }
recreate(); Toast.makeText(this, "Duração atualizada!", Toast.LENGTH_SHORT).show();
} catch (Exception e) {} } catch (Exception e) {}
}) })
.setNegativeButton("Cancelar", null) .setNegativeButton("Cancelar", null)

View File

@@ -39,6 +39,8 @@ public class Usuario {
public String avatar_url = ""; public String avatar_url = "";
public AvatarData avatar = new AvatarData(); // Avatar gamificado public AvatarData avatar = new AvatarData(); // Avatar gamificado
public java.util.List<String> dias_concluidos = new java.util.ArrayList<>(); public java.util.List<String> dias_concluidos = new java.util.ArrayList<>();
public boolean public_profile = true;
public boolean incognito = false;
// Gamification & Rewards // Gamification & Rewards
public int login_streak = 0; public int login_streak = 0;
@@ -48,6 +50,8 @@ public class Usuario {
public java.util.List<String> claimed_streak_rewards = new java.util.ArrayList<>(); public java.util.List<String> claimed_streak_rewards = new java.util.ArrayList<>();
public String last_box_open_date = ""; // YYYY-MM-DD public String last_box_open_date = ""; // YYYY-MM-DD
public int coins = 0; public int coins = 0;
public java.util.List<String> unlockedItems = new java.util.ArrayList<>();
public java.util.Map<String, Integer> inventory = new java.util.HashMap<>();
public Usuario() {} public Usuario() {}

View File

@@ -65,7 +65,7 @@
<androidx.cardview.widget.CardView style="@style/SettingsCard"> <androidx.cardview.widget.CardView style="@style/SettingsCard">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical">
<include layout="@layout/item_settings_switch" android:id="@+id/settingDarkMode" /> <include layout="@layout/item_settings_switch" android:id="@+id/settingDarkMode" />
<include layout="@layout/item_settings_clickable" android:id="@+id/settingThemeColor" /> <include layout="@layout/item_settings_clickable" android:id="@+id/settingThemeColor" android:visibility="gone" />
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
@@ -102,21 +102,22 @@
<androidx.cardview.widget.CardView style="@style/SettingsCard"> <androidx.cardview.widget.CardView style="@style/SettingsCard">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical">
<include layout="@layout/item_settings_switch" android:id="@+id/settingIncognito" /> <include layout="@layout/item_settings_switch" android:id="@+id/settingIncognito" />
<include layout="@layout/item_settings_clickable" android:id="@+id/settingExportData" /> <include layout="@layout/item_settings_clickable" android:id="@+id/settingExportData" android:visibility="gone" />
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<!-- Danger Zone --> <!-- Danger Zone -->
<Button <com.google.android.material.button.MaterialButton
android:id="@+id/btnDeleteAccount" android:id="@+id/btnDeleteAccount"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="56dp" android:layout_height="56dp"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:text="Eliminar Conta" android:text="Eliminar Conta"
android:textColor="@color/error_red" android:textColor="@color/white"
android:backgroundTint="#10EF4444" app:backgroundTint="@color/error_red"
style="@style/Widget.MaterialComponents.Button.TextButton" app:cornerRadius="12dp"
android:layout_marginBottom="40dp"/> android:layout_marginBottom="40dp"
android:textStyle="bold" />
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View File

@@ -1,17 +1,24 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="64dp" android:layout_height="64dp"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:paddingHorizontal="16dp"> android:paddingHorizontal="24dp"
android:paddingVertical="16dp">
<TextView <TextView
android:id="@+id/tvSettingTitle" android:id="@+id/tvSettingTitle"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerVertical="true" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/tvSettingValue"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:ellipsize="end"
android:singleLine="true"
android:text="Configuração" android:text="Configuração"
android:textColor="@color/text_primary" android:textColor="@color/text_primary"
android:textSize="16sp" /> android:textSize="16sp" />
@@ -20,9 +27,11 @@
android:id="@+id/tvSettingValue" android:id="@+id/tvSettingValue"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerVertical="true" app:layout_constraintEnd_toStartOf="@id/ivChevron"
android:layout_toStartOf="@id/ivChevron" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
app:layout_goneMarginEnd="0dp"
android:text="Valor" android:text="Valor"
android:textColor="@color/text_secondary" android:textColor="@color/text_secondary"
android:textSize="14sp" /> android:textSize="14sp" />
@@ -31,16 +40,11 @@
android:id="@+id/ivChevron" android:id="@+id/ivChevron"
android:layout_width="20dp" android:layout_width="20dp"
android:layout_height="20dp" android:layout_height="20dp"
android:layout_alignParentEnd="true" app:layout_constraintEnd_toEndOf="parent"
android:layout_centerVertical="true" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:src="@drawable/ic_back" android:src="@drawable/ic_back"
android:rotation="180" android:rotation="180"
android:alpha="0.3" /> android:alpha="0.3" />
<View </androidx.constraintlayout.widget.ConstraintLayout>
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_alignParentBottom="true"
android:background="@color/border_color"
android:layout_marginStart="16dp" />
</RelativeLayout>

View File

@@ -1,15 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="64dp" android:layout_height="64dp"
android:paddingHorizontal="16dp"> android:paddingHorizontal="24dp"
android:paddingVertical="16dp">
<TextView <TextView
android:id="@+id/tvSettingTitle" android:id="@+id/tvSettingTitle"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerVertical="true" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/switchSetting"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:ellipsize="end"
android:singleLine="true"
android:text="Configuração" android:text="Configuração"
android:textColor="@color/text_primary" android:textColor="@color/text_primary"
android:textSize="16sp" /> android:textSize="16sp" />
@@ -18,14 +24,9 @@
android:id="@+id/switchSetting" android:id="@+id/switchSetting"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true" app:layout_constraintEnd_toEndOf="parent"
android:layout_centerVertical="true" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:useMaterialThemeColors="true" /> app:useMaterialThemeColors="true" />
<View </androidx.constraintlayout.widget.ConstraintLayout>
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_alignParentBottom="true"
android:background="@color/border_color"
android:layout_marginStart="16dp" />
</RelativeLayout>

View File

@@ -9,11 +9,11 @@
<item name="colorSecondary">#FACC15</item> <item name="colorSecondary">#FACC15</item>
<item name="colorSecondaryVariant">#FACC15</item> <item name="colorSecondaryVariant">#FACC15</item>
<item name="colorOnSecondary">#11181C</item> <item name="colorOnSecondary">#11181C</item>
<!-- Status bar color. --> <!-- Status bar color. Uses @color/background_light so values-night can override -->
<item name="android:statusBarColor">#F9FAFB</item> <item name="android:statusBarColor">@color/background_light</item>
<item name="android:windowLightStatusBar" tools:targetApi="M">true</item> <item name="android:windowLightStatusBar" tools:targetApi="M">true</item>
<!-- Customize your theme here. --> <!-- Window background. Uses @color/background_light so values-night can override -->
<item name="android:windowBackground">#F9FAFB</item> <item name="android:windowBackground">@color/background_light</item>
</style> </style>
</resources> </resources>