continuar utilizador
This commit is contained in:
@@ -28,6 +28,7 @@
|
||||
<activity android:name=".SettingsActivity" />
|
||||
<activity android:name=".StreakActivity" />
|
||||
<activity android:name=".FindFriendsActivity" />
|
||||
<activity android:name=".TrophiesActivity" />
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,14 +1,32 @@
|
||||
package com.fluxup.app;
|
||||
|
||||
|
||||
import com.google.android.gms.tasks.Task;
|
||||
import com.google.firebase.auth.FirebaseAuth;
|
||||
import com.google.firebase.auth.FirebaseAuthException;
|
||||
import com.google.firebase.auth.FirebaseUser;
|
||||
|
||||
/**
|
||||
* AuthManager - Classe responsável pela gestão centralizada da autenticação Firebase.
|
||||
* Segue padrões de desenvolvimento sénior para garantir modularidade e tratamento de erros robusto.
|
||||
*/
|
||||
public class AuthManager {
|
||||
private static AuthManager instance;
|
||||
|
||||
private AuthManager() {
|
||||
// private constructor
|
||||
private static AuthManager instance;
|
||||
private final FirebaseAuth mAuth;
|
||||
|
||||
// Interface para comunicação de resultados com a UI (Activities/Fragments)
|
||||
public interface AuthCallback {
|
||||
void onSuccess(FirebaseUser user);
|
||||
void onError(String errorMessage);
|
||||
}
|
||||
|
||||
// Construtor privado para padrão Singleton
|
||||
private AuthManager() {
|
||||
mAuth = FirebaseAuth.getInstance();
|
||||
}
|
||||
|
||||
// Método para obter a instância única do AuthManager
|
||||
public static synchronized AuthManager getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new AuthManager();
|
||||
@@ -16,22 +34,79 @@ public class AuthManager {
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifica se existe um utilizador autenticado na sessão atual.
|
||||
* @return FirebaseUser ou null se não houver sessão ativa.
|
||||
*/
|
||||
public FirebaseUser getCurrentUser() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void loginUtilizador(String email, String password, AuthCallback callback) {
|
||||
// Mock implementation
|
||||
callback.onSuccess();
|
||||
return mAuth.getCurrentUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Realiza o registo de um novo utilizador.
|
||||
* Inclui tratamento de erros específico para passwords fracas e emails inválidos.
|
||||
*/
|
||||
public void registrarUtilizador(String email, String password, AuthCallback callback) {
|
||||
// Mock implementation
|
||||
callback.onSuccess();
|
||||
mAuth.createUserWithEmailAndPassword(email, password)
|
||||
.addOnCompleteListener(task -> {
|
||||
if (task.isSuccessful()) {
|
||||
callback.onSuccess(mAuth.getCurrentUser());
|
||||
} else {
|
||||
callback.onError(handleAuthError(task.getException()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public interface AuthCallback {
|
||||
void onSuccess();
|
||||
void onFailure(Exception e);
|
||||
/**
|
||||
* Realiza o login de um utilizador existente.
|
||||
* A gestão da sessão é feita automaticamente pelo Firebase (Persistence).
|
||||
*/
|
||||
public void loginUtilizador(String email, String password, AuthCallback callback) {
|
||||
mAuth.signInWithEmailAndPassword(email, password)
|
||||
.addOnCompleteListener(task -> {
|
||||
if (task.isSuccessful()) {
|
||||
callback.onSuccess(mAuth.getCurrentUser());
|
||||
} else {
|
||||
callback.onError(handleAuthError(task.getException()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Encerra a sessão do utilizador atual.
|
||||
*/
|
||||
public void logout() {
|
||||
mAuth.signOut();
|
||||
}
|
||||
|
||||
/**
|
||||
* Traduz exceções do Firebase Auth em mensagens amigáveis para o utilizador final.
|
||||
*/
|
||||
private String handleAuthError(Exception e) {
|
||||
if (e instanceof FirebaseAuthException) {
|
||||
String errorCode = ((FirebaseAuthException) e).getErrorCode();
|
||||
switch (errorCode) {
|
||||
case "ERROR_INVALID_EMAIL":
|
||||
return "O formato do email é inválido.";
|
||||
case "ERROR_WEAK_PASSWORD":
|
||||
return "A palavra-passe é demasiado fraca. Use pelo menos 6 caracteres.";
|
||||
case "ERROR_USER_NOT_FOUND":
|
||||
return "Não existe nenhum utilizador registado com este email.";
|
||||
case "ERROR_WRONG_PASSWORD":
|
||||
return "Palavra-passe incorreta.";
|
||||
case "ERROR_EMAIL_ALREADY_IN_USE":
|
||||
return "Este email já está a ser utilizado por outra conta.";
|
||||
case "ERROR_USER_DISABLED":
|
||||
return "Esta conta foi desativada.";
|
||||
case "ERROR_TOO_MANY_REQUESTS":
|
||||
return "Demasiadas tentativas falhadas. Tente mais tarde.";
|
||||
case "ERROR_OPERATION_NOT_ALLOWED":
|
||||
return "O login com email/password não está ativado no Firebase.";
|
||||
default:
|
||||
return "Erro na autenticação: " + e.getLocalizedMessage();
|
||||
}
|
||||
}
|
||||
return e != null ? e.getLocalizedMessage() : "Ocorreu um erro desconhecido.";
|
||||
}
|
||||
}
|
||||
114
app/src/main/java/com/fluxup/app/FindFriendsActivity.java
Normal file
114
app/src/main/java/com/fluxup/app/FindFriendsActivity.java
Normal file
@@ -0,0 +1,114 @@
|
||||
package com.fluxup.app;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class FindFriendsActivity extends AppCompatActivity {
|
||||
|
||||
private ImageButton btnClose;
|
||||
private RecyclerView rvSuggestions;
|
||||
private View btnContacts, btnSearchByName, btnProfileLink;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_find_friends);
|
||||
|
||||
btnClose = findViewById(R.id.btnClose);
|
||||
rvSuggestions = findViewById(R.id.rvSuggestions);
|
||||
btnContacts = findViewById(R.id.btnContacts);
|
||||
btnSearchByName = findViewById(R.id.btnSearchByName);
|
||||
btnProfileLink = findViewById(R.id.btnProfileLink);
|
||||
|
||||
btnClose.setOnClickListener(v -> finish());
|
||||
|
||||
setupSuggestions();
|
||||
}
|
||||
|
||||
private void setupSuggestions() {
|
||||
rvSuggestions.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
|
||||
|
||||
List<Suggestion> suggestions = new ArrayList<>();
|
||||
suggestions.add(new Suggestion("Maria Silva", "Segue você", R.color.reward_yellow));
|
||||
suggestions.add(new Suggestion("João Pereira", "Amigo em comum", R.color.success_green));
|
||||
suggestions.add(new Suggestion("Ana Costa", "Segue você", R.color.streak_orange));
|
||||
suggestions.add(new Suggestion("Ricardo M.", "Novo no Fluxup", R.color.streak_blue));
|
||||
|
||||
SuggestionsAdapter adapter = new SuggestionsAdapter(suggestions);
|
||||
rvSuggestions.setAdapter(adapter);
|
||||
}
|
||||
|
||||
// --- Data Model ---
|
||||
private static class Suggestion {
|
||||
String name;
|
||||
String info;
|
||||
int colorRes;
|
||||
|
||||
Suggestion(String name, String info, int colorRes) {
|
||||
this.name = name;
|
||||
this.info = info;
|
||||
this.colorRes = colorRes;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Adapter ---
|
||||
private class SuggestionsAdapter extends RecyclerView.Adapter<SuggestionsAdapter.ViewHolder> {
|
||||
private final List<Suggestion> suggestions;
|
||||
|
||||
SuggestionsAdapter(List<Suggestion> suggestions) {
|
||||
this.suggestions = suggestions;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_friend_suggestion, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
Suggestion item = suggestions.get(position);
|
||||
holder.tvName.setText(item.name);
|
||||
holder.tvInfo.setText(item.info);
|
||||
holder.ivAvatar.setColorFilter(getResources().getColor(item.colorRes));
|
||||
|
||||
holder.btnFollow.setOnClickListener(v -> {
|
||||
holder.btnFollow.setText("Seguindo");
|
||||
holder.btnFollow.setEnabled(false);
|
||||
holder.btnFollow.setAlpha(0.6f);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return suggestions.size();
|
||||
}
|
||||
|
||||
class ViewHolder extends RecyclerView.ViewHolder {
|
||||
ImageView ivAvatar;
|
||||
TextView tvName, tvInfo;
|
||||
MaterialButton btnFollow;
|
||||
|
||||
ViewHolder(View view) {
|
||||
super(view);
|
||||
ivAvatar = view.findViewById(R.id.ivFriendAvatar);
|
||||
tvName = view.findViewById(R.id.tvFriendName);
|
||||
tvInfo = view.findViewById(R.id.tvFriendInfo);
|
||||
btnFollow = view.findViewById(R.id.btnFollowBack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
79
app/src/main/java/com/fluxup/app/FirestoreManager.java
Normal file
79
app/src/main/java/com/fluxup/app/FirestoreManager.java
Normal file
@@ -0,0 +1,79 @@
|
||||
package com.fluxup.app;
|
||||
|
||||
import com.google.firebase.firestore.FirebaseFirestore;
|
||||
import com.google.firebase.firestore.ListenerRegistration;
|
||||
import com.google.firebase.firestore.QueryDocumentSnapshot;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class FirestoreManager {
|
||||
|
||||
private static FirestoreManager instance;
|
||||
private final FirebaseFirestore db;
|
||||
|
||||
private FirestoreManager() {
|
||||
db = FirebaseFirestore.getInstance();
|
||||
}
|
||||
|
||||
public static synchronized FirestoreManager getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new FirestoreManager();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Observa as mudanças no documento do utilizador em tempo real.
|
||||
*/
|
||||
public ListenerRegistration observeUser(String uid, Consumer<Usuario> listener) {
|
||||
return db.collection("users").document(uid)
|
||||
.addSnapshotListener((snapshot, e) -> {
|
||||
if (e != null) return;
|
||||
if (snapshot != null && snapshot.exists()) {
|
||||
Usuario usuario = snapshot.toObject(Usuario.class);
|
||||
if (usuario != null) listener.accept(usuario);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Observa as tarefas do utilizador em tempo real.
|
||||
*/
|
||||
public ListenerRegistration observeTasks(String uid, Consumer<List<Task>> listener) {
|
||||
return db.collection("tasks")
|
||||
.whereEqualTo("userId", uid)
|
||||
.addSnapshotListener((snapshots, e) -> {
|
||||
if (e != null) return;
|
||||
List<Task> tasks = new ArrayList<>();
|
||||
if (snapshots != null) {
|
||||
for (QueryDocumentSnapshot doc : snapshots) {
|
||||
tasks.add(doc.toObject(Task.class));
|
||||
}
|
||||
}
|
||||
listener.accept(tasks);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Atualiza o estado de uma tarefa.
|
||||
*/
|
||||
public void updateTask(Task task) {
|
||||
db.collection("tasks").document(task.id).set(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adiciona uma nova tarefa.
|
||||
*/
|
||||
public void addTask(Task task) {
|
||||
db.collection("tasks").document(task.id).set(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Atualiza campos específicos do perfil do utilizador (ex: XP, Streak).
|
||||
*/
|
||||
public void updateUserStats(String uid, Map<String, Object> updates) {
|
||||
db.collection("users").document(uid).update(updates);
|
||||
}
|
||||
}
|
||||
59
app/src/main/java/com/fluxup/app/FluxupApplication.java
Normal file
59
app/src/main/java/com/fluxup/app/FluxupApplication.java
Normal file
@@ -0,0 +1,59 @@
|
||||
package com.fluxup.app;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
|
||||
import com.fluxup.app.BuildConfig;
|
||||
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 FluxupApplication extends Application {
|
||||
|
||||
private static final String TAG = "FLUXUP_DEBUG";
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
// Aplicar preferências de tema
|
||||
SharedPreferences prefs = getSharedPreferences("FluxupSettings", Context.MODE_PRIVATE);
|
||||
if (prefs.getBoolean("darkMode", false)) {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
|
||||
} else {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
|
||||
}
|
||||
|
||||
// Inicializar Firebase
|
||||
FirebaseApp.initializeApp(this);
|
||||
Log.d(TAG, "FirebaseApp inicializado com sucesso.");
|
||||
|
||||
// Inicializar Firebase App Check
|
||||
FirebaseAppCheck firebaseAppCheck = FirebaseAppCheck.getInstance();
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
// 1. Instala o provider
|
||||
firebaseAppCheck.installAppCheckProviderFactory(DebugAppCheckProviderFactory.getInstance());
|
||||
|
||||
// 2. FORÇAR a exibição do token no Logcat
|
||||
// Adicionamos um log específico com a tag "FLUXUP_DEBUG"
|
||||
Log.d("FLUXUP_DEBUG", "--------------------------------------------------");
|
||||
Log.d("FLUXUP_DEBUG", "COPIE ESTE TOKEN PARA A CONSOLA FIREBASE:");
|
||||
Log.d("FLUXUP_DEBUG", "Debug Token: " + "Procure pela mensagem acima ou no log do sistema");
|
||||
Log.d("FLUXUP_DEBUG", "--------------------------------------------------");
|
||||
} else {
|
||||
// Em modo RELEASE, usamos o Play Integrity (recomendado para Android).
|
||||
firebaseAppCheck.installAppCheckProviderFactory(
|
||||
PlayIntegrityAppCheckProviderFactory.getInstance());
|
||||
Log.d(TAG, "App Check: Play Integrity Provider instalado.");
|
||||
}
|
||||
|
||||
// Garantir que os tokens são renovados automaticamente e usamos sempre o mais recente
|
||||
firebaseAppCheck.setTokenAutoRefreshEnabled(true);
|
||||
Log.d(TAG, "App Check: Auto-refresh de tokens ativado.");
|
||||
}
|
||||
}
|
||||
@@ -79,9 +79,7 @@ public class InicioFragment extends Fragment {
|
||||
});
|
||||
}
|
||||
|
||||
view.findViewById(R.id.btnAddTasks).setOnClickListener(v -> {
|
||||
Toast.makeText(getContext(), "Adicionar tarefas: Implementação futura", Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
view.findViewById(R.id.btnAddTasks).setOnClickListener(v -> showAddTaskDialog());
|
||||
|
||||
View btnStreak = view.findViewById(R.id.btnStreak);
|
||||
if (btnStreak != null) {
|
||||
@@ -331,4 +329,76 @@ public class InicioFragment extends Fragment {
|
||||
}
|
||||
pauseTimer(); // Parar o timer se a view for destruída
|
||||
}
|
||||
|
||||
private void showAddTaskDialog() {
|
||||
if (getContext() == null) return;
|
||||
|
||||
// Build input field
|
||||
android.widget.EditText editText = new android.widget.EditText(getContext());
|
||||
editText.setHint("Escreve o teu desafio…");
|
||||
editText.setInputType(android.text.InputType.TYPE_CLASS_TEXT
|
||||
| android.text.InputType.TYPE_TEXT_FLAG_CAP_SENTENCES);
|
||||
editText.setTextSize(16);
|
||||
editText.setSingleLine(false);
|
||||
editText.setMaxLines(3);
|
||||
|
||||
// Wrap with padding
|
||||
android.widget.FrameLayout container = new android.widget.FrameLayout(getContext());
|
||||
android.widget.FrameLayout.LayoutParams lp = new android.widget.FrameLayout.LayoutParams(
|
||||
android.widget.FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
android.widget.FrameLayout.LayoutParams.WRAP_CONTENT);
|
||||
int dp24 = (int) (24 * getResources().getDisplayMetrics().density);
|
||||
lp.setMargins(dp24, dp24 / 2, dp24, 0);
|
||||
editText.setLayoutParams(lp);
|
||||
container.addView(editText);
|
||||
|
||||
android.app.AlertDialog dialog = new com.google.android.material.dialog.MaterialAlertDialogBuilder(getContext())
|
||||
.setTitle("Novo Desafio")
|
||||
.setView(container)
|
||||
.setNegativeButton("Cancelar", null)
|
||||
.setPositiveButton("Adicionar", null)
|
||||
|
||||
|
||||
dialog.setOnShowListener(d -> {
|
||||
// Style buttons
|
||||
android.widget.Button btnAdd = dialog.getButton(android.app.AlertDialog.BUTTON_POSITIVE);
|
||||
android.widget.Button btnCancel = dialog.getButton(android.app.AlertDialog.BUTTON_NEGATIVE);
|
||||
if (btnAdd != null) btnAdd.setTextColor(getResources().getColor(R.color.primary_purple));
|
||||
if (btnCancel != null) btnCancel.setTextColor(getResources().getColor(R.color.text_secondary));
|
||||
|
||||
// Override positive so it validates before dismissing
|
||||
if (btnAdd != null) {
|
||||
btnAdd.setOnClickListener(v -> {
|
||||
String title = editText.getText().toString().trim();
|
||||
if (title.isEmpty()) {
|
||||
editText.setError("Escreve um desafio");
|
||||
return;
|
||||
}
|
||||
saveNewTask(title);
|
||||
dialog.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
editText.requestFocus();
|
||||
if (dialog.getWindow() != null) {
|
||||
dialog.getWindow().setSoftInputMode(
|
||||
android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
jhvjhvkvjhv
|
||||
private void saveNewTask(String title) {
|
||||
com.google.firebase.auth.FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser();
|
||||
if (currentUser == null) return;
|
||||
|
||||
String uid = currentUser.getUid();
|
||||
String taskId = com.google.firebase.firestore.FirebaseFirestore.getInstance()
|
||||
.collection("tasks").document().getId();
|
||||
|
||||
Task task = new Task(taskId, title, 10, uid);
|
||||
FirestoreManager.getInstance().addTask(task);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,9 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
if (itemId == R.id.nav_inicio) {
|
||||
selectedFragment = new InicioFragment();
|
||||
} else if (itemId == R.id.nav_trophies) {
|
||||
startActivity(new android.content.Intent(this, TrophiesActivity.class));
|
||||
return true;
|
||||
} else if (itemId == R.id.nav_profile) {
|
||||
selectedFragment = new ProfileFragment();
|
||||
} else if (itemId == R.id.nav_search) {
|
||||
|
||||
@@ -43,6 +43,11 @@ public class ProfileFragment extends Fragment {
|
||||
tvTotalXP = view.findViewById(R.id.tvTotalXP);
|
||||
tvLeagueName = view.findViewById(R.id.tvLeagueName);
|
||||
tvAchievementsCount = view.findViewById(R.id.tvAchievementsCount);
|
||||
View cardLeague = view.findViewById(R.id.cardLeague);
|
||||
cardLeague.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(getActivity(), TrophiesActivity.class);
|
||||
startActivity(intent);
|
||||
});
|
||||
|
||||
startObservingUser();
|
||||
|
||||
|
||||
@@ -1,12 +1,159 @@
|
||||
package com.fluxup.app;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class StreakActivity extends AppCompatActivity {
|
||||
|
||||
private RecyclerView rvCalendar;
|
||||
private TextView tvStreakCount;
|
||||
private ImageButton btnClose;
|
||||
private TabLayout tabLayout;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_streak);
|
||||
|
||||
rvCalendar = findViewById(R.id.rvCalendar);
|
||||
tvStreakCount = findViewById(R.id.tvStreakCount);
|
||||
btnClose = findViewById(R.id.btnClose);
|
||||
tabLayout = findViewById(R.id.tabLayout);
|
||||
|
||||
btnClose.setOnClickListener(v -> finish());
|
||||
|
||||
setupCalendar();
|
||||
setupTabs();
|
||||
}
|
||||
|
||||
private void setupTabs() {
|
||||
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
||||
@Override
|
||||
public void onTabSelected(TabLayout.Tab tab) {
|
||||
// Future: toggle between personal and friends stats
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(TabLayout.Tab tab) {}
|
||||
|
||||
@Override
|
||||
public void onTabReselected(TabLayout.Tab tab) {}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupCalendar() {
|
||||
rvCalendar.setLayoutManager(new GridLayoutManager(this, 7));
|
||||
|
||||
List<CalendarDay> days = new ArrayList<>();
|
||||
|
||||
// Simulating April 2026 (Starts on a Wednesday)
|
||||
// Add empty slots for Mon, Tue
|
||||
days.add(new CalendarDay(0, false, false)); // Mon
|
||||
days.add(new CalendarDay(0, false, false)); // Tue
|
||||
|
||||
// Days 1 to 30
|
||||
for (int i = 1; i <= 30; i++) {
|
||||
boolean isActive = (i >= 1 && i <= 7); // 7-day streak for demo
|
||||
boolean isCurrent = (i == 7);
|
||||
days.add(new CalendarDay(i, isActive, isCurrent));
|
||||
}
|
||||
|
||||
CalendarAdapter adapter = new CalendarAdapter(days);
|
||||
rvCalendar.setAdapter(adapter);
|
||||
}
|
||||
|
||||
// --- Inner Models & Adapter ---
|
||||
|
||||
private static class CalendarDay {
|
||||
int dayNumber;
|
||||
boolean isActive;
|
||||
boolean isCurrent;
|
||||
|
||||
CalendarDay(int dayNumber, boolean isActive, boolean isCurrent) {
|
||||
this.dayNumber = dayNumber;
|
||||
this.isActive = isActive;
|
||||
this.isCurrent = isCurrent;
|
||||
}
|
||||
}
|
||||
|
||||
private class CalendarAdapter extends RecyclerView.Adapter<CalendarAdapter.ViewHolder> {
|
||||
private final List<CalendarDay> days;
|
||||
|
||||
CalendarAdapter(List<CalendarDay> days) {
|
||||
this.days = days;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_calendar_day, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
CalendarDay day = days.get(position);
|
||||
|
||||
if (day.dayNumber == 0) {
|
||||
holder.tvDayNumber.setText("");
|
||||
holder.dayBackground.setVisibility(View.GONE);
|
||||
holder.streakConnector.setVisibility(View.GONE);
|
||||
} else {
|
||||
holder.tvDayNumber.setText(String.valueOf(day.dayNumber));
|
||||
|
||||
if (day.isActive) {
|
||||
holder.dayBackground.setVisibility(View.VISIBLE);
|
||||
holder.tvDayNumber.setTextColor(getResources().getColor(R.color.white));
|
||||
|
||||
// Simple logic for streak connection: if previous day was also active
|
||||
if (position > 0 && days.get(position-1).isActive && day.dayNumber > 1) {
|
||||
holder.streakConnector.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.streakConnector.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
holder.dayBackground.setVisibility(View.GONE);
|
||||
holder.streakConnector.setVisibility(View.GONE);
|
||||
holder.tvDayNumber.setTextColor(getResources().getColor(R.color.text_primary));
|
||||
}
|
||||
|
||||
if (day.isCurrent) {
|
||||
holder.dayIndicator.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.dayIndicator.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return days.size();
|
||||
}
|
||||
|
||||
class ViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView tvDayNumber;
|
||||
View dayBackground;
|
||||
View streakConnector;
|
||||
View dayIndicator;
|
||||
|
||||
ViewHolder(View view) {
|
||||
super(view);
|
||||
tvDayNumber = view.findViewById(R.id.tvDayNumber);
|
||||
dayBackground = view.findViewById(R.id.dayBackground);
|
||||
streakConnector = view.findViewById(R.id.streakConnector);
|
||||
dayIndicator = view.findViewById(R.id.dayIndicator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
app/src/main/java/com/fluxup/app/Task.java
Normal file
19
app/src/main/java/com/fluxup/app/Task.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.fluxup.app;
|
||||
|
||||
public class Task {
|
||||
public String id;
|
||||
public String title;
|
||||
public boolean completed;
|
||||
public int xpReward;
|
||||
public String userId;
|
||||
|
||||
public Task() {}
|
||||
|
||||
public Task(String id, String title, int xpReward, String userId) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.completed = false;
|
||||
this.xpReward = xpReward;
|
||||
this.userId = userId;
|
||||
}
|
||||
}
|
||||
145
app/src/main/java/com/fluxup/app/TrophiesActivity.java
Normal file
145
app/src/main/java/com/fluxup/app/TrophiesActivity.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package com.fluxup.app;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.ScaleAnimation;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import com.google.firebase.auth.FirebaseAuth;
|
||||
import com.google.firebase.firestore.ListenerRegistration;
|
||||
|
||||
public class TrophiesActivity extends AppCompatActivity {
|
||||
|
||||
private TextView tvDivisionTitle, tvTimeRemaining, tvTrophyProgress, tvMotivational;
|
||||
private ImageButton btnBack;
|
||||
private LinearLayout trophyContainer, inactiveState;
|
||||
private ImageView trophy1, trophy2, trophy3;
|
||||
|
||||
private FirestoreManager firestoreManager;
|
||||
private FirebaseAuth mAuth;
|
||||
private ListenerRegistration userListener;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_trophies);
|
||||
|
||||
firestoreManager = FirestoreManager.getInstance();
|
||||
mAuth = FirebaseAuth.getInstance();
|
||||
|
||||
initViews();
|
||||
setupListeners();
|
||||
observeUserData();
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
tvDivisionTitle = findViewById(R.id.tvDivisionTitle);
|
||||
tvTimeRemaining = findViewById(R.id.tvTimeRemaining);
|
||||
tvTrophyProgress = findViewById(R.id.tvTrophyProgress);
|
||||
tvMotivational = findViewById(R.id.tvMotivational);
|
||||
btnBack = findViewById(R.id.btnBack);
|
||||
trophyContainer = findViewById(R.id.trophyContainer);
|
||||
inactiveState = findViewById(R.id.inactiveState);
|
||||
trophy1 = findViewById(R.id.trophy1);
|
||||
trophy2 = findViewById(R.id.trophy2);
|
||||
trophy3 = findViewById(R.id.trophy3);
|
||||
}
|
||||
|
||||
private void setupListeners() {
|
||||
btnBack.setOnClickListener(v -> finish());
|
||||
}
|
||||
|
||||
private void observeUserData() {
|
||||
String uid = mAuth.getUid();
|
||||
if (uid != null) {
|
||||
userListener = firestoreManager.observeUser(uid, this::updateUI);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUI(Usuario user) {
|
||||
if (user == null) return;
|
||||
|
||||
tvDivisionTitle.setText("Divisão " + user.league);
|
||||
|
||||
// Trophy logic based on streak
|
||||
int currentStreak = user.streak;
|
||||
int trophies = user.trophiesCount;
|
||||
|
||||
// Update trophy visuals
|
||||
updateTrophyIcons(trophies);
|
||||
|
||||
// Progress message
|
||||
int daysToNext = 30 - (currentStreak % 30);
|
||||
if (daysToNext == 30 && currentStreak > 0) {
|
||||
tvTrophyProgress.setText("Troféu conquistado! Mantém a ofensiva.");
|
||||
} else {
|
||||
tvTrophyProgress.setText("Faltam " + daysToNext + " dias para o próximo troféu");
|
||||
}
|
||||
|
||||
// Handle inactive state
|
||||
if (currentStreak == 0) {
|
||||
inactiveState.setVisibility(View.VISIBLE);
|
||||
trophyContainer.setAlpha(0.5f);
|
||||
tvMotivational.setVisibility(View.GONE);
|
||||
} else {
|
||||
inactiveState.setVisibility(View.GONE);
|
||||
trophyContainer.setAlpha(1.0f);
|
||||
tvMotivational.setVisibility(View.VISIBLE);
|
||||
tvMotivational.setText("Estás a progredir bem na Divisão " + user.league + "!");
|
||||
}
|
||||
|
||||
// Simple scale animation for the active trophy
|
||||
animateActiveTrophy(trophies);
|
||||
}
|
||||
|
||||
private void updateTrophyIcons(int count) {
|
||||
// Reset alphas
|
||||
trophy1.setAlpha(0.3f);
|
||||
trophy2.setAlpha(0.3f);
|
||||
trophy3.setAlpha(0.3f);
|
||||
|
||||
if (count >= 1) trophy1.setAlpha(1.0f);
|
||||
if (count >= 2) trophy2.setAlpha(1.0f);
|
||||
if (count >= 3) trophy3.setAlpha(1.0f);
|
||||
|
||||
// Highlight current progress (the next one)
|
||||
if (count == 0) highlightTrophy(trophy1);
|
||||
else if (count == 1) highlightTrophy(trophy2);
|
||||
else if (count == 2) highlightTrophy(trophy3);
|
||||
}
|
||||
|
||||
private void highlightTrophy(ImageView trophy) {
|
||||
trophy.setAlpha(0.6f);
|
||||
trophy.setBackgroundResource(R.drawable.circle_bg);
|
||||
trophy.setBackgroundTintList(android.content.res.ColorStateList.valueOf(getResources().getColor(R.color.primary_purple)));
|
||||
trophy.setPadding(12, 12, 12, 12);
|
||||
}
|
||||
|
||||
private void animateActiveTrophy(int count) {
|
||||
ImageView target = null;
|
||||
if (count == 0) target = trophy1;
|
||||
else if (count == 1) target = trophy2;
|
||||
else if (count == 2) target = trophy3;
|
||||
|
||||
if (target != null) {
|
||||
ScaleAnimation scale = new ScaleAnimation(1f, 1.1f, 1f, 1.1f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
|
||||
scale.setDuration(1000);
|
||||
scale.setRepeatCount(Animation.INFINITE);
|
||||
scale.setRepeatMode(Animation.REVERSE);
|
||||
target.startAnimation(scale);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (userListener != null) {
|
||||
userListener.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ public class Usuario {
|
||||
public String handle = "";
|
||||
public String bio = "";
|
||||
public int achievementsCount = 0;
|
||||
public int trophiesCount = 0;
|
||||
|
||||
public Usuario() {}
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#CCCCCC" />
|
||||
</shape>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="#40FFFFFF">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/primary_purple" />
|
||||
<corners android:radius="@dimen/radius_duo" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#FFFFFF" />
|
||||
<corners android:radius="8dp" />
|
||||
<solid android:color="@color/white" />
|
||||
<corners android:radius="@dimen/radius_duo" />
|
||||
<stroke android:width="2dp" android:color="@color/border_color" />
|
||||
</shape>
|
||||
10
app/src/main/res/drawable/ic_close.xml
Normal file
10
app/src/main/res/drawable/ic_close.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/text_primary"
|
||||
android:pathData="M19,6.41L17.59,5,12,10.59,6.41,5,5,6.41,10.59,12,5,17.59,6.41,19,12,13.41,17.59,19,19,17.59,13.41,12,19,6.41z" />
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_contacts.xml
Normal file
9
app/src/main/res/drawable/ic_contacts.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M20,4H4C2.9,4 2.01,4.9 2.01,6L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.1,4 20,4zM4,6h16v7H4V6zM19,18H5l4,-5h3l1,1l2,-3l4,7z"/>
|
||||
</vector>
|
||||
@@ -1,9 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
|
||||
android:fillColor="@color/streak_orange"
|
||||
android:pathData="M13.5,0.67C13.5,0.67 14.92,3.42 14.92,5.31C14.92,7.34 13.78,8.23 12.33,9.72L12.33,4.01C12.33,4.01 10.97,1.69 8.01,3.42C5.05,5.15 6.09,10.22 6.09,10.22C6.09,10.22 4.19,7.67 2,12.31C-0.19,16.95 2.13,21.82 2.13,21.82C2.13,21.82 4.1,23.33 8.35,23.33C12.6,23.33 14,21 15.35,23.33C16.7,25.66 19.3,23.33 20,22C20.7,20.67 22,17.33 22,14.67C22,12 18,10 18,10C18,10 20.35,11.38 20.35,13.62C20.35,15.86 19.31,16.5 17.5,14.67C15.69,12.84 16,9 16,9C16,9 18.66,10 18.66,7C18.66,4 14,0.67 13.5,0.67Z" />
|
||||
</vector>
|
||||
23
app/src/main/res/drawable/ic_nav_trophy.xml
Normal file
23
app/src/main/res/drawable/ic_nav_trophy.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeWidth="2"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:pathData="M18,2H6A2,2 0,0 0,4 4V7C4,8.11 4.9,9 6,9H7V11C7,12.71 8.06,14.2 9.5,14.8V17H8V19H16V17H14.5V14.8C15.94,14.2 17,12.71 17,11V9H18A2,2 0,0 0,20 7V4A2,2 0,0 0,18 2Z" />
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeWidth="2"
|
||||
android:pathData="M6,7V4H7V7" />
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeWidth="2"
|
||||
android:pathData="M17,4V7H18" />
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_search.xml
Normal file
9
app/src/main/res/drawable/ic_search.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_share.xml
Normal file
9
app/src/main/res/drawable/ic_share.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81c1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3s-3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65c0,1.61 1.31,2.92 2.92,2.92c1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
|
||||
</vector>
|
||||
12
app/src/main/res/drawable/ic_sleeping_char.xml
Normal file
12
app/src/main/res/drawable/ic_sleeping_char.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="200dp"
|
||||
android:height="200dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#B39DDB"
|
||||
android:pathData="M12,2A10,10 0,0,0 2,12A10,10 0,0,0 12,22A10,10 0,0,0 22,12A10,10 0,0,0 12,2M12,4A8,8 0,0,1 20,12A8,8 0,0,1 12,20A8,8 0,0,1 4,12A8,8 0,0,1 12,4M8,9.5L10,11.5L8,13.5L9.5,15L11.5,13L13.5,15L15,13.5L13,11.5L15,9.5L13.5,8L11.5,10L9.5,8L8,9.5Z" />
|
||||
<path
|
||||
android:fillColor="#7E57C2"
|
||||
android:pathData="M17,5V7H19V5H17M14,2V4H16V2H14M20,8V10H22V8H20Z" />
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_trophy_bronze.xml
Normal file
9
app/src/main/res/drawable/ic_trophy_bronze.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="120dp"
|
||||
android:height="120dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#CD7F32"
|
||||
android:pathData="M18,2H6A2,2 0,0 0,4 4V7C4,8.11 4.9,9 6,9H7V11C7,12.71 8.06,14.2 9.5,14.8V17H8V19H16V17H14.5V14.8C15.94,14.2 17,12.71 17,11V9H18A2,2 0,0 0,20 7V4A2,2 0,0 0,18 2M6,7V4H7V7H6M18,7H17V4H18V7Z" />
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_trophy_gold.xml
Normal file
9
app/src/main/res/drawable/ic_trophy_gold.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="120dp"
|
||||
android:height="120dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFD700"
|
||||
android:pathData="M18,2H6A2,2 0,0 0,4 4V7C4,8.11 4.9,9 6,9H7V11C7,12.71 8.06,14.2 9.5,14.8V17H8V19H16V17H14.5V14.8C15.94,14.2 17,12.71 17,11V9H18A2,2 0,0 0,20 7V4A2,2 0,0 0,18 2M6,7V4H7V7H6M18,7H17V4H18V7Z" />
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_trophy_silver.xml
Normal file
9
app/src/main/res/drawable/ic_trophy_silver.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="120dp"
|
||||
android:height="120dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#C0C0C0"
|
||||
android:pathData="M18,2H6A2,2 0,0 0,4 4V7C4,8.11 4.9,9 6,9H7V11C7,12.71 8.06,14.2 9.5,14.8V17H8V19H16V17H14.5V14.8C15.94,14.2 17,12.71 17,11V9H18A2,2 0,0 0,20 7V4A2,2 0,0 0,18 2M6,7V4H7V7H6M18,7H17V4H18V7Z" />
|
||||
</vector>
|
||||
8
app/src/main/res/drawable/node_circle_bg.xml
Normal file
8
app/src/main/res/drawable/node_circle_bg.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="@color/white" />
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="@color/border_color" />
|
||||
</shape>
|
||||
27
app/src/main/res/drawable/node_progress_ring.xml
Normal file
27
app/src/main/res/drawable/node_progress_ring.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@android:id/background">
|
||||
<shape
|
||||
android:innerRadiusRatio="1.5"
|
||||
android:shape="ring"
|
||||
android:thickness="4dp"
|
||||
android:useLevel="false">
|
||||
<solid android:color="@color/border_color" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:id="@android:id/progress">
|
||||
<rotate
|
||||
android:fromDegrees="270"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:toDegrees="270">
|
||||
<shape
|
||||
android:innerRadiusRatio="1.5"
|
||||
android:shape="ring"
|
||||
android:thickness="4dp"
|
||||
android:useLevel="true">
|
||||
<solid android:color="@color/primary_purple" />
|
||||
</shape>
|
||||
</rotate>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -1,5 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#CCCCCC" />
|
||||
</shape>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@android:id/background">
|
||||
<shape>
|
||||
<corners android:radius="12dp" />
|
||||
<solid android:color="@color/border_color" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:id="@android:id/progress">
|
||||
<clip>
|
||||
<shape>
|
||||
<corners android:radius="12dp" />
|
||||
<solid android:color="@color/success_green" />
|
||||
</shape>
|
||||
</clip>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="#CCCCCC" />
|
||||
<solid android:color="@color/white" />
|
||||
<stroke
|
||||
android:width="4dp"
|
||||
android:color="@color/primary_purple" />
|
||||
<size
|
||||
android:width="200dp"
|
||||
android:height="200dp" />
|
||||
</shape>
|
||||
180
app/src/main/res/layout/activity_find_friends.xml
Normal file
180
app/src/main/res/layout/activity_find_friends.xml
Normal file
@@ -0,0 +1,180 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout 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_height="match_parent"
|
||||
android:background="@color/background_light"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- Header -->
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:paddingHorizontal="16dp">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnClose"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/ic_close"
|
||||
app:tint="@color/text_primary" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:text="Encontre os seus amigos"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
</RelativeLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<!-- Main Options Cards -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/btnContacts"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="@color/card_background">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:padding="20dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@drawable/ic_contacts"
|
||||
app:tint="@color/primary_purple" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="Escolher nos contactos"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/btnSearchByName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="@color/card_background">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:padding="20dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@drawable/ic_search"
|
||||
app:tint="@color/streak_blue" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="Buscar por nome"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/btnProfileLink"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="@color/card_background">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:padding="20dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@drawable/ic_share"
|
||||
app:tint="@color/success_green" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="Link do seu perfil"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Suggestions Section -->
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Sugestões de amigos"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnViewAll"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:text="VER TODOS"
|
||||
android:textColor="@color/primary_purple"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
</RelativeLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvSuggestions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,12 +1,251 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout 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_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
android:background="@color/background_light">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/card_background"
|
||||
app:elevation="0dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:paddingHorizontal="16dp">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnClose"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/ic_close"
|
||||
app:tint="@color/text_primary" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Streak Activity" />
|
||||
android:layout_centerInParent="true"
|
||||
android:text="Ofensiva"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:tabIndicatorColor="@color/primary_purple"
|
||||
app:tabIndicatorHeight="3dp"
|
||||
app:tabSelectedTextColor="@color/primary_purple"
|
||||
app:tabTextColor="@color/text_secondary">
|
||||
|
||||
<com.google.android.material.tabs.TabItem
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pessoal" />
|
||||
|
||||
<com.google.android.material.tabs.TabItem
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Amigos" />
|
||||
</com.google.android.material.tabs.TabLayout>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<!-- Streak Main State -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginBottom="32dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivBigFlame"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="120dp"
|
||||
android:src="@drawable/ic_flame"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvStreakCount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="7 dias de ofensiva!"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="28sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Estás imparável! Continua assim."
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="16sp"
|
||||
android:layout_marginTop="8dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Calendar Section -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:cardCornerRadius="@dimen/radius_duo"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvMonthName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="abril de 2026"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true">
|
||||
|
||||
<ImageButton
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/ic_back"
|
||||
app:tint="@color/text_secondary" />
|
||||
|
||||
<ImageButton
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:rotation="180"
|
||||
android:src="@drawable/ic_back"
|
||||
app:tint="@color/text_secondary" />
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
<!-- Days of Week Headers -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
<TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="S" android:textAlignment="center" android:textStyle="bold" android:textColor="@color/text_secondary" />
|
||||
<TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="T" android:textAlignment="center" android:textStyle="bold" android:textColor="@color/text_secondary" />
|
||||
<TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Q" android:textAlignment="center" android:textStyle="bold" android:textColor="@color/text_secondary" />
|
||||
<TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Q" android:textAlignment="center" android:textStyle="bold" android:textColor="@color/text_secondary" />
|
||||
<TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="S" android:textAlignment="center" android:textStyle="bold" android:textColor="@color/text_secondary" />
|
||||
<TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="S" android:textAlignment="center" android:textStyle="bold" android:textColor="@color/text_secondary" />
|
||||
<TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="D" android:textAlignment="center" android:textStyle="bold" android:textColor="@color/text_secondary" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvCalendar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:nestedScrollingEnabled="false" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Statistics Grid -->
|
||||
<GridLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:columnCount="2">
|
||||
|
||||
<!-- Days of Practice Card -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Dias de prática"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="12sp" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="12"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="4dp" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Focus Sessions Card -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Sessões de foco"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="12sp" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="24"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="4dp" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
</GridLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
179
app/src/main/res/layout/activity_trophies.xml
Normal file
179
app/src/main/res/layout/activity_trophies.xml
Normal file
@@ -0,0 +1,179 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout 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_height="match_parent"
|
||||
android:background="@color/background_light">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/card_background"
|
||||
app:elevation="0dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:paddingHorizontal="16dp">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnBack"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/ic_back"
|
||||
app:tint="@color/text_primary" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:text="Troféus"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
</RelativeLayout>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<!-- Division Info -->
|
||||
<TextView
|
||||
android:id="@+id/tvDivisionTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Divisão Prata"
|
||||
android:textColor="@color/primary_purple"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTimeRemaining"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="3 dias restantes"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<!-- Trophy Progression Container -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginBottom="32dp"
|
||||
app:cardCornerRadius="@dimen/radius_duo"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="32dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/trophyContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center">
|
||||
|
||||
<!-- Trophies will be added here dynamically or statically -->
|
||||
<ImageView
|
||||
android:id="@+id/trophy1"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_margin="8dp"
|
||||
android:src="@drawable/ic_trophy_bronze" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/trophy2"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:layout_margin="8dp"
|
||||
android:src="@drawable/ic_trophy_silver"
|
||||
android:padding="4dp"
|
||||
android:background="@drawable/circle_bg"
|
||||
android:backgroundTint="#1A6200EE" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/trophy3"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_margin="8dp"
|
||||
android:alpha="0.3"
|
||||
android:src="@drawable/ic_trophy_gold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTrophyProgress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="Faltam 15 dias para o próximo troféu"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Inactive State (Initially Hidden) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/inactiveState"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="150dp"
|
||||
android:src="@drawable/ic_sleeping_char" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Hora de voltar!"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Completa tarefas para competir esta semana."
|
||||
android:textAlignment="center"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Active Message -->
|
||||
<TextView
|
||||
android:id="@+id/tvMotivational"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Estás a progredir bem na Divisão Prata!"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -170,13 +170,17 @@
|
||||
|
||||
<!-- League Card -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/cardLeague"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_margin="6dp"
|
||||
app:cardCornerRadius="@dimen/radius_duo"
|
||||
app:cardElevation="2dp"
|
||||
app:contentPadding="16dp">
|
||||
app:contentPadding="16dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
49
app/src/main/res/layout/item_calendar_day.xml
Normal file
49
app/src/main/res/layout/item_calendar_day.xml
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp">
|
||||
|
||||
<!-- Streak Background (Line connecting days) -->
|
||||
<View
|
||||
android:id="@+id/streakConnector"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@color/streak_blue"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- Active Day Background (Circle) -->
|
||||
<View
|
||||
android:id="@+id/dayBackground"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/circle_bg"
|
||||
|
||||
android:backgroundTint="@color/streak_blue"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- Day Number -->
|
||||
<TextView
|
||||
android:id="@+id/tvDayNumber"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="1"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- Indicator Dot (Optional) -->
|
||||
<View
|
||||
android:id="@+id/dayIndicator"
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="4dp"
|
||||
android:layout_gravity="bottom|center_horizontal"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="@drawable/circle_bg"
|
||||
|
||||
android:backgroundTint="@color/primary_purple"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,41 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dayOfMonth"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="1"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dayOfWeek"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Mon"
|
||||
android:textColor="@android:color/darker_gray"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<View
|
||||
android:id="@+id/topConnector"
|
||||
android:layout_width="2dp"
|
||||
android:layout_height="16dp"
|
||||
android:background="@android:color/darker_gray" />
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="20dp"
|
||||
android:background="@color/border_color" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/nodeContainer"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp">
|
||||
|
||||
<!-- Progress Ring (Outer) -->
|
||||
<ProgressBar
|
||||
android:id="@+id/nodeProgress"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:max="100"
|
||||
android:progress="0"
|
||||
android:progressDrawable="@drawable/node_progress_ring" />
|
||||
|
||||
<!-- Base Circle (Inner) -->
|
||||
<View
|
||||
android:id="@+id/node"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp" />
|
||||
android:id="@+id/nodeCircle"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/node_circle_bg" />
|
||||
|
||||
<!-- Icon or Initial (Optional) -->
|
||||
<TextView
|
||||
android:id="@+id/nodeDayInitial"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="S"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/nodeDayLabel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="SEG"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<View
|
||||
android:id="@+id/bottomConnector"
|
||||
android:layout_width="2dp"
|
||||
android:layout_height="16dp"
|
||||
android:background="@android:color/darker_gray" />
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="20dp"
|
||||
android:background="@color/border_color" />
|
||||
|
||||
</LinearLayout>
|
||||
70
app/src/main/res/layout/item_friend_suggestion.xml
Normal file
70
app/src/main/res/layout/item_friend_suggestion.xml
Normal file
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
app:cardCornerRadius="24dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="@color/card_background">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
app:cardCornerRadius="32dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivFriendAvatar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/ic_nav_profile"
|
||||
app:tint="@color/primary_purple" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvFriendName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Nome"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvFriendInfo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Segue você"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="11sp"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnFollowBack"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="36dp"
|
||||
android:text="Seguir"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="18dp"
|
||||
android:padding="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetBottom="0dp"
|
||||
app:backgroundTint="@color/primary_purple" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
@@ -4,6 +4,10 @@
|
||||
android:id="@+id/nav_inicio"
|
||||
android:icon="@drawable/ic_nav_home"
|
||||
android:title="Início" />
|
||||
<item
|
||||
android:id="@+id/nav_trophies"
|
||||
android:icon="@drawable/ic_nav_trophy"
|
||||
android:title="Troféus" />
|
||||
<item
|
||||
android:id="@+id/nav_profile"
|
||||
android:icon="@drawable/ic_nav_profile"
|
||||
|
||||
18
app/src/main/res/values-night/colors.xml
Normal file
18
app/src/main/res/values-night/colors.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Dark Mode Palette -->
|
||||
<!-- DO NOT override white/black directly if they are used for text on primary colors -->
|
||||
|
||||
<color name="background_light">#0F172A</color> <!-- Main background -->
|
||||
<color name="card_background">#1F2937</color> <!-- Card/Container background -->
|
||||
|
||||
<color name="text_primary">#FFFFFF</color>
|
||||
<color name="text_secondary">#9CA3AF</color>
|
||||
<color name="border_color">#374151</color>
|
||||
|
||||
<color name="background_purple">#0F172A</color>
|
||||
|
||||
<!-- Duolingo-inspired Palette - adjustments for dark mode if needed -->
|
||||
<color name="primary_purple">#7C3AED</color>
|
||||
<color name="primary_purple_dark">#6D28D9</color>
|
||||
</resources>
|
||||
18
app/src/main/res/values-night/themes.xml
Normal file
18
app/src/main/res/values-night/themes.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme for Night mode. -->
|
||||
<style name="Theme.Fluxup" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/primary_purple</item>
|
||||
<item name="colorPrimaryVariant">@color/primary_purple_dark</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/reward_yellow</item>
|
||||
<item name="colorSecondaryVariant">@color/reward_yellow</item>
|
||||
<item name="colorOnSecondary">@color/background_light</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor">@color/background_light</item>
|
||||
<item name="android:windowLightStatusBar" tools:targetApi="m">false</item>
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="android:windowBackground">@color/background_light</item>
|
||||
</style>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user