ja meti o droplist nos medicamentos

This commit is contained in:
2026-04-15 12:33:39 +01:00
parent cbfb87d052
commit 069562ecf3
34 changed files with 1678 additions and 1704 deletions

View File

@@ -45,6 +45,7 @@ dependencies {
implementation 'androidx.navigation:navigation-ui:2.7.7'
// Adiciona a biblioteca para Auth se for do Google ID (credentials)
implementation 'androidx.biometric:biometric:1.1.0'
implementation 'androidx.credentials:credentials:1.5.0'
implementation 'androidx.credentials:credentials-play-services-auth:1.5.0'
//noinspection UseIdentifyId

View File

@@ -12,6 +12,7 @@
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
<application
android:name=".CuidaApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
@@ -58,6 +59,16 @@
<receiver android:name=".services.AlarmReceiver" android:exported="false" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>

View File

@@ -13,8 +13,10 @@ import com.example.cuida.ui.auth.LoginActivity;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.example.cuida.utils.NotificationHelper;
public class MainActivity extends AppCompatActivity {

View File

@@ -12,6 +12,10 @@ import com.example.cuida.R;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import androidx.biometric.BiometricPrompt;
import androidx.biometric.BiometricManager;
import androidx.core.content.ContextCompat;
import java.util.concurrent.Executor;
public class LoginActivity extends AppCompatActivity {
// gvjhbk
@@ -56,6 +60,69 @@ public class LoginActivity extends AppCompatActivity {
binding.forgotPasswordLink.setOnClickListener(v -> {
startActivity(new Intent(this, ForgotPasswordActivity.class));
});
setupBiometrics();
}
private void setupBiometrics() {
BiometricManager biometricManager = BiometricManager.from(this);
int canAuthenticate = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL);
SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);
String savedEmail = prefs.getString("saved_email", null);
String savedPass = prefs.getString("saved_pass", null);
if (canAuthenticate == BiometricManager.BIOMETRIC_SUCCESS && savedEmail != null && savedPass != null) {
binding.biometric_button.setVisibility(android.view.View.VISIBLE);
binding.biometric_button.setOnClickListener(v -> showBiometricPrompt(savedEmail, savedPass));
}
}
private void showBiometricPrompt(String email, String pass) {
Executor executor = ContextCompat.getMainExecutor(this);
BiometricPrompt biometricPrompt = new BiometricPrompt(LoginActivity.this, executor, new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
}
@Override
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
// Perform login with saved credentials
loginWithSavedCredentials(email, pass);
}
@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
}
});
BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setTitle("Autenticação Biométrica")
.setSubtitle("Entre na sua conta usando biometria")
.setNegativeButtonText("Usar Password")
.build();
biometricPrompt.authenticate(promptInfo);
}
private void loginWithSavedCredentials(String email, String pass) {
binding.loginButton.setEnabled(false);
binding.loginButton.setText("A entrar...");
mAuth.signInWithEmailAndPassword(email, pass)
.addOnCompleteListener(this, task -> {
if (task.isSuccessful()) {
startActivity(new Intent(LoginActivity.this, MainActivity.class));
finish();
} else {
binding.loginButton.setEnabled(true);
binding.loginButton.setText(R.string.login_button);
Toast.makeText(this, "Erro no login biométrico. Use a password.", Toast.LENGTH_SHORT).show();
}
});
}
private void login() {
@@ -108,6 +175,12 @@ public class LoginActivity extends AppCompatActivity {
prefs.edit().putBoolean("is_logged_in", true).apply();
prefs.edit().putBoolean("remember_me", rememberMe).apply();
// Save for biometrics if remember me is on
if (rememberMe) {
prefs.edit().putString("saved_email", email).apply();
prefs.edit().putString("saved_pass", password).apply();
}
if (fetchTask.isSuccessful() && fetchTask.getResult() != null
&& fetchTask.getResult().exists()) {

View File

@@ -31,6 +31,10 @@ import android.text.TextWatcher;
import com.example.cuida.data.model.Comprimido;
import android.widget.AdapterView;
import android.widget.Toast;
import com.google.android.material.chip.Chip;
import com.google.android.material.chip.ChipGroup;
import com.google.android.material.button.MaterialButton;
import java.util.Collections;
public class MedicationDialog extends DialogFragment {
@@ -43,7 +47,8 @@ public class MedicationDialog extends DialogFragment {
private EditText editNotes;
private android.widget.RadioButton radioOral, radioTopical, radioInhalatory;
private android.widget.RadioGroup radioGroupRoute;
private TextView textTime;
private ChipGroup chipGroupTimes;
private List<String> selectedTimes = new ArrayList<>();
private Medication medicationToEdit;
private OnMedicationSaveListener listener;
private OnMedicationDeleteListener deleteListener;
@@ -78,7 +83,8 @@ public class MedicationDialog extends DialogFragment {
editName = view.findViewById(R.id.edit_med_name);
recyclerResults = view.findViewById(R.id.recycler_search_results);
editNotes = view.findViewById(R.id.edit_med_notes);
textTime = view.findViewById(R.id.text_med_time);
chipGroupTimes = view.findViewById(R.id.chip_group_times);
MaterialButton btnAddTime = view.findViewById(R.id.btn_add_time);
radioGroupRoute = view.findViewById(R.id.radio_group_route);
radioOral = view.findViewById(R.id.radio_oral);
@@ -124,12 +130,18 @@ public class MedicationDialog extends DialogFragment {
radioInhalatory = view.findViewById(R.id.radio_inhalatory);
// Set up TimePicker
textTime.setOnClickListener(v -> showTimePicker());
btnAddTime.setOnClickListener(v -> showTimePicker());
if (medicationToEdit != null) {
editName.setText(medicationToEdit.name);
editNotes.setText(medicationToEdit.notes);
textTime.setText(medicationToEdit.time);
if (medicationToEdit.time != null && !medicationToEdit.time.isEmpty()) {
String[] times = medicationToEdit.time.split(",\\s*");
for (String t : times) {
if (!t.isEmpty()) selectedTimes.add(t);
}
refreshTimeChips();
}
String dosage = medicationToEdit.dosage;
if (dosage != null) {
@@ -146,14 +158,23 @@ public class MedicationDialog extends DialogFragment {
builder.setTitle("Adicionar Medicamento");
// Default time to current time
Calendar cal = Calendar.getInstance();
updateTimeLabel(cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
String defaultTime = String.format(Locale.getDefault(), "%02d:%02d", cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
selectedTimes.add(defaultTime);
refreshTimeChips();
}
builder.setView(view)
.setPositiveButton("Guardar", (dialog, id) -> {
String name = editName.getText().toString();
String notes = editNotes.getText().toString();
String time = textTime.getText().toString();
// Join times with comma
StringBuilder timeBuilder = new StringBuilder();
for (int i = 0; i < selectedTimes.size(); i++) {
timeBuilder.append(selectedTimes.get(i));
if (i < selectedTimes.size() - 1) timeBuilder.append(", ");
}
String time = timeBuilder.toString();
int selectedId = radioGroupRoute.getCheckedRadioButtonId();
String dosage = "Via não especificada";
@@ -227,25 +248,32 @@ public class MedicationDialog extends DialogFragment {
int hour = cal.get(Calendar.HOUR_OF_DAY);
int minute = cal.get(Calendar.MINUTE);
if (medicationToEdit != null) {
try {
String[] parts = medicationToEdit.time.split(":");
hour = Integer.parseInt(parts[0]);
minute = Integer.parseInt(parts[1]);
} catch (Exception e) {
// Use current time if parsing fails
}
}
TimePickerDialog timePickerDialog = new TimePickerDialog(getContext(),
(view, hourOfDay, minute1) -> updateTimeLabel(hourOfDay, minute1),
(view, hourOfDay, minute1) -> {
String time = String.format(Locale.getDefault(), "%02d:%02d", hourOfDay, minute1);
if (!selectedTimes.contains(time)) {
selectedTimes.add(time);
Collections.sort(selectedTimes);
refreshTimeChips();
}
},
hour, minute, true);
timePickerDialog.show();
}
private void updateTimeLabel(int hourOfDay, int minute) {
String time = String.format(Locale.getDefault(), "%02d:%02d", hourOfDay, minute);
textTime.setText(time);
private void refreshTimeChips() {
if (chipGroupTimes == null) return;
chipGroupTimes.removeAllViews();
for (String time : selectedTimes) {
Chip chip = new Chip(requireContext());
chip.setText(time);
chip.setCloseIconVisible(true);
chip.setOnCloseIconClickListener(v -> {
selectedTimes.remove(time);
refreshTimeChips();
});
chipGroupTimes.addView(chip);
}
}
private void fetchAllMedsOnce() {

View File

@@ -58,36 +58,55 @@ public class MedicationFragment extends Fragment {
MedicationDialog dialog = new MedicationDialog();
dialog.setMedicationToEdit(medication);
dialog.setListener(medicationToSave -> {
// If it's an edit, cancel old alarms first
if (medication != null && medication.time != null) {
String[] oldTimes = medication.time.split(",\\s*");
for (String t : oldTimes) {
if (t.isEmpty()) continue;
try {
int oldId = (medication.name + t).hashCode();
com.example.cuida.utils.AlarmScheduler.cancelAlarm(requireContext(), oldId);
} catch (Exception e) {}
}
}
if (medication == null) {
medicationViewModel.insert(medicationToSave);
} else {
medicationViewModel.update(medicationToSave);
}
try {
String[] timeParts = medicationToSave.time.split(":");
int hour = Integer.parseInt(timeParts[0]);
int minute = Integer.parseInt(timeParts[1]);
String[] times = medicationToSave.time.split(",\\s*");
for (String t : times) {
if (t.isEmpty()) continue;
try {
String[] timeParts = t.split(":");
int hour = Integer.parseInt(timeParts[0]);
int minute = Integer.parseInt(timeParts[1]);
java.util.Calendar calendar = java.util.Calendar.getInstance();
calendar.set(java.util.Calendar.HOUR_OF_DAY, hour);
calendar.set(java.util.Calendar.MINUTE, minute);
calendar.set(java.util.Calendar.SECOND, 0);
java.util.Calendar calendar = java.util.Calendar.getInstance();
calendar.set(java.util.Calendar.HOUR_OF_DAY, hour);
calendar.set(java.util.Calendar.MINUTE, minute);
calendar.set(java.util.Calendar.SECOND, 0);
if (calendar.getTimeInMillis() <= System.currentTimeMillis()) {
calendar.add(java.util.Calendar.DAY_OF_YEAR, 1);
if (calendar.getTimeInMillis() <= System.currentTimeMillis()) {
calendar.add(java.util.Calendar.DAY_OF_YEAR, 1);
}
String title = "Hora do Medicamento";
String msg = "É hora de tomar: " + medicationToSave.name + " (" + medicationToSave.dosage + ")";
int alarmId = (medicationToSave.name + t).hashCode();
com.example.cuida.utils.AlarmScheduler.scheduleAlarm(
requireContext(),
calendar.getTimeInMillis(),
title,
msg,
alarmId);
} catch (Exception e) {
e.printStackTrace();
}
String title = "Hora do Medicamento";
String msg = "É hora de tomar: " + medicationToSave.name + " (" + medicationToSave.dosage + ")";
com.example.cuida.utils.AlarmScheduler.scheduleAlarm(
requireContext(),
calendar.getTimeInMillis(),
title,
msg,
medicationToSave.name.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
dialog.dismiss();
@@ -96,13 +115,18 @@ public class MedicationFragment extends Fragment {
dialog.setDeleteListener(medicationToDelete -> {
medicationViewModel.delete(medicationToDelete);
// Cancel alarm if configured
try {
com.example.cuida.utils.AlarmScheduler.cancelAlarm(
requireContext(),
medicationToDelete.name.hashCode());
} catch (Exception e) {
e.printStackTrace();
// Cancel all alarms for this medication
if (medicationToDelete.time != null) {
String[] times = medicationToDelete.time.split(",\\s*");
for (String t : times) {
if (t.isEmpty()) continue;
try {
int alarmId = (medicationToDelete.name + t).hashCode();
com.example.cuida.utils.AlarmScheduler.cancelAlarm(requireContext(), alarmId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});

View File

@@ -11,6 +11,18 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.Toast;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.pdf.PdfDocument;
import androidx.core.content.FileProvider;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.example.cuida.data.model.Medication;
import com.example.cuida.data.model.Appointment;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
@@ -47,6 +59,8 @@ public class ProfileFragment extends Fragment {
getActivity().finish();
});
binding.buttonExportReport.setOnClickListener(v -> exportMonthlyReport());
return binding.getRoot();
}
@@ -285,9 +299,119 @@ public class ProfileFragment extends Fragment {
dialog.show();
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
private void exportMonthlyReport() {
if (currentUser == null || auth.getCurrentUser() == null) return;
String userId = auth.getCurrentUser().getUid();
Toast.makeText(getContext(), "A gerar relatório...", Toast.LENGTH_SHORT).show();
db.collection("medicamentos").whereEqualTo("userId", userId).get()
.addOnSuccessListener(medSnapshots -> {
List<Medication> meds = new ArrayList<>();
for (com.google.firebase.firestore.DocumentSnapshot doc : medSnapshots) {
meds.add(doc.toObject(Medication.class));
}
db.collection("consultas").whereEqualTo("userId", userId).get()
.addOnSuccessListener(apptSnapshots -> {
List<Appointment> appts = new ArrayList<>();
for (com.google.firebase.firestore.DocumentSnapshot doc : apptSnapshots) {
appts.add(doc.toObject(Appointment.class));
}
generateAndSharePDF(meds, appts);
});
});
}
private void generateAndSharePDF(List<Medication> meds, List<Appointment> appts) {
PdfDocument document = new PdfDocument();
PdfDocument.PageInfo pageInfo = new PdfDocument.PageInfo.Builder(595, 842, 1).create(); // A4 size
PdfDocument.Page page = document.startPage(pageInfo);
Canvas canvas = page.getCanvas();
Paint paint = new Paint();
int y = 50;
paint.setTextSize(24);
paint.setFakeBoldText(true);
canvas.drawText("Relatório Mensal de Saúde - Cuida+", 50, y, paint);
y += 40;
paint.setTextSize(14);
paint.setFakeBoldText(false);
canvas.drawText("Utilizador: " + currentUser.name, 50, y, paint);
y += 20;
canvas.drawText("Data: " + java.text.DateFormat.getDateTimeInstance().format(new java.util.Date()), 50, y, paint);
y += 40;
paint.setFakeBoldText(true);
paint.setTextSize(18);
canvas.drawText("Medicação Atual:", 50, y, paint);
y += 25;
paint.setFakeBoldText(false);
paint.setTextSize(12);
if (meds.isEmpty()) {
canvas.drawText("Nenhuma medicação registada.", 70, y, paint);
y += 20;
} else {
for (Medication m : meds) {
canvas.drawText("- " + m.name + " (" + m.dosage + ") - Horários: " + m.time, 70, y, paint);
y += 20;
if (m.notes != null && !m.notes.isEmpty()) {
paint.setColor(Color.GRAY);
canvas.drawText(" Notas: " + m.notes, 70, y, paint);
paint.setColor(Color.BLACK);
y += 20;
}
if (y > 750) break; // Simple page break check
}
}
y += 20;
paint.setFakeBoldText(true);
paint.setTextSize(18);
canvas.drawText("Consultas Agendadas:", 50, y, paint);
y += 25;
paint.setFakeBoldText(false);
paint.setTextSize(12);
if (appts.isEmpty()) {
canvas.drawText("Nenhuma consulta registada.", 70, y, paint);
} else {
for (Appointment a : appts) {
canvas.drawText("- " + a.type + " em " + a.date + " às " + a.time, 70, y, paint);
y += 20;
canvas.drawText(" Motivo: " + a.reason, 70, y, paint);
y += 25;
if (y > 800) break;
}
}
document.finishPage(page);
File file = new File(requireContext().getCacheDir(), "Relatorio_Saude_Cuida.pdf");
try {
document.writeTo(new FileOutputStream(file));
document.close();
shareFile(file);
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(getContext(), "Erro ao gerar PDF", Toast.LENGTH_SHORT).show();
document.close();
}
}
private void shareFile(File file) {
android.net.Uri uri = FileProvider.getUriForFile(requireContext(),
requireContext().getPackageName() + ".fileprovider", file);
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("application/pdf");
intent.putExtra(Intent.EXTRA_SUBJECT, "Relatório de Saúde Cuida+");
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(intent, "Partilhar Relatório"));
}
}

View File

@@ -199,20 +199,25 @@ public class ScheduleViewModel extends AndroidViewModel {
int hour = Integer.parseInt(timeParts[0]);
int minute = Integer.parseInt(timeParts[1]);
Calendar calendar = Calendar.getInstance();
calendar.set(year, month, day, hour, minute, 0);
// 1 hour before
calendar.add(Calendar.HOUR_OF_DAY, -1);
Calendar baseCal = Calendar.getInstance();
baseCal.set(year, month, day, hour, minute, 0);
if (calendar.getTimeInMillis() > System.currentTimeMillis()) {
String title = "Lembrete de Consulta";
String msg = "A sua consulta é daqui a 1 hora (" + time + ").";
AlarmScheduler.scheduleAlarm(
getApplication(),
calendar.getTimeInMillis(),
title,
msg,
(date + time).hashCode());
// Schedule 24 hours before
Calendar cal24h = (Calendar) baseCal.clone();
cal24h.add(Calendar.DAY_OF_YEAR, -1);
if (cal24h.getTimeInMillis() > System.currentTimeMillis()) {
AlarmScheduler.scheduleAlarm(getApplication(), cal24h.getTimeInMillis(),
"Lembrete de Consulta", "A sua consulta é amanhã às " + time,
(date + time + "24h").hashCode());
}
// Schedule 30 minutes before
Calendar cal30m = (Calendar) baseCal.clone();
cal30m.add(Calendar.MINUTE, -30);
if (cal30m.getTimeInMillis() > System.currentTimeMillis()) {
AlarmScheduler.scheduleAlarm(getApplication(), cal30m.getTimeInMillis(),
"Lembrete de Consulta", "A sua consulta é daqui a 30 minutos (" + time + ")",
(date + time + "30m").hashCode());
}
} catch (Exception e) {
e.printStackTrace();

View File

@@ -13,6 +13,10 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.example.cuida.databinding.FragmentSns24Binding;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.FirebaseFirestore;
import java.util.HashMap;
import java.util.Map;
public class Sns24Fragment extends Fragment {
@@ -85,6 +89,9 @@ public class Sns24Fragment extends Fragment {
startActivity(mapIntent);
});
}
// Guardar no Histórico do Firestore
saveTriageToHistory(symptoms, displayResult);
});
}
}
@@ -101,6 +108,26 @@ public class Sns24Fragment extends Fragment {
});
}
private void saveTriageToHistory(String symptoms, String result) {
FirebaseAuth auth = FirebaseAuth.getInstance();
if (auth.getCurrentUser() == null) return;
Map<String, Object> triage = new HashMap<>();
triage.put("userId", auth.getCurrentUser().getUid());
triage.put("symptoms", symptoms);
triage.put("result", result);
triage.put("timestamp", com.google.firebase.Timestamp.now());
FirebaseFirestore.getInstance().collection("triagens")
.add(triage)
.addOnSuccessListener(documentReference -> {
// Histórico guardado com sucesso
})
.addOnFailureListener(e -> {
// Falha ao guardar histórico
});
}
@Override
public void onDestroyView() {
super.onDestroyView();

View File

@@ -86,7 +86,18 @@
android:text="@string/login_button"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="16dp"/>
android:layout_marginBottom="8dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/biometric_button"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Login com Biometria"
android:icon="@android:drawable/ic_dialog_info"
android:layout_marginBottom="16dp"
android:visibility="gone"/>
<TextView
android:id="@+id/forgot_password_link"

View File

@@ -29,36 +29,38 @@
android:inputType="textCapWords" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:text="Horário"
android:layout_width="wrap_content"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14sp"
android:layout_marginBottom="4dp"/>
<!-- O resto do conteúdo do diálogo continua aqui em baixo -->
</LinearLayout>
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="4dp">
<TextView
android:text="Horários"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="14sp"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_search_results"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginTop="60dp"
android:visibility="gone"
android:elevation="8dp"
android:background="@drawable/bg_search_results" />
</FrameLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_add_time"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Adicionar"
android:padding="0dp"
android:minWidth="0dp"
android:minHeight="0dp"/>
</LinearLayout>
<com.google.android.material.chip.ChipGroup
android:id="@+id/chip_group_times"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:padding="4dp"/>
<TextView
android:id="@+id/text_med_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="08:00"
android:textSize="18sp"
android:padding="12dp"
android:background="#E0E0E0"
android:gravity="center"
android:layout_marginBottom="16dp"/>
<TextView
android:text="Via de Administração"

View File

@@ -115,6 +115,17 @@
android:layout_marginBottom="16dp"
android:backgroundTint="@color/secondary_color"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/button_export_report"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="60dp"
android:text="Exportar Relatório Mensal"
android:layout_marginBottom="16dp"
app:strokeColor="@color/secondary_color"
android:textColor="@color/secondary_color"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/button_logout"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path name="my_cache" path="." />
<external-path name="my_external" path="." />
</paths>