Compare commits

...

1 Commits

Author SHA1 Message Date
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>
<SelectionState runConfigName="app">
<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">
<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>
</Target>
</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>
</selectionStates>
</component>

1
.idea/misc.xml generated
View File

@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<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:exported="false" />
<activity
android:name=".GenerateCodeActivity"
android:exported="false" />
<activity
android:name=".InviteCodeActivity"
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.os.Bundle;
import android.os.CountDownTimer;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.ProgressBar;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.firestore.FieldValue;
import com.google.firebase.firestore.FirebaseFirestore;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class InviteCodeActivity extends AppCompatActivity {
private EditText inputCode;
private Button btnContinue;
private ProgressBar progressBar;
private FirebaseAuth mAuth;
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
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -48,201 +33,77 @@ public class InviteCodeActivity extends AppCompatActivity {
mAuth = FirebaseAuth.getInstance();
db = FirebaseFirestore.getInstance();
layoutGenerateCode = findViewById(R.id.layoutGenerateCode);
layoutEnterCode = findViewById(R.id.layoutEnterCode);
textInviteCode = findViewById(R.id.textInviteCode);
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);
inputCode = findViewById(R.id.input_invite_code);
btnContinue = findViewById(R.id.btn_validate_code);
progressBar = findViewById(R.id.progress_bar_invite);
isGuardianMode = getIntent().getBooleanExtra("is_guardian", false);
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());
btnValidateCode.setOnClickListener(v -> validateCode());
btnBack.setOnClickListener(v -> finish());
btnContinue.setOnClickListener(v -> validateInviteCode());
}
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();
private void validateInviteCode() {
String code = inputCode.getText().toString().trim();
FirebaseUser currentUser = mAuth.getCurrentUser();
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;
}
if (mAuth.getCurrentUser() == null) {
Toast.makeText(this, "Erro: Não autenticado", Toast.LENGTH_SHORT).show();
if (currentUser == null) {
Toast.makeText(this, "Erro: Sessão inválida. Faça login novamente.", Toast.LENGTH_SHORT).show();
return;
}
btnValidateCode.setEnabled(false);
setLoading(true);
db.collection("inviteCodes").document(code)
.get()
.addOnSuccessListener(document -> {
if (!document.exists()) {
btnValidateCode.setEnabled(true);
Toast.makeText(this, "❌ Código inválido", Toast.LENGTH_SHORT).show();
return;
}
// 1. Encontrar o código na coleção 'inviteCodes'
db.collection("inviteCodes").document(code).get().addOnSuccessListener(documentSnapshot -> {
if (!documentSnapshot.exists()) {
showError("Código inválido ou expirado.");
return;
}
Boolean used = document.getBoolean("used");
Long expiresAt = document.getLong("expiresAt");
String targetUserId = document.getString("userId");
String patientId = documentSnapshot.getString("userId");
if (patientId == null) {
showError("Erro no código. Tente gerar um novo.");
return;
}
if (used != null && used) {
btnValidateCode.setEnabled(true);
Toast.makeText(this, "❌ Código já utilizado", Toast.LENGTH_SHORT).show();
return;
}
String guardianId = currentUser.getUid();
if (expiresAt != null && System.currentTimeMillis() > expiresAt) {
btnValidateCode.setEnabled(true);
Toast.makeText(this, "❌ Código expirado", Toast.LENGTH_SHORT).show();
return;
}
// 2. Atualizar o perfil do paciente para adicionar o ID do responsável
db.collection("users").document(patientId)
.update("guardianId", guardianId)
.addOnSuccessListener(aVoid -> {
linkGuardianToUser(code, targetUserId);
})
.addOnFailureListener(e -> {
btnValidateCode.setEnabled(true);
Toast.makeText(this, "Erro: " + e.getMessage(), Toast.LENGTH_LONG).show();
});
// 3. Atualizar o perfil do responsável para adicionar o ID do paciente
db.collection("users").document(guardianId)
.update("managedUsers", FieldValue.arrayUnion(patientId))
.addOnSuccessListener(aVoid1 -> {
// 4. Apagar o código de convite para que não seja reutilizado
db.collection("inviteCodes").document(code).delete();
Toast.makeText(this, "Utilizador associado com sucesso!", Toast.LENGTH_LONG).show();
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 -> showError("Falha ao associar. Verifique se o utilizador já tem um responsável."));
}).addOnFailureListener(e -> showError("Erro ao validar o código. Verifique a sua ligação."));
}
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 -> {
// 2. Update the Guardian's document (Add target user to their list)
db.collection("users").document(guardianId)
.update("managedUsers", FieldValue.arrayUnion(targetUserId))
.addOnSuccessListener(aVoid2 -> {
// 3. Mark code as used
db.collection("inviteCodes").document(code)
.update("used", true)
.addOnSuccessListener(aVoid3 -> {
Toast.makeText(this, "✓ Vinculado com sucesso!", Toast.LENGTH_LONG).show();
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
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 -> {
// 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 -> {
btnValidateCode.setEnabled(true);
Toast.makeText(this, "Erro ao vincular: " + e.getMessage(), Toast.LENGTH_LONG).show();
});
private void setLoading(boolean loading) {
progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
btnContinue.setEnabled(!loading);
}
@Override
protected void onDestroy() {
super.onDestroy();
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();
db = FirebaseFirestore.getInstance();
// Forçar login sempre que abrir a app: faz signOut se já existir sessão
if (mAuth.getCurrentUser() != null) {
mAuth.signOut();
}
@@ -242,13 +241,11 @@ public class LoginActivity extends AppCompatActivity {
.apply();
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");
if (managedUsers != null && !managedUsers.isEmpty()) {
redirectToApp();
} else {
// If not linked, redirect to enter access code
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);
@@ -259,37 +256,45 @@ public class LoginActivity extends AppCompatActivity {
redirectToApp();
}
} 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();
String email = user != null ? user.getEmail() : "";
String name = (email != null && email.contains("@")) ? email.substring(0, email.indexOf("@"))
: "Utilizador";
String name = (email != null && email.contains("@")) ? email.substring(0, email.indexOf("@")) : "Utilizador";
String userType = isGuardianMode ? "guardian" : "user";
Map<String, Object> userData = new HashMap<>();
userData.put("email", email);
userData.put("name", name);
userData.put("phone", "");
userData.put("type", "user");
userData.put("type", userType);
userData.put("createdAt", System.currentTimeMillis());
db.collection("users").document(uid)
.set(userData)
.addOnSuccessListener(aVoid -> {
prefs.edit()
.putString("user_type", "user")
.putString("user_type", userType)
.putString("user_name", name)
.apply();
redirectToApp();
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();
}
})
.addOnFailureListener(e -> {
Toast.makeText(this, "Erro ao criar perfil: " + e.getMessage(), Toast.LENGTH_SHORT)
.show();
Toast.makeText(this, "Erro ao criar perfil: " + e.getMessage(), Toast.LENGTH_SHORT).show();
btnLogin.setEnabled(true);
});
}
})
.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);
});
}

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"?>
<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:background="@color/background_surface"
android:gravity="center"
android:orientation="vertical"
android:padding="32dp">
<ImageView
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" />
android:padding="24dp"
android:background="@color/background_surface"
tools:context=".InviteCodeActivity">
<TextView
android:layout_width="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:textSize="24sp"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:id="@+id/textModeDescription"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:text="Gere um código temporário para vincular responsável"
android:textColor="@color/neutral_medium"
android:textSize="14sp" />
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" />
<LinearLayout
android:id="@+id/layoutGenerateCode"
<EditText
android:id="@+id/input_invite_code"
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">
android:hint="Código de 6 dígitos"
android:inputType="number"
android:maxLength="6"
android:textAlignment="center"
android:textSize="20sp" />
<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
android:id="@+id/inputInviteCode"
android:layout_width="match_parent"
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"
<ProgressBar
android:id="@+id/progress_bar_invite"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:background="@android:color/transparent"
android:text="← Voltar"
android:textAllCaps="false"
android:textColor="@color/neutral_medium"
android:textSize="14sp" />
</LinearLayout>
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>

View File

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

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: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>
@@ -77,13 +69,6 @@
android:textSize="20sp"
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>
<Button
@@ -106,68 +91,5 @@
android:orientation="vertical"
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>
</androidx.core.widget.NestedScrollView>
</androidx.core.widget.NestedScrollView>

View File

@@ -89,13 +89,6 @@
android:textSize="14sp" />
</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>
@@ -206,206 +199,12 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android.layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Relaxe e aproveite o dia"
android:textColor="@color/neutral_medium"
android:textSize="13sp" />
</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>
</androidx.core.widget.NestedScrollView>
</androidx.core.widget.NestedScrollView>

View File

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