diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8b3bdae..8855d45 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ + { + android.util.Log.d("FLUXUP_DEBUG", "TASK_UPDATE_SUCCESS"); + if (onSuccess != null) onSuccess.run(); + }) + .addOnFailureListener(e -> android.util.Log.e("FLUXUP_DEBUG", "TASK_UPDATE_FAIL: " + e.getMessage())); + } + public void updateTask(Task task) { - db.collection("tasks").document(task.id).set(task); + updateTask(task, null); } /** @@ -92,19 +101,75 @@ public class FirestoreManager { /** * Atualiza campos específicos do perfil do utilizador (ex: XP, Streak). */ + public void updateUserStats(String uid, Map updates, Runnable onSuccess) { + db.collection("users").document(uid).update(updates) + .addOnSuccessListener(aVoid -> { + if (onSuccess != null) onSuccess.run(); + }) + .addOnFailureListener(e -> android.util.Log.e("FLUXUP_DEBUG", "UPDATE_USER_STATS_FAIL: " + e.getMessage())); + } + public void updateUserStats(String uid, Map updates) { - db.collection("users").document(uid).update(updates); + updateUserStats(uid, updates, null); } /** * Regista um log de XP. */ - public void addXpLog(String uid, int amount, String type) { + public void addXpLog(String uid, int amount, String type, String taskId) { Map log = new java.util.HashMap<>(); log.put("userId", uid); log.put("amount", amount); log.put("type", type); + if (taskId != null) log.put("taskId", taskId); log.put("created_at", com.google.firebase.firestore.FieldValue.serverTimestamp()); - db.collection("xp_logs").add(log); + db.collection("xp_logs").add(log) + .addOnSuccessListener(ref -> android.util.Log.d("FLUXUP_DEBUG", "XP_LOG_INSERT_SUCCESS")) + .addOnFailureListener(e -> android.util.Log.e("FLUXUP_DEBUG", "XP_LOG_INSERT_FAIL: " + e.getMessage())); + } + + public void addXpLog(String uid, int amount, String type) { + addXpLog(uid, amount, type, null); + } + + /** + * Calcula o XP total ganho hoje a partir dos logs. + */ + public void getTodayXp(String uid, Consumer callback) { + if (uid == null) { + android.util.Log.e("FLUXUP_DEBUG", "getTodayXp: uid is null"); + callback.accept(0); + return; + } + java.util.Calendar cal = java.util.Calendar.getInstance(); + cal.set(java.util.Calendar.HOUR_OF_DAY, 0); + cal.set(java.util.Calendar.MINUTE, 0); + cal.set(java.util.Calendar.SECOND, 0); + cal.set(java.util.Calendar.MILLISECOND, 0); + java.util.Date start = cal.getTime(); + + cal.add(java.util.Calendar.DAY_OF_MONTH, 1); + java.util.Date end = cal.getTime(); + + db.collection("xp_logs") + .whereEqualTo("userId", uid) + .whereGreaterThanOrEqualTo("created_at", start) + .whereLessThan("created_at", end) + .get() + .addOnSuccessListener(snapshots -> { + int total = 0; + if (snapshots != null) { + for (com.google.firebase.firestore.QueryDocumentSnapshot doc : snapshots) { + Long amount = doc.getLong("amount"); + if (amount != null) total += amount.intValue(); + } + } + android.util.Log.d("FLUXUP_DEBUG", "TODAY_XP_FROM_LOGS: " + total); + callback.accept(total); + }) + .addOnFailureListener(e -> { + android.util.Log.e("FLUXUP_DEBUG", "XP_QUERY_FAIL: " + e.getMessage()); + callback.accept(0); + }); } } diff --git a/app/src/main/java/com/fluxup/app/InicioFragment.java b/app/src/main/java/com/fluxup/app/InicioFragment.java index 275eeb4..c44ce1f 100644 --- a/app/src/main/java/com/fluxup/app/InicioFragment.java +++ b/app/src/main/java/com/fluxup/app/InicioFragment.java @@ -1,5 +1,6 @@ package com.fluxup.app; + import android.annotation.SuppressLint; import android.os.Bundle; import android.os.CountDownTimer; @@ -50,6 +51,7 @@ public class InicioFragment extends Fragment { private ListenerRegistration tasksListener, userListener, rankingListener; private List currentTasks = new ArrayList<>(); private Task selectedTaskForFocus = null; + private boolean isCompletingFocus = false; private boolean isTimerRunning = false; private long timeLeftInMillis = 25 * 60 * 1000; // 25 minutos @@ -165,7 +167,6 @@ public class InicioFragment extends Fragment { } currentTasks = activeTasks; updateTasksUI(); - updateTodayCard(); }); } } @@ -180,9 +181,7 @@ public class InicioFragment extends Fragment { if (tvTodayStreak != null) { tvTodayStreak.setText(user.streak + " dias"); } - if (tvTodayXP != null) { - tvTodayXP.setText(String.valueOf(user.xp_hoje)); - } + refreshTodayStats(user); // Update Reward Button State String today = new java.text.SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new java.util.Date()); @@ -212,7 +211,29 @@ public class InicioFragment extends Fragment { if (tasksAdapter != null) { tasksAdapter.setTasks(currentTasks); } - updateTodayCard(); + refreshTodayStats(); + } + + private void refreshTodayStats() { + FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser(); + if (currentUser == null) return; + FirestoreManager.getInstance().getUser(currentUser.getUid(), this::refreshTodayStats); + } + + private void refreshTodayStats(Usuario user) { + if (user == null || !isAdded()) return; + + // Update basic card info (Tasks, Progress) + updateTodayCard(user); + + // Update XP specifically from logs + FirestoreManager.getInstance().getTodayXp(user.id_usuario, todayXp -> { + if (isAdded() && tvTodayXP != null) { + tvTodayXP.setText(String.valueOf(todayXp)); + android.util.Log.d("FLUXUP_DEBUG", "XP_STATE_UPDATED: " + todayXp); + android.util.Log.d("FLUXUP_DEBUG", "XP_VALUE_RENDERED_IN_CARD: " + todayXp); + } + }); } private void selectTaskForFocus(Task task) { @@ -251,6 +272,8 @@ public class InicioFragment extends Fragment { updates.put("xp_hoje", com.google.firebase.firestore.FieldValue.increment(amount)); updates.put("xp_semanal", com.google.firebase.firestore.FieldValue.increment(amount)); + FirestoreManager.getInstance().addXpLog(uid, amount, "bonus"); + FirestoreManager.getInstance().getUser(uid, user -> { if (user != null && isAdded()) { int currentLevel = user.level; @@ -260,16 +283,14 @@ public class InicioFragment extends Fragment { showLevelUpAnimation(currentLevel + 1); } FirestoreManager.getInstance().updateUserStats(uid, updates); + refreshTodayStats(); } }); } } private void updateTodayCard() { - FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser(); - if (currentUser != null) { - FirestoreManager.getInstance().getUser(currentUser.getUid(), this::updateTodayCard); - } + refreshTodayStats(); } private void updateTodayCard(Usuario user) { @@ -281,7 +302,7 @@ public class InicioFragment extends Fragment { tvTodayTasksCount.setText(completed + "/" + goal); } - if (pbDailyTasksProgress != null) { + if (pbDailyTasksProgress != null && goal > 0) { int progress = (completed * 100) / goal; if (progress > 100) progress = 100; pbDailyTasksProgress.setProgress(progress); @@ -297,6 +318,10 @@ public class InicioFragment extends Fragment { layoutTasksSection.setVisibility(View.VISIBLE); } } + + if (tasksAdapter != null) { + tasksAdapter.setTasks(currentTasks); + } } private void checkDailyResetAndStreak(Usuario user) { @@ -494,13 +519,28 @@ public class InicioFragment extends Fragment { private void triggerVibration() { if (getContext() == null) return; - android.os.Vibrator v = (android.os.Vibrator) getContext().getSystemService(android.content.Context.VIBRATOR_SERVICE); - if (v != null) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - v.vibrate(android.os.VibrationEffect.createOneShot(100, android.os.VibrationEffect.DEFAULT_AMPLITUDE)); - } else { - v.vibrate(100); + + try { + // Check settings + android.content.SharedPreferences prefs = getContext().getSharedPreferences("FluxupSettings", android.content.Context.MODE_PRIVATE); + boolean vibrationEnabled = prefs.getBoolean("vibration_on_finish", true); + + if (!vibrationEnabled) { + android.util.Log.d("FLUXUP_DEBUG", "VIBRATION_DISABLED_BY_USER"); + return; } + + android.os.Vibrator v = (android.os.Vibrator) getContext().getSystemService(android.content.Context.VIBRATOR_SERVICE); + if (v != null && v.hasVibrator()) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + v.vibrate(android.os.VibrationEffect.createOneShot(100, android.os.VibrationEffect.DEFAULT_AMPLITUDE)); + } else { + v.vibrate(100); + } + android.util.Log.d("FLUXUP_DEBUG", "VIBRATION_SUCCESS"); + } + } catch (Exception e) { + android.util.Log.e("FLUXUP_DEBUG", "VIBRATION_ERROR: " + e.getMessage()); } } @@ -574,127 +614,118 @@ public class InicioFragment extends Fragment { } @Override public void onFinish() { - android.util.Log.d("FLUXUP_DEBUG", "FOCUS_FINISHED_START"); - isTimerRunning = false; - - if (getActivity() != null && isAdded()) { - btnStartFocus.setText("Começar Foco"); - } - - try { - if (selectedTaskForFocus == null) { - android.util.Log.e("FLUXUP_DEBUG", "FOCUS_FINISHED_ERROR: selectedTask is null"); + handleFocusComplete(); + } + }.start(); + isTimerRunning = true; + btnStartFocus.setText("Pausar Foco"); + } + + private void handleFocusComplete() { + if (isCompletingFocus) return; + isCompletingFocus = true; + + android.util.Log.d("FLUXUP_DEBUG", "FOCUS_COMPLETE_START"); + + try { + if (countDownTimer != null) { + countDownTimer.cancel(); + android.util.Log.d("FLUXUP_DEBUG", "TIMER_CANCELLED"); + } + + isTimerRunning = false; + if (btnStartFocus != null) btnStartFocus.setText("Começar Foco"); + + if (selectedTaskForFocus == null) { + android.util.Log.e("FLUXUP_DEBUG", "FOCUS_COMPLETE_ERROR: selectedTask null"); + isCompletingFocus = false; + return; + } + android.util.Log.d("FLUXUP_DEBUG", "SELECTED_TASK: " + selectedTaskForFocus.id); + + FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser(); + if (currentUser == null) { + android.util.Log.e("FLUXUP_DEBUG", "FOCUS_COMPLETE_ERROR: userId null"); + isCompletingFocus = false; + return; + } + String userId = currentUser.getUid(); + android.util.Log.d("FLUXUP_DEBUG", "USER_ID: " + userId); + + final Task task = selectedTaskForFocus; + final String taskId = task.id; + + android.util.Log.d("FLUXUP_DEBUG", "TASK_UPDATE_START"); + task.completed = true; + FirestoreManager.getInstance().updateTask(task, () -> { + // Task update success is logged in FirestoreManager + + android.util.Log.d("FLUXUP_DEBUG", "XP_LOG_INSERT_START"); + FirestoreManager.getInstance().addXpLog(userId, 50, "focus_task", taskId); + + FirestoreManager.getInstance().getUser(userId, user -> { + if (!isAdded()) { + isCompletingFocus = false; return; } - - String taskId = selectedTaskForFocus.id; - android.util.Log.d("FLUXUP_DEBUG", "TASK_ID: " + taskId); - FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser(); - if (currentUser == null) { - android.util.Log.e("FLUXUP_DEBUG", "FOCUS_FINISHED_ERROR: USER_ID is null"); - return; - } - String userId = currentUser.getUid(); - android.util.Log.d("FLUXUP_DEBUG", "USER_ID: " + userId); - - if (taskId == null || taskId.isEmpty()) { - android.util.Log.e("FLUXUP_DEBUG", "FOCUS_FINISHED_ERROR: task.id is empty"); + if (user == null) { + android.util.Log.e("FLUXUP_DEBUG", "FOCUS_COMPLETE_ERROR: User object null"); + isCompletingFocus = false; return; } - // Início da sincronização segura - android.util.Log.d("FLUXUP_DEBUG", "FOCUS_COMPLETE_START"); - - FirestoreManager.getInstance().getUser(userId, user -> { - if (user == null) { - android.util.Log.e("FLUXUP_DEBUG", "FOCUS_COMPLETE_ERROR: User data is null"); - return; + Map updates = new HashMap<>(); + updates.put("xp", com.google.firebase.firestore.FieldValue.increment(50)); + updates.put("xp_hoje", com.google.firebase.firestore.FieldValue.increment(50)); + updates.put("xp_semanal", com.google.firebase.firestore.FieldValue.increment(50)); + updates.put("tempo_foco_total", com.google.firebase.firestore.FieldValue.increment(task.duration)); + updates.put("tempo_foco_hoje", com.google.firebase.firestore.FieldValue.increment(task.duration)); + updates.put("sessoes_foco_completas", com.google.firebase.firestore.FieldValue.increment(1)); + updates.put("tasks_concluidas_hoje", com.google.firebase.firestore.FieldValue.increment(1)); + updates.put("total_tasks_concluidas", com.google.firebase.firestore.FieldValue.increment(1)); + + if (user.tasks_concluidas_hoje == 0) { + updates.put("streak", com.google.firebase.firestore.FieldValue.increment(1)); + if (user.streak + 1 > user.melhor_streak) { + updates.put("melhor_streak", user.streak + 1); } - if (!isAdded()) return; + } + + int currentLevel = user.level; + int threshold = (currentLevel * (currentLevel + 1) / 2) * 100; + if (user.xp + 50 >= threshold) { + updates.put("level", currentLevel + 1); + showLevelUpAnimation(currentLevel + 1); + } + + FirestoreManager.getInstance().updateUserStats(userId, updates, () -> { + android.util.Log.d("FLUXUP_DEBUG", "REFRESH_HOME_START"); + refreshTodayStats(); - android.util.Log.d("FLUXUP_DEBUG", "SELECTED_TASK: " + taskId); - android.util.Log.d("FLUXUP_DEBUG", "USER_ID: " + userId); - android.util.Log.d("FLUXUP_DEBUG", "XP_BEFORE: " + user.xp_hoje); - - // 1. Log de XP - Map log = new HashMap<>(); - log.put("userId", userId); - log.put("amount", 50); - log.put("type", "focus_task"); - log.put("created_at", com.google.firebase.firestore.FieldValue.serverTimestamp()); - - com.google.firebase.firestore.FirebaseFirestore.getInstance().collection("xp_logs") - .add(log) - .addOnSuccessListener(ref -> android.util.Log.d("FLUXUP_DEBUG", "XP_LOG_INSERT_RESULT: SUCCESS")) - .addOnFailureListener(e -> android.util.Log.e("FLUXUP_DEBUG", "XP_LOG_INSERT_RESULT: FAIL - " + e.getMessage())); - - // 2. Preparar updates consolidados - Map updates = new HashMap<>(); - // Usando os nomes exatos de Usuario.java: xp, xp_hoje, xp_semanal - updates.put("xp", com.google.firebase.firestore.FieldValue.increment(50)); - updates.put("xp_hoje", com.google.firebase.firestore.FieldValue.increment(50)); - updates.put("xp_semanal", com.google.firebase.firestore.FieldValue.increment(50)); - - updates.put("tempo_foco_total", com.google.firebase.firestore.FieldValue.increment(selectedTaskForFocus.duration)); - updates.put("tempo_foco_hoje", com.google.firebase.firestore.FieldValue.increment(selectedTaskForFocus.duration)); - updates.put("sessoes_foco_completas", com.google.firebase.firestore.FieldValue.increment(1)); - - updates.put("tasks_concluidas_hoje", com.google.firebase.firestore.FieldValue.increment(1)); - updates.put("total_tasks_concluidas", com.google.firebase.firestore.FieldValue.increment(1)); - - if (user.tasks_concluidas_hoje == 0) { - updates.put("streak", com.google.firebase.firestore.FieldValue.increment(1)); - if (user.streak + 1 > user.melhor_streak) { - updates.put("melhor_streak", user.streak + 1); - } - } - - // Level Up - int currentLevel = user.level; - int threshold = (currentLevel * (currentLevel + 1) / 2) * 100; - if (user.xp + 50 >= threshold) { - updates.put("level", currentLevel + 1); - } - - // 3. Gravar tudo no Backend (User Stats) - com.google.firebase.firestore.FirebaseFirestore.getInstance().collection("users").document(userId) - .update(updates) - .addOnSuccessListener(aVoid -> { - android.util.Log.d("FLUXUP_DEBUG", "USER_XP_UPDATE_RESULT: SUCCESS"); - if (isAdded()) { - android.util.Log.d("FLUXUP_DEBUG", "HOME_REFRESH_RESULT: SUCCESS"); - } - }) - .addOnFailureListener(e -> android.util.Log.e("FLUXUP_DEBUG", "USER_XP_UPDATE_RESULT: FAIL - " + e.getMessage())); - - // 4. Concluir tarefa no backend - selectedTaskForFocus.completed = true; - com.google.firebase.firestore.FirebaseFirestore.getInstance().collection("tasks").document(taskId) - .set(selectedTaskForFocus) - .addOnSuccessListener(aVoid -> android.util.Log.d("FLUXUP_DEBUG", "TASK_UPDATE_RESULT: SUCCESS")) - .addOnFailureListener(e -> android.util.Log.e("FLUXUP_DEBUG", "TASK_UPDATE_RESULT: FAIL - " + e.getMessage())); - - if (getContext() != null && isAdded()) { + if (isAdded()) { + android.util.Log.d("FLUXUP_DEBUG", "HOME_STATE_UPDATED"); Toast.makeText(getContext(), "Boa! Tarefa concluída +50 XP", Toast.LENGTH_LONG).show(); triggerVibration(); selectedTaskForFocus = null; timeLeftInMillis = 25 * 60 * 1000; updateCountDownText(); + android.util.Log.d("FLUXUP_DEBUG", "FOCUS_COMPLETE_SUCCESS"); } + isCompletingFocus = false; }); + }); + }); - } catch (Exception e) { - android.util.Log.e("FLUXUP_DEBUG", "FOCUS_FINISHED_ERROR: " + e.getMessage()); - if (getContext() != null && isAdded()) { - Toast.makeText(getContext(), "Erro ao concluir tarefa. Tenta novamente.", Toast.LENGTH_SHORT).show(); - } - } + } catch (Exception e) { + android.util.Log.e("FLUXUP_DEBUG", "FOCUS_COMPLETE_ERROR: " + e.getMessage()); + e.printStackTrace(); + if (getContext() != null) { + Toast.makeText(getContext(), "Não foi possível concluir a tarefa. Tenta novamente.", Toast.LENGTH_SHORT).show(); } - }.start(); - isTimerRunning = true; - btnStartFocus.setText("Pausar Foco"); + isCompletingFocus = false; + } } private void pauseTimerWithWarning() { diff --git a/app/src/main/java/com/fluxup/app/MyFriendsActivity.java b/app/src/main/java/com/fluxup/app/MyFriendsActivity.java new file mode 100644 index 0000000..dd5c303 --- /dev/null +++ b/app/src/main/java/com/fluxup/app/MyFriendsActivity.java @@ -0,0 +1,103 @@ +package com.fluxup.app; + +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; +import androidx.appcompat.app.AppCompatActivity; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.firestore.FirebaseFirestore; +import java.util.ArrayList; +import java.util.List; + +public class MyFriendsActivity extends AppCompatActivity { + + private EditText etSearch; + private LinearLayout friendsContainer, emptyState; + private TextView tvFriendsCount; + private FirebaseFirestore db; + private String myUid; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_my_friends); + + db = FirebaseFirestore.getInstance(); + myUid = FirebaseAuth.getInstance().getUid(); + + etSearch = findViewById(R.id.etSearchFriends); + friendsContainer = findViewById(R.id.friendsListContainer); + emptyState = findViewById(R.id.emptyStateContainer); + tvFriendsCount = findViewById(R.id.tvFriendsCount); + + findViewById(R.id.btnBack).setOnClickListener(v -> finish()); + findViewById(R.id.btnFindNewFriends).setOnClickListener(v -> startActivity(new Intent(this, FindFriendsActivity.class))); + findViewById(R.id.btnGoToFindFriends).setOnClickListener(v -> startActivity(new Intent(this, FindFriendsActivity.class))); + + loadFriends(); + } + + private void loadFriends() { + if (myUid == null) return; + + // Fetching users I follow + db.collection("follows") + .whereEqualTo("followerId", myUid) + .get() + .addOnSuccessListener(snapshots -> { + if (snapshots.isEmpty()) { + showEmptyState(true); + return; + } + + showEmptyState(false); + tvFriendsCount.setText(snapshots.size() + " Amigos"); + friendsContainer.removeAllViews(); + + for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) { + String friendId = doc.getString("followingId"); + if (friendId != null) { + fetchFriendDetails(friendId); + } + } + }); + } + + private void fetchFriendDetails(String friendId) { + db.collection("users").document(friendId).get().addOnSuccessListener(snapshot -> { + Usuario friend = snapshot.toObject(Usuario.class); + if (friend != null) { + addFriendItem(friend); + } + }); + } + + private void addFriendItem(Usuario friend) { + View view = getLayoutInflater().inflate(R.layout.item_ranking_user, friendsContainer, false); + TextView tvName = view.findViewById(R.id.tvRankingName); + TextView tvXP = view.findViewById(R.id.tvRankingXP); + TextView tvLabel = view.findViewById(R.id.tvRankingLabelTu); + + tvName.setText(friend.usuario); + tvXP.setText(friend.xp + " XP"); + tvLabel.setText("Nível " + friend.level); + tvLabel.setVisibility(View.VISIBLE); + tvLabel.setTextColor(getResources().getColor(R.color.primary_purple)); + + view.setOnClickListener(v -> { + // Open public profile (future) + Toast.makeText(this, "Perfil de " + friend.usuario, Toast.LENGTH_SHORT).show(); + }); + + friendsContainer.addView(view); + } + + private void showEmptyState(boolean show) { + emptyState.setVisibility(show ? View.VISIBLE : View.GONE); + friendsContainer.setVisibility(show ? View.GONE : View.VISIBLE); + } +} diff --git a/app/src/main/java/com/fluxup/app/NotificationHelper.java b/app/src/main/java/com/fluxup/app/NotificationHelper.java new file mode 100644 index 0000000..12f19ce --- /dev/null +++ b/app/src/main/java/com/fluxup/app/NotificationHelper.java @@ -0,0 +1,52 @@ +package com.fluxup.app; + +import com.fluxup.app.R; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; +import android.Manifest; +import android.content.pm.PackageManager; +import androidx.core.app.ActivityCompat; + +public class NotificationHelper { + private static final String CHANNEL_ID = "fluxup_notifications"; + private static final String CHANNEL_NAME = "Fluxup Reminders"; + + public static void showNotification(Context context, String title, String message) { + NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT); + notificationManager.createNotificationChannel(channel); + } + + Intent intent = new Intent(context, MainActivity.class); + PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + + NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID) + .setSmallIcon(R.drawable.ic_nav_home) + .setContentTitle(title) + .setContentText(message) + .setAutoCancel(true) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setContentIntent(pendingIntent); + + NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(context); + + // Verificar permissão no Android 13+ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + return; + } + } + + notificationManagerCompat.notify((int) System.currentTimeMillis(), builder.build()); + } +} diff --git a/app/src/main/java/com/fluxup/app/SettingsActivity.java b/app/src/main/java/com/fluxup/app/SettingsActivity.java index da9049d..a0e3c43 100644 --- a/app/src/main/java/com/fluxup/app/SettingsActivity.java +++ b/app/src/main/java/com/fluxup/app/SettingsActivity.java @@ -55,6 +55,7 @@ public class SettingsActivity extends AppCompatActivity { // --- FOCO --- setupClickable(R.id.settingFocusDuration, "Duração do Foco", sharedPreferences.getInt("focus_duration", 25) + " min", v -> showDurationDialog("focus_duration", "Duração do Foco", 25)); setupClickable(R.id.settingBreakDuration, "Duração da Pausa", sharedPreferences.getInt("break_duration", 5) + " min", v -> showDurationDialog("break_duration", "Duração da Pausa", 5)); + setupSwitch(R.id.settingVibration, "Vibração ao Terminar", "vibration_on_finish", true); // --- PRIVACIDADE --- setupSwitch(R.id.settingIncognito, "Modo Incógnito", "incognito", false); diff --git a/app/src/main/java/com/fluxup/app/StatisticsActivity.java b/app/src/main/java/com/fluxup/app/StatisticsActivity.java new file mode 100644 index 0000000..645b729 --- /dev/null +++ b/app/src/main/java/com/fluxup/app/StatisticsActivity.java @@ -0,0 +1,120 @@ +package com.fluxup.app; + +import android.graphics.Color; +import android.os.Bundle; +import android.view.View; +import android.widget.TextView; +import androidx.appcompat.app.AppCompatActivity; +import com.github.mikephil.charting.charts.BarChart; +import com.github.mikephil.charting.charts.LineChart; +import com.github.mikephil.charting.components.XAxis; +import com.github.mikephil.charting.data.BarData; +import com.github.mikephil.charting.data.BarDataSet; +import com.github.mikephil.charting.data.BarEntry; +import com.github.mikephil.charting.data.Entry; +import com.github.mikephil.charting.data.LineData; +import com.github.mikephil.charting.data.LineDataSet; +import com.github.mikephil.charting.formatter.ValueFormatter; +import java.util.ArrayList; +import java.util.List; + +public class StatisticsActivity extends AppCompatActivity { + + private LineChart xpChart; + private BarChart focusChart; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_statistics); + + xpChart = findViewById(R.id.xpChart); + focusChart = findViewById(R.id.focusChart); + + findViewById(R.id.btnBack).setOnClickListener(v -> finish()); + + setupXPChart(); + setupFocusChart(); + loadStats(); + } + + private void setupXPChart() { + List entries = new ArrayList<>(); + entries.add(new Entry(0, 100)); + entries.add(new Entry(1, 150)); + entries.add(new Entry(2, 130)); + entries.add(new Entry(3, 200)); + entries.add(new Entry(4, 250)); + entries.add(new Entry(5, 220)); + entries.add(new Entry(6, 300)); + + LineDataSet dataSet = new LineDataSet(entries, "XP Diário"); + dataSet.setColor(Color.parseColor("#7C3AED")); + dataSet.setCircleColor(Color.parseColor("#7C3AED")); + dataSet.setLineWidth(3f); + dataSet.setMode(LineDataSet.Mode.CUBIC_BEZIER); + dataSet.setDrawFilled(true); + dataSet.setFillColor(Color.parseColor("#DDD6FE")); + dataSet.setValueTextSize(10f); + + LineData lineData = new LineData(dataSet); + xpChart.setData(lineData); + xpChart.getDescription().setEnabled(false); + xpChart.getXAxis().setPosition(XAxis.XAxisPosition.BOTTOM); + xpChart.getXAxis().setDrawGridLines(false); + xpChart.getAxisRight().setEnabled(false); + xpChart.animateX(1000); + xpChart.invalidate(); + } + + private void setupFocusChart() { + List entries = new ArrayList<>(); + entries.add(new BarEntry(0, 45)); + entries.add(new BarEntry(1, 60)); + entries.add(new BarEntry(2, 30)); + entries.add(new BarEntry(3, 90)); + entries.add(new BarEntry(4, 120)); + entries.add(new BarEntry(5, 45)); + entries.add(new BarEntry(6, 75)); + + BarDataSet dataSet = new BarDataSet(entries, "Minutos de Foco"); + dataSet.setColor(Color.parseColor("#3B82F6")); + dataSet.setValueTextSize(10f); + + BarData barData = new BarData(dataSet); + focusChart.setData(barData); + focusChart.getDescription().setEnabled(false); + focusChart.getXAxis().setPosition(XAxis.XAxisPosition.BOTTOM); + focusChart.getXAxis().setDrawGridLines(false); + focusChart.getAxisRight().setEnabled(false); + focusChart.animateY(1000); + focusChart.invalidate(); + + String[] days = {"Seg", "Ter", "Qua", "Qui", "Sex", "Sáb", "Dom"}; + focusChart.getXAxis().setValueFormatter(new ValueFormatter() { + @Override + public String getFormattedValue(float value) { + int index = (int) value; + if (index >= 0 && index < days.length) { + return days[index]; + } + return ""; + } + }); + } + + private void loadStats() { + updateStatCard(findViewById(R.id.statAvgFocus), "Média Foco", "62m"); + updateStatCard(findViewById(R.id.statTasksPerDay), "Tarefas/Dia", "4.2"); + updateStatCard(findViewById(R.id.statBestDay), "Melhor Dia", "Sex"); + updateStatCard(findViewById(R.id.statTotalSessions), "Total Sessões", "28"); + } + + private void updateStatCard(View card, String label, String value) { + if (card == null) return; + TextView tvLabel = card.findViewById(R.id.tvStatLabel); + TextView tvValue = card.findViewById(R.id.tvStatValue); + if (tvLabel != null) tvLabel.setText(label); + if (tvValue != null) tvValue.setText(value); + } +} diff --git a/app/src/main/java/com/fluxup/app/TasksAdapter.java b/app/src/main/java/com/fluxup/app/TasksAdapter.java new file mode 100644 index 0000000..b61d440 --- /dev/null +++ b/app/src/main/java/com/fluxup/app/TasksAdapter.java @@ -0,0 +1,85 @@ +package com.fluxup.app; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.RecyclerView; +import java.util.List; + +public class TasksAdapter extends RecyclerView.Adapter { + + private List taskList; + private OnTaskClickListener listener; + + public interface OnTaskClickListener { + void onTaskFocus(Task task); + void onTaskDelete(Task task); + void onTaskEdit(Task task); + } + + public TasksAdapter(List taskList, OnTaskClickListener listener) { + this.taskList = taskList; + this.listener = listener; + } + + public void setTasks(List tasks) { + this.taskList = tasks; + notifyDataSetChanged(); + } + + @NonNull + @Override + public TaskViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_task_home, parent, false); + return new TaskViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull TaskViewHolder holder, int position) { + Task task = taskList.get(position); + holder.bind(task, listener); + } + + @Override + public int getItemCount() { + return taskList.size(); + } + + public static class TaskViewHolder extends RecyclerView.ViewHolder { + TextView tvTitle, tvDuration; + Button btnFocus; + + public TaskViewHolder(@NonNull View itemView) { + super(itemView); + tvTitle = itemView.findViewById(R.id.tvTaskTitle); + tvDuration = itemView.findViewById(R.id.tvTaskDuration); + btnFocus = itemView.findViewById(R.id.btnStartTaskFocus); + } + + public void bind(Task task, OnTaskClickListener listener) { + tvTitle.setText(task.title); + tvDuration.setText(task.duration + " min"); + + if (task.completed) { + tvTitle.setPaintFlags(tvTitle.getPaintFlags() | android.graphics.Paint.STRIKE_THRU_TEXT_FLAG); + tvTitle.setTextColor(ContextCompat.getColor(itemView.getContext(), R.color.text_secondary)); + btnFocus.setEnabled(false); + btnFocus.setAlpha(0.5f); + btnFocus.setText("Concluído"); + } else { + tvTitle.setPaintFlags(tvTitle.getPaintFlags() & (~android.graphics.Paint.STRIKE_THRU_TEXT_FLAG)); + tvTitle.setTextColor(ContextCompat.getColor(itemView.getContext(), R.color.text_primary)); + btnFocus.setEnabled(true); + btnFocus.setAlpha(1.0f); + btnFocus.setText("Focar"); + } + + btnFocus.setOnClickListener(v -> listener.onTaskFocus(task)); + itemView.setOnClickListener(v -> listener.onTaskEdit(task)); + } + } +} diff --git a/app/src/main/res/drawable/edittext_bg.xml b/app/src/main/res/drawable/edittext_bg.xml new file mode 100644 index 0000000..67ce6de --- /dev/null +++ b/app/src/main/res/drawable/edittext_bg.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_trend_down.xml b/app/src/main/res/drawable/ic_trend_down.xml new file mode 100644 index 0000000..0c233ac --- /dev/null +++ b/app/src/main/res/drawable/ic_trend_down.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_trend_up.xml b/app/src/main/res/drawable/ic_trend_up.xml new file mode 100644 index 0000000..ccd3a4a --- /dev/null +++ b/app/src/main/res/drawable/ic_trend_up.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/task_selected_bg.xml b/app/src/main/res/drawable/task_selected_bg.xml new file mode 100644 index 0000000..bbd7fe8 --- /dev/null +++ b/app/src/main/res/drawable/task_selected_bg.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_my_friends.xml b/app/src/main/res/layout/activity_my_friends.xml new file mode 100644 index 0000000..95cef3f --- /dev/null +++ b/app/src/main/res/layout/activity_my_friends.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +