Compare commits

...

3 Commits

Author SHA1 Message Date
a9746c042c Merge branch 'main' of https://git.epvc.pt/230410/BemPAP into atualizado 2026-01-27 22:28:17 +00:00
9058c57c5b Pap atualizada 2026-01-27 22:23:45 +00:00
b65817f36c janeiro 2026-01-11 12:22:59 +00:00
14 changed files with 681 additions and 1039 deletions

View File

@@ -4,14 +4,27 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-12-02T16:07:28.813132Z"> <DropdownSelection timestamp="2025-12-14T18:12:56.139375400Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/230410/.android/avd/Pixel_9_Pro.avd" /> <DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\lucas\.android\avd\Pixel_9_Pro.avd" />
</handle> </handle>
</Target> </Target>
</DropdownSelection> </DropdownSelection>
<DialogSelection /> <DialogSelection>
<targets>
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\lucas\.android\avd\Pixel_9_Pro.avd" />
</handle>
</Target>
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\lucas\.android\avd\Medium_Phone.avd" />
</handle>
</Target>
</targets>
</DialogSelection>
</SelectionState> </SelectionState>
</selectionStates> </selectionStates>
</component> </component>

1
.idea/misc.xml generated
View File

@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">

View File

@@ -36,6 +36,10 @@
android:name=".MainActivity" android:name=".MainActivity"
android:exported="false" /> android:exported="false" />
<activity
android:name=".GenerateCodeActivity"
android:exported="false" />
<activity <activity
android:name=".InviteCodeActivity" android:name=".InviteCodeActivity"
android:exported="false" /> android:exported="false" />

View File

@@ -0,0 +1,115 @@
package com.example.bem;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
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.HashMap;
import java.util.Map;
import java.util.Random;
public class GenerateCodeActivity extends AppCompatActivity {
private FirebaseFirestore db;
private FirebaseAuth mAuth;
private TextView textCode;
private TextView textTimer;
private Button btnGenerate;
private ProgressBar progressBar;
private CountDownTimer countDownTimer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_generate_code);
db = FirebaseFirestore.getInstance();
mAuth = FirebaseAuth.getInstance();
textCode = findViewById(R.id.text_code);
textTimer = findViewById(R.id.text_timer);
btnGenerate = findViewById(R.id.btn_generate_code);
progressBar = findViewById(R.id.progress_bar);
btnGenerate.setOnClickListener(v -> generateCode());
generateCode();
}
private void generateCode() {
setLoading(true);
String userId = mAuth.getCurrentUser().getUid();
if (userId == null) {
Toast.makeText(this, "Erro: Utilizador não autenticado.", Toast.LENGTH_SHORT).show();
setLoading(false);
return;
}
String code = String.format("%06d", new Random().nextInt(999999));
Map<String, Object> codeData = new HashMap<>();
codeData.put("userId", userId);
codeData.put("createdAt", System.currentTimeMillis());
db.collection("inviteCodes").document(code)
.set(codeData)
.addOnSuccessListener(aVoid -> {
textCode.setText(code);
startTimer();
setLoading(false);
})
.addOnFailureListener(e -> {
Toast.makeText(GenerateCodeActivity.this, "Falha ao gerar o código.", Toast.LENGTH_SHORT).show();
setLoading(false);
});
}
private void startTimer() {
if (countDownTimer != null) {
countDownTimer.cancel();
}
countDownTimer = new CountDownTimer(30000, 1000) {
@Override
public void onTick(long millisUntilFinished) {
textTimer.setText(String.format("Expira em %d segundos", millisUntilFinished / 1000));
}
@Override
public void onFinish() {
textTimer.setText("Código expirado");
textCode.setText("------");
}
}.start();
}
private void setLoading(boolean isLoading) {
if (isLoading) {
progressBar.setVisibility(View.VISIBLE);
btnGenerate.setEnabled(false);
} else {
progressBar.setVisibility(View.GONE);
btnGenerate.setEnabled(true);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (countDownTimer != null) {
countDownTimer.cancel();
}
}
}

View File

@@ -2,44 +2,29 @@ package com.example.bem;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.os.CountDownTimer;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.LinearLayout; import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.firestore.FieldValue; import com.google.firebase.firestore.FieldValue;
import com.google.firebase.firestore.FirebaseFirestore; import com.google.firebase.firestore.FirebaseFirestore;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class InviteCodeActivity extends AppCompatActivity { public class InviteCodeActivity extends AppCompatActivity {
private EditText inputCode;
private Button btnContinue;
private ProgressBar progressBar;
private FirebaseAuth mAuth; private FirebaseAuth mAuth;
private FirebaseFirestore db; private FirebaseFirestore db;
private LinearLayout layoutGenerateCode;
private LinearLayout layoutEnterCode;
private TextView textInviteCode;
private TextView textCountdown;
private TextView textModeDescription;
private Button btnGenerateCode;
private EditText inputInviteCode;
private Button btnValidateCode;
private Button btnBack;
private String currentCode = null;
private CountDownTimer countDownTimer;
private boolean isGuardianMode = false;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@@ -48,201 +33,77 @@ public class InviteCodeActivity extends AppCompatActivity {
mAuth = FirebaseAuth.getInstance(); mAuth = FirebaseAuth.getInstance();
db = FirebaseFirestore.getInstance(); db = FirebaseFirestore.getInstance();
layoutGenerateCode = findViewById(R.id.layoutGenerateCode); inputCode = findViewById(R.id.input_invite_code);
layoutEnterCode = findViewById(R.id.layoutEnterCode); btnContinue = findViewById(R.id.btn_validate_code);
textInviteCode = findViewById(R.id.textInviteCode); progressBar = findViewById(R.id.progress_bar_invite);
textCountdown = findViewById(R.id.textCountdown);
textModeDescription = findViewById(R.id.textModeDescription);
btnGenerateCode = findViewById(R.id.btnGenerateCode);
inputInviteCode = findViewById(R.id.inputInviteCode);
btnValidateCode = findViewById(R.id.btnValidateCode);
btnBack = findViewById(R.id.btnBack);
isGuardianMode = getIntent().getBooleanExtra("is_guardian", false); btnContinue.setOnClickListener(v -> validateInviteCode());
if (isGuardianMode) {
textModeDescription.setText("Insira o código do utilizador para se vincular como responsável");
layoutGenerateCode.setVisibility(View.GONE);
layoutEnterCode.setVisibility(View.VISIBLE);
} else {
textModeDescription.setText("Gere um código temporário para vincular responsável");
layoutGenerateCode.setVisibility(View.VISIBLE);
layoutEnterCode.setVisibility(View.GONE);
generateCode();
} }
btnGenerateCode.setOnClickListener(v -> generateCode()); private void validateInviteCode() {
btnValidateCode.setOnClickListener(v -> validateCode()); String code = inputCode.getText().toString().trim();
btnBack.setOnClickListener(v -> finish()); FirebaseUser currentUser = mAuth.getCurrentUser();
}
private void generateCode() {
if (mAuth.getCurrentUser() == null) {
Toast.makeText(this, "Erro: Não autenticado", Toast.LENGTH_SHORT).show();
return;
}
String code = String.format("%06d", new Random().nextInt(999999));
currentCode = code;
textInviteCode.setText(code);
String userId = mAuth.getCurrentUser().getUid();
Map<String, Object> codeData = new HashMap<>();
codeData.put("code", code);
codeData.put("userId", userId);
codeData.put("createdAt", System.currentTimeMillis());
codeData.put("expiresAt", System.currentTimeMillis() + 30000);
codeData.put("used", false);
db.collection("inviteCodes").document(code)
.set(codeData)
.addOnSuccessListener(aVoid -> {
startCountdown();
Toast.makeText(this, "✓ Código gerado! Válido por 30 segundos", Toast.LENGTH_SHORT).show();
})
.addOnFailureListener(e -> {
Toast.makeText(this, "Erro ao gerar código: " + e.getMessage(), Toast.LENGTH_LONG).show();
});
}
private void startCountdown() {
if (countDownTimer != null) {
countDownTimer.cancel();
}
countDownTimer = new CountDownTimer(30000, 1000) {
@Override
public void onTick(long millisUntilFinished) {
int seconds = (int) (millisUntilFinished / 1000);
textCountdown.setText("Expira em: " + seconds + "s");
if (seconds <= 10) {
textCountdown.setTextColor(getResources().getColor(android.R.color.holo_red_dark));
}
}
@Override
public void onFinish() {
textCountdown.setText("Código expirado");
textInviteCode.setText("------");
currentCode = null;
btnGenerateCode.setEnabled(true);
}
};
countDownTimer.start();
btnGenerateCode.setEnabled(false);
}
private void validateCode() {
String code = inputInviteCode.getText().toString().trim();
if (TextUtils.isEmpty(code) || code.length() != 6) { if (TextUtils.isEmpty(code) || code.length() != 6) {
inputInviteCode.setError("Código deve ter 6 dígitos"); inputCode.setError("O código deve ter 6 dígitos.");
return; return;
} }
if (mAuth.getCurrentUser() == null) { if (currentUser == null) {
Toast.makeText(this, "Erro: Não autenticado", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "Erro: Sessão inválida. Faça login novamente.", Toast.LENGTH_SHORT).show();
return; return;
} }
btnValidateCode.setEnabled(false); setLoading(true);
db.collection("inviteCodes").document(code) // 1. Encontrar o código na coleção 'inviteCodes'
.get() db.collection("inviteCodes").document(code).get().addOnSuccessListener(documentSnapshot -> {
.addOnSuccessListener(document -> { if (!documentSnapshot.exists()) {
if (!document.exists()) { showError("Código inválido ou expirado.");
btnValidateCode.setEnabled(true);
Toast.makeText(this, "❌ Código inválido", Toast.LENGTH_SHORT).show();
return; return;
} }
Boolean used = document.getBoolean("used"); String patientId = documentSnapshot.getString("userId");
Long expiresAt = document.getLong("expiresAt"); if (patientId == null) {
String targetUserId = document.getString("userId"); showError("Erro no código. Tente gerar um novo.");
if (used != null && used) {
btnValidateCode.setEnabled(true);
Toast.makeText(this, "❌ Código já utilizado", Toast.LENGTH_SHORT).show();
return; return;
} }
if (expiresAt != null && System.currentTimeMillis() > expiresAt) { String guardianId = currentUser.getUid();
btnValidateCode.setEnabled(true);
Toast.makeText(this, "❌ Código expirado", Toast.LENGTH_SHORT).show();
return;
}
linkGuardianToUser(code, targetUserId); // 2. Atualizar o perfil do paciente para adicionar o ID do responsável
}) db.collection("users").document(patientId)
.addOnFailureListener(e -> { .update("guardianId", guardianId)
btnValidateCode.setEnabled(true);
Toast.makeText(this, "Erro: " + e.getMessage(), Toast.LENGTH_LONG).show();
});
}
private void linkGuardianToUser(String code, String targetUserId) {
String guardianId = mAuth.getCurrentUser().getUid();
Map<String, Object> linkData = new HashMap<>();
linkData.put("guardianId", guardianId);
linkData.put("linkedAt", System.currentTimeMillis());
// 1. Update the Target User's document (Add guardian to their list)
db.collection("users").document(targetUserId)
.update("guardians", FieldValue.arrayUnion(guardianId))
.addOnSuccessListener(aVoid -> { .addOnSuccessListener(aVoid -> {
// 2. Update the Guardian's document (Add target user to their list) // 3. Atualizar o perfil do responsável para adicionar o ID do paciente
db.collection("users").document(guardianId) db.collection("users").document(guardianId)
.update("managedUsers", FieldValue.arrayUnion(targetUserId)) .update("managedUsers", FieldValue.arrayUnion(patientId))
.addOnSuccessListener(aVoid2 -> { .addOnSuccessListener(aVoid1 -> {
// 3. Mark code as used // 4. Apagar o código de convite para que não seja reutilizado
db.collection("inviteCodes").document(code) db.collection("inviteCodes").document(code).delete();
.update("used", true)
.addOnSuccessListener(aVoid3 -> {
Toast.makeText(this, "✓ Vinculado com sucesso!", Toast.LENGTH_LONG).show();
Toast.makeText(this, "Utilizador associado com sucesso!", Toast.LENGTH_LONG).show();
Intent intent = new Intent(this, MainActivity.class); Intent intent = new Intent(this, MainActivity.class);
intent.setFlags( intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent); startActivity(intent);
finish(); finish();
})
.addOnFailureListener(e -> {
// Even if marking code fails, the link is done.
// But ideally we should handle this. For now, proceeding.
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
});
}) }).addOnFailureListener(e -> showError("Falha ao atualizar o seu perfil. Tente novamente."));
.addOnFailureListener(e -> {
// If updating guardian fails, we should probably revert or warn.
// For MVP/Simple app: Log and warn.
btnValidateCode.setEnabled(true);
Toast.makeText(this, "Erro ao atualizar perfil do responsável: " + e.getMessage(),
Toast.LENGTH_LONG).show();
});
}) }).addOnFailureListener(e -> showError("Falha ao associar. Verifique se o utilizador já tem um responsável."));
.addOnFailureListener(e -> {
btnValidateCode.setEnabled(true); }).addOnFailureListener(e -> showError("Erro ao validar o código. Verifique a sua ligação."));
Toast.makeText(this, "Erro ao vincular: " + e.getMessage(), Toast.LENGTH_LONG).show();
});
} }
@Override private void setLoading(boolean loading) {
protected void onDestroy() { progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
super.onDestroy(); btnContinue.setEnabled(!loading);
if (countDownTimer != null) {
countDownTimer.cancel();
} }
private void showError(String message) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
setLoading(false);
} }
} }

View File

@@ -53,7 +53,6 @@ public class LoginActivity extends AppCompatActivity {
mAuth = FirebaseAuth.getInstance(); mAuth = FirebaseAuth.getInstance();
db = FirebaseFirestore.getInstance(); db = FirebaseFirestore.getInstance();
// Forçar login sempre que abrir a app: faz signOut se já existir sessão
if (mAuth.getCurrentUser() != null) { if (mAuth.getCurrentUser() != null) {
mAuth.signOut(); mAuth.signOut();
} }
@@ -242,13 +241,11 @@ public class LoginActivity extends AppCompatActivity {
.apply(); .apply();
if ("guardian".equals(type)) { if ("guardian".equals(type)) {
// Verifies if guardian is already linked to a user
java.util.List<String> managedUsers = (java.util.List<String>) document.get("managedUsers"); java.util.List<String> managedUsers = (java.util.List<String>) document.get("managedUsers");
if (managedUsers != null && !managedUsers.isEmpty()) { if (managedUsers != null && !managedUsers.isEmpty()) {
redirectToApp(); redirectToApp();
} else { } else {
// If not linked, redirect to enter access code
Intent intent = new Intent(LoginActivity.this, InviteCodeActivity.class); Intent intent = new Intent(LoginActivity.this, InviteCodeActivity.class);
intent.putExtra("is_guardian", true); intent.putExtra("is_guardian", true);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
@@ -259,37 +256,45 @@ public class LoginActivity extends AppCompatActivity {
redirectToApp(); redirectToApp();
} }
} else { } else {
// Se o documento não existir (ex.: registo antigo sem salvar), cria um básico // Se o perfil não existe, cria um com base no modo de login atual.
FirebaseUser user = mAuth.getCurrentUser(); FirebaseUser user = mAuth.getCurrentUser();
String email = user != null ? user.getEmail() : ""; String email = user != null ? user.getEmail() : "";
String name = (email != null && email.contains("@")) ? email.substring(0, email.indexOf("@")) String name = (email != null && email.contains("@")) ? email.substring(0, email.indexOf("@")) : "Utilizador";
: "Utilizador"; String userType = isGuardianMode ? "guardian" : "user";
Map<String, Object> userData = new HashMap<>(); Map<String, Object> userData = new HashMap<>();
userData.put("email", email); userData.put("email", email);
userData.put("name", name); userData.put("name", name);
userData.put("phone", ""); userData.put("phone", "");
userData.put("type", "user"); userData.put("type", userType);
userData.put("createdAt", System.currentTimeMillis()); userData.put("createdAt", System.currentTimeMillis());
db.collection("users").document(uid) db.collection("users").document(uid)
.set(userData) .set(userData)
.addOnSuccessListener(aVoid -> { .addOnSuccessListener(aVoid -> {
prefs.edit() prefs.edit()
.putString("user_type", "user") .putString("user_type", userType)
.putString("user_name", name) .putString("user_name", name)
.apply(); .apply();
if ("guardian".equals(userType)) {
Intent intent = new Intent(LoginActivity.this, InviteCodeActivity.class);
intent.putExtra("is_guardian", true);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
} else {
redirectToApp(); redirectToApp();
}
}) })
.addOnFailureListener(e -> { .addOnFailureListener(e -> {
Toast.makeText(this, "Erro ao criar perfil: " + e.getMessage(), Toast.LENGTH_SHORT) Toast.makeText(this, "Erro ao criar perfil: " + e.getMessage(), Toast.LENGTH_SHORT).show();
.show();
btnLogin.setEnabled(true); btnLogin.setEnabled(true);
}); });
} }
}) })
.addOnFailureListener(e -> { .addOnFailureListener(e -> {
Toast.makeText(this, "Erro ao carregar dados", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "Erro ao carregar dados do utilizador: " + e.getMessage(), Toast.LENGTH_SHORT).show();
btnLogin.setEnabled(true); btnLogin.setEnabled(true);
}); });
} }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="24dp"
android:background="@color/background_surface"
tools:context=".GenerateCodeActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Código de Convite"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="@color/neutral_dark"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Partilhe este código com o seu responsável"/>
<TextView
android:id="@+id/text_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:textSize="48sp"
android:textStyle="bold"
android:textColor="@color/primary"
tools:text="123456"/>
<TextView
android:id="@+id/text_timer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
tools:text="Expira em 30 segundos"/>
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:visibility="gone"/>
<Button
android:id="@+id/btn_generate_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Gerar Novo Código"/>
</LinearLayout>

View File

@@ -1,148 +1,54 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/background_surface"
android:gravity="center" android:gravity="center"
android:orientation="vertical" android:orientation="vertical"
android:padding="32dp"> android:padding="24dp"
android:background="@color/background_surface"
<ImageView tools:context=".InviteCodeActivity">
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginBottom="24dp"
android:src="@android:drawable/ic_lock_lock"
android:tint="@color/guardian_purple_start" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Código de Acesso" android:text="Associar a um Utilizador"
android:textColor="@color/neutral_dark" android:textColor="@color/neutral_dark"
android:textSize="24sp" android:textSize="22sp"
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <TextView
android:id="@+id/textModeDescription" android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:gravity="center" android:gravity="center"
android:text="Gere um código temporário para vincular responsável" android:text="Insira o código de 6 dígitos fornecido pelo utilizador para começar a monitorizar os seus alarmes."
android:textColor="@color/neutral_medium" android:textColor="@color/neutral_medium" />
android:textSize="14sp" />
<LinearLayout
android:id="@+id/layoutGenerateCode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:background="@drawable/bg_guardian_hero"
android:gravity="center"
android:orientation="vertical"
android:padding="32dp"
android:visibility="visible">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Partilhe este código:"
android:textColor="@android:color/white"
android:textSize="14sp" />
<TextView
android:id="@+id/textInviteCode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="------"
android:textColor="@android:color/white"
android:textSize="48sp"
android:textStyle="bold"
android:letterSpacing="0.2" />
<TextView
android:id="@+id/textCountdown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Expira em: 30s"
android:textColor="@color/reminder_info_bg"
android:textSize="16sp"
android:textStyle="bold" />
<Button
android:id="@+id/btnGenerateCode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:backgroundTint="@android:color/white"
android:padding="12dp"
android:text="Gerar Novo Código"
android:textAllCaps="false"
android:textColor="@color/guardian_purple_end"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/layoutEnterCode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:background="@drawable/bg_guardian_card"
android:orientation="vertical"
android:padding="24dp"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Insira o código do utilizador:"
android:textColor="@color/neutral_dark"
android:textSize="16sp"
android:textStyle="bold" />
<EditText <EditText
android:id="@+id/inputInviteCode" android:id="@+id/input_invite_code"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@drawable/bg_search"
android:gravity="center"
android:hint="000000"
android:inputType="number"
android:letterSpacing="0.3"
android:maxLength="6"
android:padding="16dp"
android:textColor="@color/neutral_dark"
android:textSize="32sp"
android:textStyle="bold" />
<Button
android:id="@+id/btnValidateCode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@drawable/bg_new_button"
android:padding="14dp"
android:text="Vincular Responsável"
android:textAllCaps="false"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<Button
android:id="@+id/btnBack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp" android:layout_marginTop="32dp"
android:background="@android:color/transparent" android:hint="Código de 6 dígitos"
android:text="← Voltar" android:inputType="number"
android:textAllCaps="false" android:maxLength="6"
android:textColor="@color/neutral_medium" android:textAlignment="center"
android:textSize="14sp" /> android:textSize="20sp" />
<ProgressBar
android:id="@+id/progress_bar_invite"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:visibility="gone" />
<Button
android:id="@+id/btn_validate_code"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Validar e Associar" />
</LinearLayout> </LinearLayout>

View File

@@ -20,16 +20,16 @@
<ImageView <ImageView
android:id="@+id/profileButton" android:id="@+id/profileButton"
android:layout_width="44dp" android:layout_width="56dp"
android:layout_height="44dp" android:layout_height="56dp"
android:layout_marginStart="16dp" android:layout_marginStart="24dp"
android:layout_marginTop="50dp" android:layout_marginTop="60dp"
android:background="@drawable/bg_avatar" android:background="@drawable/bg_avatar"
android:padding="8dp" android:padding="12dp"
android:src="@mipmap/ic_launcher_round" android:src="@mipmap/ic_launcher_round"
android:contentDescription="Perfil" android:contentDescription="Perfil"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app.layout_constraintTop_toTopOf="parent" />
<LinearLayout <LinearLayout
android:id="@+id/bottomBar" android:id="@+id/bottomBar"

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp"
android:background="@color/background_surface">
<ImageView
android:layout_width="60dp"
android:layout_height="60dp"
android:src="@mipmap/ic_launcher_round"
android:layout_gravity="center_horizontal"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:text="Bem-vindo ao Bem+"
android:textColor="@color/neutral_dark"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:text="Esta aplicação ajuda-o a gerir os seus medicamentos, a manter os seus responsáveis informados e a receber lembretes úteis para o seu dia a dia."
android:textColor="@color/neutral_medium"
android:lineSpacingExtra="4dp"
android:textSize="16sp" />
<Button
android:id="@+id/btnSkipIntro"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="24dp"
android:text="Começar"
android:background="@drawable/bg_new_button"
android:textColor="@android:color/white"
android:paddingHorizontal="32dp"/>
</LinearLayout>

View File

@@ -45,14 +45,6 @@
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="16sp" /> android:textSize="16sp" />
<ImageView
android:id="@+id/btnSettings"
android:layout_width="36dp"
android:layout_height="36dp"
android:background="@drawable/bg_icon_button"
android:padding="8dp"
android:src="@android:drawable/ic_menu_manage"
android:tint="@android:color/white" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
@@ -77,13 +69,6 @@
android:textSize="20sp" android:textSize="20sp"
android:textStyle="bold" /> android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="Crie lembretes para cada medicamento e acompanhe facilmente."
android:textColor="@color/neutral_medium"
android:textSize="14sp" />
</LinearLayout> </LinearLayout>
<Button <Button
@@ -106,68 +91,5 @@
android:orientation="vertical" android:orientation="vertical"
android:paddingBottom="8dp" /> android:paddingBottom="8dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:background="@drawable/bg_reminder_info_card"
android:orientation="vertical"
android:padding="18dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@android:drawable/ic_dialog_info"
android:tint="@color/primary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:text="Como confirmar medicação"
android:textColor="@color/neutral_dark"
android:textSize="17sp"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:lineSpacingExtra="4dp"
android:text="• Toque no botão '✓ Confirmar' de cada alarme quando tomar o medicamento\n\n• O responsável receberá automaticamente a notificação\n\n• O botão ficará marcado como 'Tomado' até amanhã"
android:textColor="@color/neutral_medium"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Personalização de notificações"
android:textColor="@color/neutral_dark"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Configure frequência, tipos de lembretes e painel de controlos parentais."
android:textColor="@color/neutral_medium"
android:textSize="13sp" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View File

@@ -89,13 +89,6 @@
android:textSize="14sp" /> android:textSize="14sp" />
</LinearLayout> </LinearLayout>
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/bg_icon_button"
android:padding="10dp"
android:src="@android:drawable/ic_menu_manage"
android:tint="@android:color/white" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
@@ -206,206 +199,12 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android.layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:text="Relaxe e aproveite o dia" android:text="Relaxe e aproveite o dia"
android:textColor="@color/neutral_medium" android:textColor="@color/neutral_medium"
android:textSize="13sp" /> android:textSize="13sp" />
</LinearLayout> </LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@drawable/bg_reminder_info_card"
android:orientation="vertical"
android:padding="18dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sistema Inteligente"
android:textColor="@color/neutral_dark"
android:textSize="15sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="Recebe lembretes nos momentos certos e dicas baseadas no clima. Configure tudo nas definições."
android:textColor="@color/neutral_medium"
android:textSize="13sp" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Mini-Lembretes Inteligentes baseados no clima"
android:textColor="@color/neutral_dark"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Sistema contextual completo"
android:textColor="@color/neutral_dark"
android:textSize="15sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="vertical"
android:paddingBottom="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Temperatura alta (>25°C):"
android:textColor="@color/neutral_dark"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="• Hidratação: “Beba água regularmente, mesmo sem sede”."
android:textColor="@color/neutral_medium"
android:textSize="13sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="• Proteção: “Sol forte. Fique na sombra entre 11h-16h”."
android:textColor="@color/neutral_medium"
android:textSize="13sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="• Atividade: “Dia quente! Que tal ir à praia?”."
android:textColor="@color/neutral_medium"
android:textSize="13sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="• Conforto: “Vista roupa clara e leve”."
android:textColor="@color/neutral_medium"
android:textSize="13sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Temperatura baixa (&lt;15°C):"
android:textColor="@color/neutral_dark"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="• Conforto: “Dia frio. Ótimo para ficar em casa”."
android:textColor="@color/neutral_medium"
android:textSize="13sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="• Agasalho: “Vista várias camadas de roupa”."
android:textColor="@color/neutral_medium"
android:textSize="13sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="• Bem-estar: “Um chá ou café quente faz bem”."
android:textColor="@color/neutral_medium"
android:textSize="13sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Chuva:"
android:textColor="@color/neutral_dark"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="• Lembrete: “Não se esqueça do guarda-chuva!”."
android:textColor="@color/neutral_medium"
android:textSize="13sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="• Atividade: “Leia um livro ou veja um filme”."
android:textColor="@color/neutral_medium"
android:textSize="13sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Tempo perfeito (15-25°C):"
android:textColor="@color/neutral_dark"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="• Passeio: “Tempo ideal! Faça uma caminhada no parque”."
android:textColor="@color/neutral_medium"
android:textSize="13sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="• Ar fresco: “Abra as janelas para arejar”."
android:textColor="@color/neutral_medium"
android:textSize="13sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Dicas gerais de saúde:"
android:textColor="@color/neutral_dark"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="• Alongamento (10h-11h)."
android:textColor="@color/neutral_medium"
android:textSize="13sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="• Postura e movimento (14h-15h)."
android:textColor="@color/neutral_medium"
android:textSize="13sp" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View File

@@ -5,6 +5,7 @@
<color name="primary">#00B894</color> <color name="primary">#00B894</color>
<color name="accent">#5FE0C5</color> <color name="accent">#5FE0C5</color>
<color name="primary_light_bg">#E6F8F5</color>
<color name="neutral_dark">#1E2A28</color> <color name="neutral_dark">#1E2A28</color>
<color name="neutral_medium">#5E7671</color> <color name="neutral_medium">#5E7671</color>
<color name="background_surface">#EEFFF7</color> <color name="background_surface">#EEFFF7</color>