commit 0c54f9a9cc57737bea4cec5a3d990bc410554fdd
Author: 230410 <230410@epvc.pt>
Date: Fri Dec 12 10:22:34 2025 +0000
first commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..092cff1
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+Bem+
\ No newline at end of file
diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml
new file mode 100644
index 0000000..4a53bee
--- /dev/null
+++ b/.idea/AndroidProjectSystem.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..b86273d
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
new file mode 100644
index 0000000..afe9e82
--- /dev/null
+++ b/.idea/deploymentTargetSelector.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml
new file mode 100644
index 0000000..91f9558
--- /dev/null
+++ b/.idea/deviceManager.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..639c779
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/migrations.xml b/.idea/migrations.xml
new file mode 100644
index 0000000..f8051a6
--- /dev/null
+++ b/.idea/migrations.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..74dd639
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..16660f1
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/studiobot.xml b/.idea/studiobot.xml
new file mode 100644
index 0000000..539e3b8
--- /dev/null
+++ b/.idea/studiobot.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ADICIONAR_LOGO.md b/ADICIONAR_LOGO.md
new file mode 100644
index 0000000..ddcd882
--- /dev/null
+++ b/ADICIONAR_LOGO.md
@@ -0,0 +1,119 @@
+# 🎨 Como Adicionar o Logo Bem+ à App
+
+## 📍 Passo 1: Preparar a Imagem do Logo
+
+Precisa do logo em **diferentes tamanhos** para Android:
+
+### Tamanhos necessários:
+- **mdpi**: 48x48 px
+- **hdpi**: 72x72 px
+- **xhdpi**: 96x96 px
+- **xxhdpi**: 144x144 px
+- **xxxhdpi**: 192x192 px
+
+### 🔧 Opção A: Gerar automaticamente (RECOMENDADO)
+
+1. Vá para [icon.kitchen](https://icon.kitchen) ou [appicon.co](https://appicon.co)
+2. Faça upload da imagem do logo Bem+
+3. Escolha **"Android"**
+4. Download do pacote ZIP com todos os tamanhos
+5. Descompacte o ZIP
+
+### 🔧 Opção B: Android Studio (MAIS FÁCIL)
+
+1. Abra o projeto no Android Studio
+2. Clique direito em `app/src/main/res`
+3. **"New"** → **"Image Asset"**
+4. **"Icon Type"**: Launcher Icons (Adaptive and Legacy)
+5. **"Path"**: Selecione a imagem do logo
+6. **"Name"**: `ic_launcher`
+7. Clique **"Next"** → **"Finish"**
+8. ✅ Android Studio gera TODOS os tamanhos automaticamente!
+
+---
+
+## 📍 Passo 2: Colocar os Ficheiros nas Pastas Corretas
+
+### Se usou ferramentas online (Opção A):
+
+Copie os ficheiros para:
+
+```
+/Users/230410/AndroidStudioProjects/Bem/app/src/main/res/
+
+mipmap-mdpi/
+ └── ic_launcher.png (48x48)
+
+mipmap-hdpi/
+ └── ic_launcher.png (72x72)
+
+mipmap-xhdpi/
+ └── ic_launcher.png (96x96)
+
+mipmap-xxhdpi/
+ └── ic_launcher.png (144x144)
+
+mipmap-xxxhdpi/
+ └── ic_launcher.png (192x192)
+```
+
+### Também substitua os ícones redondos:
+
+```
+mipmap-mdpi/
+ └── ic_launcher_round.png
+
+mipmap-hdpi/
+ └── ic_launcher_round.png
+
+(... mesmos tamanhos que acima)
+```
+
+---
+
+## 📍 Passo 3: Logo para usar DENTRO da App
+
+Para mostrar o logo nas telas internas:
+
+1. Pegue a imagem em **alta resolução** (pelo menos 512x512)
+2. Renomeie para: `logo_bem.png`
+3. Cole em:
+ ```
+ /Users/230410/AndroidStudioProjects/Bem/app/src/main/res/drawable/logo_bem.png
+ ```
+
+---
+
+## ✅ Verificar no Terminal
+
+Depois de adicionar, execute:
+
+```bash
+cd /Users/230410/AndroidStudioProjects/Bem
+ls -la app/src/main/res/mipmap-xhdpi/ic_launcher.png
+ls -la app/src/main/res/drawable/logo_bem.png
+```
+
+Se os ficheiros aparecerem, está tudo correto! ✓
+
+---
+
+## 🚀 Já Preparei o Código!
+
+Quando adicionar as imagens, a app VAI USAR AUTOMATICAMENTE:
+
+### Nas telas internas:
+- ✅ Tela de login
+- ✅ Cabeçalho da aba Alarmes
+- ✅ Activity de alarme a tocar
+- ✅ Diálogos
+
+### Como ícone da app:
+- ✅ Ecrã principal (launcher)
+- ✅ Notificações
+- ✅ Recentes/multitarefa
+
+**Não precisa programar nada!** Só adicionar as imagens. 🎉
+
+
+
diff --git a/COMO_USAR.md b/COMO_USAR.md
new file mode 100644
index 0000000..61ca3b8
--- /dev/null
+++ b/COMO_USAR.md
@@ -0,0 +1,172 @@
+# 📱 Bem+ - Guia de Utilização Completo
+
+## 🎯 Visão Geral
+
+A app **Bem+** é um sistema completo de gestão de medicação para idosos com controlo parental integrado.
+
+---
+
+## 👤 PARA UTILIZADORES (Idosos)
+
+### 1️⃣ Primeiro Acesso
+
+1. Abra a app
+2. Toque em **"Registar"**
+3. Preencha:
+ - Nome completo
+ - Email
+ - Palavra-passe (mínimo 6 caracteres)
+4. Toque em **"Criar Conta"**
+
+### 2️⃣ Criar Alarmes
+
+1. Na aba **"Alarmes"** (sino verde)
+2. Toque no botão **"+ Novo"**
+3. Configure:
+ - Nome do medicamento (ex: Aspirina)
+ - Horário (toque para escolher com picker)
+ - Dosagem (ex: 100mg • Segunda a Sexta)
+4. Toque **"Guardar"**
+5. ✅ Alarme criado e agendado!
+
+### 3️⃣ Editar Alarmes
+
+Toque no ícone do **lápis** ✏️ em qualquer alarme:
+
+- **⏰ Alterar horário** - Mude a hora
+- **🔔 Escolher toque** - Escolha som dos toques do telemóvel
+- **🎵 Testar toque atual** - Ouve 5 segundos de pré-visualização
+- **📝 Editar título** - Renomeie o medicamento
+
+### 4️⃣ Quando o Alarme Toca
+
+O ecrã liga automaticamente e mostra:
+
+- 💊 Nome do medicamento
+- 🕐 Hora atual
+- **⏰ Adiar 5min** - Toca novamente em 5 minutos
+- **✓ Parar** - Para o alarme
+
+### 5️⃣ Confirmar que Tomou
+
+Em cada alarme, toque no botão **"✓ Confirmar"**:
+- Regista a hora exata
+- Notifica o responsável automaticamente
+- Botão muda para **"✓ Tomado"** (cinza)
+- Reseta amanhã
+
+### 6️⃣ Adicionar Responsável
+
+1. Toque no ícone de **⚙️ Configurações** (canto superior direito)
+2. Escolha **"🎟️ Gerar código para responsável"**
+3. Partilhe o código de **6 dígitos** com o responsável
+4. ⏱️ **Válido por 30 segundos!**
+
+---
+
+## 👨⚕️ PARA RESPONSÁVEIS
+
+### 1️⃣ Criar Conta de Responsável
+
+1. Na tela de login, toque em **"🔒 Sou Responsável"**
+2. Toque em **"Registar"**
+3. Preencha:
+ - Nome completo
+ - Email
+ - Palavra-passe
+ - Número de telemóvel
+4. Toque em **"Criar Conta"**
+
+### 2️⃣ Vincular a um Utilizador
+
+Após criar conta:
+
+1. Peça ao utilizador para gerar um código
+2. Na tela de código, insira o código de **6 dígitos**
+3. Toque **"Vincular Responsável"**
+4. ✅ Vinculado com sucesso!
+
+### 3️⃣ Monitorizar Medicação
+
+Aceda à aba **"Responsáveis"** (escudo roxo):
+
+**Dashboard em tempo real:**
+- **Tomados**: Quantos medicamentos foram confirmados hoje
+- **Pendentes**: Quantos ainda faltam
+- **Adesão**: Percentagem de cumprimento
+
+**Lista detalhada:**
+- ✅ **"✓ Tomado às 14:32"** (verde) - Confirmado
+- ⚠️ **"⚠ Pendente"** (vermelho) - Ainda não tomou
+
+---
+
+## 🔐 Segurança
+
+### Recuperar Palavra-passe
+
+1. Na tela de login, toque em **"Esqueci-me da palavra-passe"**
+2. Insira o email
+3. Toque **"Enviar"**
+4. Verifique o email com link de recuperação
+
+### Múltiplos Responsáveis
+
+- Um utilizador pode ter **vários responsáveis**
+- Cada responsável vê os mesmos dados
+- Todos recebem notificações de confirmação
+
+### Códigos Temporários
+
+- ⏱️ Válidos por **30 segundos**
+- 🔒 Uso único (não pode ser reutilizado)
+- 🎲 Aleatórios de 6 dígitos
+- ♻️ Pode gerar novos códigos ilimitadamente
+
+---
+
+## 🎨 Abas da App
+
+### 🔔 Alarmes
+- Criar, editar e apagar alarmes
+- Confirmar tomada de medicamentos
+- Escolher toques personalizados
+
+### 💚 Lembretes
+- Dicas inteligentes baseadas no clima
+- Sugestões de atividades
+- Sistema contextual automático
+
+### 🛡️ Responsáveis (Controlo Parental)
+- Monitorização em tempo real
+- Histórico de medicações
+- Estatísticas de adesão
+- **Só acessível por responsáveis autenticados!**
+
+---
+
+## ⚡ Dicas
+
+- 🔊 **Toques tocam MUITO ALTO** - Use volume máximo do canal de alarme
+- 🔋 **Vibração otimizada** - Padrão intermitente poupa bateria
+- 📱 **Funciona mesmo bloqueado** - Ecrã liga automaticamente
+- ☁️ **Dados na cloud** - Firebase sincroniza tudo
+
+---
+
+## 🆘 Problemas Comuns
+
+**"Alarme não toca"**
+→ Verifique permissões de notificação e alarmes exatos
+
+**"Código expirou"**
+→ São só 30 segundos! Gere novo código
+
+**"Não consigo entrar no Controlo Parental"**
+→ Só responsáveis registados têm acesso
+
+**"Esqueci-me da palavra-passe"**
+→ Use "Recuperar palavra-passe" com o email
+
+
+
diff --git a/FIREBASE_SETUP.md b/FIREBASE_SETUP.md
new file mode 100644
index 0000000..f7b1ad5
--- /dev/null
+++ b/FIREBASE_SETUP.md
@@ -0,0 +1,93 @@
+# 🔥 Configuração do Firebase para Bem+
+
+## Passo 1: Criar Projeto no Firebase Console
+
+1. Aceda a [console.firebase.google.com](https://console.firebase.google.com)
+2. Clique em **"Adicionar projeto"**
+3. Nome do projeto: **Bem Plus** (ou outro à escolha)
+4. Desative Google Analytics (opcional)
+5. Clique em **"Criar projeto"**
+
+## Passo 2: Adicionar App Android
+
+1. No painel do projeto, clique no ícone **Android** (robô verde)
+2. **Nome do pacote**: `com.example.bem`
+3. **Nome da app**: `Bem+`
+4. Clique em **"Registar app"**
+
+## Passo 3: Transferir google-services.json
+
+1. Faça download do ficheiro **google-services.json**
+2. Cole o ficheiro em:
+ ```
+ /Users/230410/AndroidStudioProjects/Bem/app/google-services.json
+ ```
+3. **IMPORTANTE**: O ficheiro DEVE estar dentro da pasta `app/`
+
+## Passo 4: Ativar Authentication
+
+1. No menu lateral, clique em **"Authentication"**
+2. Clique em **"Começar"**
+3. Ative o método: **"Email/Password"**
+4. Clique em **"Ativar"** e depois **"Guardar"**
+
+## Passo 5: Ativar Firestore Database
+
+1. No menu lateral, clique em **"Firestore Database"**
+2. Clique em **"Criar base de dados"**
+3. Selecione **"Modo de teste"** (permite leitura/escrita por 30 dias)
+4. Localização: **"europe-west1"** (Frankfurt - mais próximo de Portugal)
+5. Clique em **"Ativar"**
+
+## Passo 6: Configurar Regras de Segurança (Opcional mas recomendado)
+
+No Firestore, vá para **"Regras"** e cole:
+
+```javascript
+rules_version = '2';
+service cloud.firestore {
+ match /databases/{database}/documents {
+ match /users/{userId} {
+ allow read, write: if request.auth != null && request.auth.uid == userId;
+ allow read: if request.auth != null &&
+ request.auth.uid in resource.data.guardians;
+ }
+
+ match /inviteCodes/{code} {
+ allow read, write: if request.auth != null;
+ allow delete: if false;
+ }
+ }
+}
+```
+
+Clique em **"Publicar"**.
+
+## Passo 7: Compilar a App
+
+Após seguir TODOS os passos acima:
+
+```bash
+cd /Users/230410/AndroidStudioProjects/Bem
+./gradlew assembleDebug
+```
+
+## ✅ Verificação
+
+Se tudo estiver correto:
+- ✅ Ficheiro `google-services.json` na pasta `app/`
+- ✅ Authentication ativo com Email/Password
+- ✅ Firestore Database criado
+- ✅ Build sem erros
+
+## 🚀 Pronto!
+
+A app agora tem:
+- 🔐 Login/Registo de utilizadores
+- 👥 Login de responsáveis
+- 🎟️ Códigos temporários de 30 segundos
+- 🔄 Recuperação de palavra-passe
+- 📊 Painel de monitorização protegido
+
+
+
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 0000000..8db86d3
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,51 @@
+plugins {
+ alias(libs.plugins.android.application)
+ id("com.google.gms.google-services")
+}
+
+android {
+ namespace = "com.example.bem"
+ compileSdk = 36
+
+ defaultConfig {
+ applicationId = "com.example.bem"
+ minSdk = 24
+ targetSdk = 36
+ versionCode = 1
+ versionName = "1.0"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+}
+
+dependencies {
+
+ implementation(libs.appcompat)
+ implementation(libs.material)
+ implementation(libs.activity)
+ implementation(libs.constraintlayout)
+
+ // Firebase
+ implementation(platform("com.google.firebase:firebase-bom:32.7.0"))
+ implementation("com.google.firebase:firebase-auth")
+ implementation("com.google.firebase:firebase-firestore")
+ implementation("com.google.firebase:firebase-analytics")
+
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.ext.junit)
+ androidTestImplementation(libs.espresso.core)
+}
\ No newline at end of file
diff --git a/app/google-services.json b/app/google-services.json
new file mode 100644
index 0000000..3a8ec62
--- /dev/null
+++ b/app/google-services.json
@@ -0,0 +1,29 @@
+{
+ "project_info": {
+ "project_number": "785325517224",
+ "project_id": "bem-firebase",
+ "storage_bucket": "bem-firebase.firebasestorage.app"
+ },
+ "client": [
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:785325517224:android:705886336a3809fc3f159d",
+ "android_client_info": {
+ "package_name": "com.example.bem"
+ }
+ },
+ "oauth_client": [],
+ "api_key": [
+ {
+ "current_key": "AIzaSyAe0XbtC_eQY93MWyWzu8I_d1ALOZkBDgQ"
+ }
+ ],
+ "services": {
+ "appinvite_service": {
+ "other_platform_oauth_client": []
+ }
+ }
+ }
+ ],
+ "configuration_version": "1"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/example/bem/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/example/bem/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..c1d5ddc
--- /dev/null
+++ b/app/src/androidTest/java/com/example/bem/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.example.bem;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ assertEquals("com.example.bem", appContext.getPackageName());
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..98c733b
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png
new file mode 100644
index 0000000..2adebc2
Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ
diff --git a/app/src/main/java/com/example/bem/AlarmReceiver.java b/app/src/main/java/com/example/bem/AlarmReceiver.java
new file mode 100644
index 0000000..26fdf3b
--- /dev/null
+++ b/app/src/main/java/com/example/bem/AlarmReceiver.java
@@ -0,0 +1,119 @@
+package com.example.bem;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Build;
+
+import androidx.core.app.NotificationCompat;
+
+public class AlarmReceiver extends BroadcastReceiver {
+
+ private static final String CHANNEL_ID = "alarm_channel";
+ private static final int NOTIFICATION_ID = 1001;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String title = intent.getStringExtra("alarm_title");
+ String message = intent.getStringExtra("alarm_message");
+ String ringtoneUriString = intent.getStringExtra("ringtone_uri");
+ int alarmId = intent.getIntExtra("alarm_id", -1);
+
+ if (title == null) {
+ title = "Alarme de Medicamento";
+ }
+ if (message == null) {
+ message = "Hora de tomar o seu medicamento!";
+ }
+
+ Intent activityIntent = new Intent(context, AlarmRingingActivity.class);
+ activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_CLEAR_TOP |
+ Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ activityIntent.putExtra("alarm_title", title);
+ activityIntent.putExtra("alarm_time", new java.text.SimpleDateFormat("HH:mm", java.util.Locale.getDefault()).format(new java.util.Date()));
+ activityIntent.putExtra("ringtone_uri", ringtoneUriString);
+ activityIntent.putExtra("alarm_id", alarmId);
+ context.startActivity(activityIntent);
+
+ Intent serviceIntent = new Intent(context, AlarmSoundService.class);
+ serviceIntent.putExtra("alarm_title", title);
+ serviceIntent.putExtra("ringtone_uri", ringtoneUriString);
+
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+ context.startForegroundService(serviceIntent);
+ } else {
+ context.startService(serviceIntent);
+ }
+
+ createNotificationChannel(context);
+ showNotification(context, title, message, ringtoneUriString, alarmId);
+ }
+
+ private void createNotificationChannel(Context context) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ CharSequence name = "Alarmes de Medicação";
+ String description = "Notificações para lembretes de medicamentos";
+ int importance = NotificationManager.IMPORTANCE_HIGH;
+ NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
+ channel.setDescription(description);
+ channel.enableVibration(true);
+ channel.enableLights(true);
+
+ NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
+ if (notificationManager != null) {
+ notificationManager.createNotificationChannel(channel);
+ }
+ }
+ }
+
+ private void showNotification(Context context, String title, String message, String ringtoneUriString, int alarmId) {
+ Intent activityIntent = new Intent(context, AlarmRingingActivity.class);
+ activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ activityIntent.putExtra("alarm_title", title);
+ activityIntent.putExtra("alarm_time", new java.text.SimpleDateFormat("HH:mm", java.util.Locale.getDefault()).format(new java.util.Date()));
+ activityIntent.putExtra("ringtone_uri", ringtoneUriString);
+ activityIntent.putExtra("alarm_id", alarmId);
+
+ PendingIntent pendingIntent = PendingIntent.getActivity(
+ context,
+ alarmId >= 0 ? alarmId : 0,
+ activityIntent,
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
+ );
+
+ Intent stopIntent = new Intent(context, AlarmSoundService.class);
+ stopIntent.setAction("STOP_ALARM");
+ PendingIntent stopPendingIntent = PendingIntent.getService(
+ context,
+ alarmId >= 0 ? alarmId + 1000 : 1000,
+ stopIntent,
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
+ );
+
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
+ .setSmallIcon(android.R.drawable.ic_lock_idle_alarm)
+ .setContentTitle("⏰ " + title)
+ .setContentText(message)
+ .setPriority(NotificationCompat.PRIORITY_MAX)
+ .setCategory(NotificationCompat.CATEGORY_ALARM)
+ .setFullScreenIntent(pendingIntent, true)
+ .setOngoing(true)
+ .setAutoCancel(false)
+ .setContentIntent(pendingIntent)
+ .addAction(android.R.drawable.ic_media_pause, "Parar", stopPendingIntent);
+
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ if (notificationManager != null) {
+ notificationManager.notify(NOTIFICATION_ID, builder.build());
+ }
+ }
+
+}
+
diff --git a/app/src/main/java/com/example/bem/AlarmRingingActivity.java b/app/src/main/java/com/example/bem/AlarmRingingActivity.java
new file mode 100644
index 0000000..cce553e
--- /dev/null
+++ b/app/src/main/java/com/example/bem/AlarmRingingActivity.java
@@ -0,0 +1,121 @@
+package com.example.bem;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import java.util.Calendar;
+
+public class AlarmRingingActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
+ setShowWhenLocked(true);
+ setTurnScreenOn(true);
+ } else {
+ getWindow().addFlags(
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
+ WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON |
+ WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
+ WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
+ );
+ }
+
+ setContentView(R.layout.activity_alarm_ringing);
+
+ String title = getIntent().getStringExtra("alarm_title");
+ String time = getIntent().getStringExtra("alarm_time");
+ String ringtoneUri = getIntent().getStringExtra("ringtone_uri");
+ int alarmId = getIntent().getIntExtra("alarm_id", -1);
+
+ TextView textTitle = findViewById(R.id.textAlarmTitle);
+ TextView textTime = findViewById(R.id.textAlarmTime);
+ Button btnSnooze = findViewById(R.id.btnSnooze);
+ Button btnStop = findViewById(R.id.btnStop);
+
+ if (title != null) {
+ textTitle.setText(title);
+ }
+ if (time != null) {
+ textTime.setText(time);
+ }
+
+ btnSnooze.setOnClickListener(v -> {
+ snoozeAlarm(title, ringtoneUri, alarmId);
+ stopAlarmService();
+ finish();
+ });
+
+ btnStop.setOnClickListener(v -> {
+ stopAlarmService();
+ finish();
+ });
+ }
+
+ private void snoozeAlarm(String title, String ringtoneUri, int alarmId) {
+ AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
+ if (alarmManager == null) return;
+
+ Intent intent = new Intent(this, AlarmReceiver.class);
+ intent.putExtra("alarm_title", title);
+ intent.putExtra("alarm_message", "Hora de tomar: " + title);
+ intent.putExtra("ringtone_uri", ringtoneUri);
+
+ int snoozeId = alarmId >= 0 ? alarmId + 10000 : (int) System.currentTimeMillis();
+
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ this,
+ snoozeId,
+ intent,
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
+ );
+
+ Calendar calendar = Calendar.getInstance();
+ calendar.add(Calendar.MINUTE, 5);
+
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ alarmManager.setExactAndAllowWhileIdle(
+ AlarmManager.RTC_WAKEUP,
+ calendar.getTimeInMillis(),
+ pendingIntent
+ );
+ } else {
+ alarmManager.setExact(
+ AlarmManager.RTC_WAKEUP,
+ calendar.getTimeInMillis(),
+ pendingIntent
+ );
+ }
+
+ android.widget.Toast.makeText(this, "⏰ Alarme adiado por 5 minutos", android.widget.Toast.LENGTH_LONG).show();
+ } catch (SecurityException e) {
+ android.widget.Toast.makeText(this, "Erro ao adiar alarme", android.widget.Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void stopAlarmService() {
+ Intent serviceIntent = new Intent(this, AlarmSoundService.class);
+ stopService(serviceIntent);
+ }
+
+ @Override
+ public void onBackPressed() {
+ // Não permite voltar sem parar o alarme
+ }
+}
+
+
+
+
diff --git a/app/src/main/java/com/example/bem/AlarmSoundService.java b/app/src/main/java/com/example/bem/AlarmSoundService.java
new file mode 100644
index 0000000..b361c8f
--- /dev/null
+++ b/app/src/main/java/com/example/bem/AlarmSoundService.java
@@ -0,0 +1,201 @@
+package com.example.bem;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+
+import androidx.core.app.NotificationCompat;
+
+public class AlarmSoundService extends Service {
+
+ private static final String CHANNEL_ID = "alarm_sound_channel";
+ private static final int NOTIFICATION_ID = 2001;
+ private MediaPlayer mediaPlayer;
+ private Vibrator vibrator;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ createNotificationChannel();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (intent != null && "STOP_ALARM".equals(intent.getAction())) {
+ stopSelf();
+ return START_NOT_STICKY;
+ }
+
+ String title = intent.getStringExtra("alarm_title");
+ String ringtoneUriString = intent.getStringExtra("ringtone_uri");
+
+ startForeground(NOTIFICATION_ID, createNotification(title));
+
+ playAlarmSound(ringtoneUriString);
+ vibratePhone();
+
+ new android.os.Handler().postDelayed(this::stopSelf, 60000);
+
+ return START_NOT_STICKY;
+ }
+
+ private void playAlarmSound(String ringtoneUriString) {
+ try {
+ Uri soundUri;
+ if (ringtoneUriString != null && !ringtoneUriString.isEmpty()) {
+ soundUri = Uri.parse(ringtoneUriString);
+ } else {
+ soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
+ if (soundUri == null) {
+ soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
+ }
+ }
+
+ mediaPlayer = new MediaPlayer();
+ mediaPlayer.setDataSource(this, soundUri);
+
+ AudioAttributes audioAttributes = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_ALARM)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .build();
+ mediaPlayer.setAudioAttributes(audioAttributes);
+
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ if (audioManager != null) {
+ int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM);
+ audioManager.setStreamVolume(AudioManager.STREAM_ALARM, maxVolume, 0);
+ }
+
+ mediaPlayer.setLooping(true);
+ mediaPlayer.setVolume(1.0f, 1.0f);
+
+ mediaPlayer.setOnPreparedListener(mp -> {
+ mp.start();
+ });
+
+ mediaPlayer.setOnErrorListener((mp, what, extra) -> {
+ mp.reset();
+ return true;
+ });
+
+ mediaPlayer.prepareAsync();
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ try {
+ Uri defaultUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
+ if (defaultUri != null && mediaPlayer != null) {
+ mediaPlayer.reset();
+ mediaPlayer.setDataSource(this, defaultUri);
+ mediaPlayer.prepare();
+ mediaPlayer.start();
+ }
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+ }
+
+ private void vibratePhone() {
+ vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
+ if (vibrator != null && vibrator.hasVibrator()) {
+ long[] pattern = {
+ 0, // Início
+ 400, // Vibra 400ms
+ 1200, // Pausa 1.2s
+ 400, // Vibra 400ms
+ 3000 // Pausa 3s antes de repetir
+ };
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ vibrator.vibrate(VibrationEffect.createWaveform(pattern, 0));
+ } else {
+ vibrator.vibrate(pattern, 0);
+ }
+ }
+ }
+
+ private Notification createNotification(String title) {
+ Intent stopIntent = new Intent(this, AlarmSoundService.class);
+ stopIntent.setAction("STOP_ALARM");
+ PendingIntent stopPendingIntent = PendingIntent.getService(
+ this,
+ 0,
+ stopIntent,
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
+ );
+
+ Intent openIntent = new Intent(this, MainActivity.class);
+ openIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ PendingIntent openPendingIntent = PendingIntent.getActivity(
+ this,
+ 0,
+ openIntent,
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
+ );
+
+ return new NotificationCompat.Builder(this, CHANNEL_ID)
+ .setSmallIcon(android.R.drawable.ic_lock_idle_alarm)
+ .setContentTitle("⏰ " + (title != null ? title : "Alarme de Medicamento"))
+ .setContentText("Hora de tomar o medicamento! Toque para abrir.")
+ .setPriority(NotificationCompat.PRIORITY_MAX)
+ .setCategory(NotificationCompat.CATEGORY_ALARM)
+ .setOngoing(true)
+ .setAutoCancel(false)
+ .setContentIntent(openPendingIntent)
+ .addAction(android.R.drawable.ic_media_pause, "Parar", stopPendingIntent)
+ .build();
+ }
+
+ private void createNotificationChannel() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel channel = new NotificationChannel(
+ CHANNEL_ID,
+ "Serviço de Alarme",
+ NotificationManager.IMPORTANCE_HIGH
+ );
+ channel.setDescription("Toca o alarme de medicação");
+ channel.setSound(null, null);
+
+ NotificationManager manager = getSystemService(NotificationManager.class);
+ if (manager != null) {
+ manager.createNotificationChannel(channel);
+ }
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ if (mediaPlayer != null) {
+ if (mediaPlayer.isPlaying()) {
+ mediaPlayer.stop();
+ }
+ mediaPlayer.release();
+ mediaPlayer = null;
+ }
+
+ if (vibrator != null) {
+ vibrator.cancel();
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+}
+
diff --git a/app/src/main/java/com/example/bem/InviteCodeActivity.java b/app/src/main/java/com/example/bem/InviteCodeActivity.java
new file mode 100644
index 0000000..9586f48
--- /dev/null
+++ b/app/src/main/java/com/example/bem/InviteCodeActivity.java
@@ -0,0 +1,248 @@
+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.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.google.firebase.auth.FirebaseAuth;
+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 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);
+ setContentView(R.layout.activity_invite_code);
+
+ 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);
+
+ 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());
+ }
+
+ 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 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) {
+ inputInviteCode.setError("Código deve ter 6 dígitos");
+ return;
+ }
+
+ if (mAuth.getCurrentUser() == null) {
+ Toast.makeText(this, "Erro: Não autenticado", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ btnValidateCode.setEnabled(false);
+
+ 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;
+ }
+
+ Boolean used = document.getBoolean("used");
+ Long expiresAt = document.getLong("expiresAt");
+ String targetUserId = document.getString("userId");
+
+ if (used != null && used) {
+ btnValidateCode.setEnabled(true);
+ Toast.makeText(this, "❌ Código já utilizado", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ if (expiresAt != null && System.currentTimeMillis() > expiresAt) {
+ btnValidateCode.setEnabled(true);
+ Toast.makeText(this, "❌ Código expirado", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ linkGuardianToUser(code, targetUserId);
+ })
+ .addOnFailureListener(e -> {
+ 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 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();
+ });
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (countDownTimer != null) {
+ countDownTimer.cancel();
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/bem/LoginActivity.java b/app/src/main/java/com/example/bem/LoginActivity.java
new file mode 100644
index 0000000..ffa7c67
--- /dev/null
+++ b/app/src/main/java/com/example/bem/LoginActivity.java
@@ -0,0 +1,340 @@
+package com.example.bem;
+
+import android.app.AlertDialog;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatDelegate;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.auth.FirebaseUser;
+import com.google.firebase.firestore.FirebaseFirestore;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class LoginActivity extends AppCompatActivity {
+
+ private FirebaseAuth mAuth;
+ private FirebaseFirestore db;
+ private SharedPreferences prefs;
+ private static final String PREF_DARK_MODE = "dark_mode";
+
+ private EditText inputEmail;
+ private EditText inputPassword;
+ private EditText inputName;
+ private EditText inputPhone;
+ private Button btnLogin;
+ private TextView textLoginType;
+ private TextView textSwitchMode;
+ private TextView textForgotPassword;
+ private TextView textSwitchToGuardian;
+ private ProgressBar progressBar;
+
+ private boolean isRegisterMode = false;
+ private boolean isGuardianMode = false;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
+ applyThemeFromPrefs();
+
+ 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();
+ }
+
+ setContentView(R.layout.activity_login);
+
+ inputEmail = findViewById(R.id.inputEmail);
+ inputPassword = findViewById(R.id.inputPassword);
+ inputName = findViewById(R.id.inputName);
+ inputPhone = findViewById(R.id.inputPhone);
+ btnLogin = findViewById(R.id.btnLogin);
+ textLoginType = findViewById(R.id.textLoginType);
+ textSwitchMode = findViewById(R.id.textSwitchMode);
+ textForgotPassword = findViewById(R.id.textForgotPassword);
+ textSwitchToGuardian = findViewById(R.id.textSwitchToGuardian);
+
+ btnLogin.setOnClickListener(v -> handleLogin());
+ textSwitchMode.setOnClickListener(v -> toggleMode());
+ textForgotPassword.setOnClickListener(v -> showForgotPasswordDialog());
+ textSwitchToGuardian.setOnClickListener(v -> switchToGuardianMode());
+ }
+
+ private void switchToGuardianMode() {
+ isGuardianMode = !isGuardianMode;
+
+ if (isGuardianMode) {
+ textLoginType.setText("Login de Responsável");
+ textSwitchToGuardian.setText("👤 Sou Utilizador");
+ inputPhone.setVisibility(View.VISIBLE);
+ inputPhone.setHint("Número de telemóvel");
+ } else {
+ textLoginType.setText("Login de Utilizador");
+ textSwitchToGuardian.setText("🔒 Sou Responsável");
+ inputPhone.setVisibility(View.GONE);
+ }
+
+ isRegisterMode = false;
+ updateUI();
+ }
+
+ private void toggleMode() {
+ isRegisterMode = !isRegisterMode;
+ updateUI();
+ }
+
+ private void updateUI() {
+ if (isRegisterMode) {
+ btnLogin.setText("Criar Conta");
+ textSwitchMode.setText("Já tem conta? Entrar");
+ inputName.setVisibility(View.VISIBLE);
+
+ if (isGuardianMode) {
+ inputPhone.setVisibility(View.VISIBLE);
+ }
+ } else {
+ btnLogin.setText("Entrar");
+ textSwitchMode.setText("Não tem conta? Registar");
+ inputName.setVisibility(View.GONE);
+
+ if (!isGuardianMode) {
+ inputPhone.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ private void handleLogin() {
+ String email = inputEmail.getText().toString().trim();
+ String password = inputPassword.getText().toString().trim();
+ String name = inputName.getText().toString().trim();
+ String phone = inputPhone.getText().toString().trim();
+
+ if (TextUtils.isEmpty(email)) {
+ inputEmail.setError("Insira o email");
+ return;
+ }
+
+ if (!android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
+ inputEmail.setError("Email inválido");
+ return;
+ }
+
+ if (TextUtils.isEmpty(password)) {
+ inputPassword.setError("Insira a palavra-passe");
+ return;
+ }
+
+ if (password.length() < 6) {
+ inputPassword.setError("Mínimo 6 caracteres");
+ return;
+ }
+
+ if (isRegisterMode && TextUtils.isEmpty(name)) {
+ inputName.setError("Insira o nome");
+ return;
+ }
+
+ btnLogin.setEnabled(false);
+
+ if (isRegisterMode) {
+ registerUser(email, password, name, phone);
+ } else {
+ loginUser(email, password);
+ }
+ }
+
+ private void registerUser(String email, String password, String name, String phone) {
+ mAuth.createUserWithEmailAndPassword(email, password)
+ .addOnCompleteListener(task -> {
+ if (task.isSuccessful()) {
+ FirebaseUser user = mAuth.getCurrentUser();
+ if (user != null) {
+ saveUserData(user.getUid(), email, name, phone, true);
+ } else {
+ btnLogin.setEnabled(true);
+ Toast.makeText(this, "Erro ao obter utilizador. Tente entrar com o email/senha.",
+ Toast.LENGTH_LONG).show();
+ mAuth.signOut();
+ }
+ } else {
+ btnLogin.setEnabled(true);
+ String error = task.getException() != null ? task.getException().getMessage()
+ : "Erro desconhecido";
+ Toast.makeText(this, "Erro: " + error, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+
+ private void saveUserData(String uid, String email, String name, String phone, boolean fromRegister) {
+ Map userData = new HashMap<>();
+ userData.put("email", email);
+ userData.put("name", name);
+ userData.put("phone", phone != null ? phone : "");
+ userData.put("type", isGuardianMode ? "guardian" : "user");
+ userData.put("createdAt", System.currentTimeMillis());
+
+ db.collection("users").document(uid)
+ .set(userData)
+ .addOnSuccessListener(aVoid -> {
+ if (fromRegister) {
+ Toast.makeText(this, "✓ Conta criada! Faça login para continuar.", Toast.LENGTH_LONG).show();
+ mAuth.signOut();
+ btnLogin.setEnabled(true);
+ // Recarrega a tela limpa
+ Intent intent = getIntent();
+ finish();
+ startActivity(intent);
+ } else {
+ redirectToApp();
+ }
+ })
+ .addOnFailureListener(e -> {
+ btnLogin.setEnabled(true);
+ Toast.makeText(this, "Erro ao salvar dados: " + e.getMessage(), Toast.LENGTH_LONG).show();
+ });
+ }
+
+ private void loginUser(String email, String password) {
+ mAuth.signInWithEmailAndPassword(email, password)
+ .addOnCompleteListener(task -> {
+ if (task.isSuccessful()) {
+ FirebaseUser user = mAuth.getCurrentUser();
+ if (user != null) {
+ checkUserTypeAndRedirect(user.getUid());
+ } else {
+ btnLogin.setEnabled(true);
+ Toast.makeText(this, "Erro ao iniciar sessão. Tente novamente.", Toast.LENGTH_LONG).show();
+ }
+ } else {
+ btnLogin.setEnabled(true);
+ String error = task.getException() != null ? task.getException().getMessage()
+ : "Credenciais inválidas";
+ Toast.makeText(this, "Erro: " + error, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+
+ private void checkUserTypeAndRedirect(String uid) {
+ db.collection("users").document(uid)
+ .get()
+ .addOnSuccessListener(document -> {
+ if (document.exists()) {
+ String type = document.getString("type");
+ prefs.edit()
+ .putString("user_type", type != null ? type : "user")
+ .putString("user_name", document.getString("name"))
+ .apply();
+
+ if ("guardian".equals(type)) {
+ // Verifies if guardian is already linked to a user
+ java.util.List managedUsers = (java.util.List) 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);
+ startActivity(intent);
+ finish();
+ }
+ } else {
+ redirectToApp();
+ }
+ } else {
+ // Se o documento não existir (ex.: registo antigo sem salvar), cria um básico
+ FirebaseUser user = mAuth.getCurrentUser();
+ String email = user != null ? user.getEmail() : "";
+ String name = (email != null && email.contains("@")) ? email.substring(0, email.indexOf("@"))
+ : "Utilizador";
+
+ Map userData = new HashMap<>();
+ userData.put("email", email);
+ userData.put("name", name);
+ userData.put("phone", "");
+ userData.put("type", "user");
+ userData.put("createdAt", System.currentTimeMillis());
+
+ db.collection("users").document(uid)
+ .set(userData)
+ .addOnSuccessListener(aVoid -> {
+ prefs.edit()
+ .putString("user_type", "user")
+ .putString("user_name", name)
+ .apply();
+ redirectToApp();
+ })
+ .addOnFailureListener(e -> {
+ 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();
+ btnLogin.setEnabled(true);
+ });
+ }
+
+ private void redirectToApp() {
+ Intent intent = new Intent(this, MainActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ startActivity(intent);
+ finish();
+ }
+
+ private void showForgotPasswordDialog() {
+ View dialogView = getLayoutInflater().inflate(android.R.layout.simple_list_item_1, null);
+ EditText emailInput = new EditText(this);
+ emailInput.setHint("Email de recuperação");
+ emailInput.setInputType(android.text.InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
+ emailInput.setPadding(50, 40, 50, 40);
+
+ new AlertDialog.Builder(this)
+ .setTitle("Recuperar Palavra-passe")
+ .setMessage("Insira o email para receber o link de recuperação:")
+ .setView(emailInput)
+ .setPositiveButton("Enviar", (dialog, which) -> {
+ String email = emailInput.getText().toString().trim();
+ if (TextUtils.isEmpty(email)) {
+ Toast.makeText(this, "Insira um email", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ mAuth.sendPasswordResetEmail(email)
+ .addOnSuccessListener(aVoid -> {
+ Toast.makeText(this, "✓ Email enviado! Verifique a caixa de entrada.",
+ Toast.LENGTH_LONG).show();
+ })
+ .addOnFailureListener(e -> {
+ Toast.makeText(this, "Erro: " + e.getMessage(), Toast.LENGTH_LONG).show();
+ });
+ })
+ .setNegativeButton("Cancelar", null)
+ .show();
+ }
+
+ private void applyThemeFromPrefs() {
+ boolean dark = getSharedPreferences("app_prefs", MODE_PRIVATE).getBoolean(PREF_DARK_MODE, false);
+ AppCompatDelegate
+ .setDefaultNightMode(dark ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO);
+ }
+}
diff --git a/app/src/main/java/com/example/bem/MainActivity.java b/app/src/main/java/com/example/bem/MainActivity.java
new file mode 100644
index 0000000..7334e4b
--- /dev/null
+++ b/app/src/main/java/com/example/bem/MainActivity.java
@@ -0,0 +1,932 @@
+package com.example.bem;
+
+import android.Manifest;
+import android.app.AlarmManager;
+import android.app.AlertDialog;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.Switch;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.activity.EdgeToEdge;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatDelegate;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.auth.FirebaseUser;
+import com.google.firebase.firestore.FirebaseFirestore;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+public class MainActivity extends AppCompatActivity {
+
+ private enum Tab {
+ ALARMS,
+ REMINDERS,
+ GUARDIANS,
+ CALENDAR
+ }
+
+ private FrameLayout tabContainer;
+ private LinearLayout tabAlarms;
+ private LinearLayout tabReminders;
+ private LinearLayout tabGuardians;
+ private LinearLayout tabCalendar;
+ private ImageView iconAlarms;
+ private ImageView iconReminders;
+ private ImageView iconGuardians;
+ private ImageView iconCalendar;
+ private TextView textAlarms;
+ private TextView textReminders;
+ private TextView textGuardians;
+ private TextView textCalendar;
+
+ private LayoutInflater inflater;
+ private View alarmsView;
+ private View remindersView;
+ private View guardiansView;
+ private View calendarView;
+ private LinearLayout alarmListContainer;
+ private final List alarms = new ArrayList<>();
+ private Tab currentTab = null;
+ private FirebaseAuth mAuth;
+ private FirebaseFirestore db;
+ private SharedPreferences prefs;
+ private static final String PREF_GUARDIAN_PIN = "guardian_pin";
+ private static final String PREF_DARK_MODE = "dark_mode";
+ private static final int REQUEST_NOTIFICATION_PERMISSION = 1001;
+ private static final int REQUEST_RINGTONE_PICKER = 1002;
+ private int editingAlarmIndex = -1;
+ private String currentUserId = null;
+ private boolean isGuardian = false;
+
+ private final ActivityResultLauncher notificationPermissionLauncher = registerForActivityResult(
+ new ActivityResultContracts.RequestPermission(), isGranted -> {
+ if (isGranted) {
+ Toast.makeText(this, "Permissão de notificações concedida!", Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(this, "Permissão negada. Alarmes podem não tocar.", Toast.LENGTH_LONG).show();
+ }
+ });
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mAuth = FirebaseAuth.getInstance();
+ db = FirebaseFirestore.getInstance();
+
+ FirebaseUser currentUser = mAuth.getCurrentUser();
+ if (currentUser == null) {
+ redirectToLogin();
+ return;
+ }
+
+ prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
+ applyThemeFromPrefs();
+
+ EdgeToEdge.enable(this);
+ setContentView(R.layout.activity_main);
+ inflater = LayoutInflater.from(this);
+
+ currentUserId = currentUser.getUid();
+ String userType = prefs.getString("user_type", "user");
+ isGuardian = "guardian".equals(userType);
+
+ tabContainer = findViewById(R.id.tabContainer);
+ tabAlarms = findViewById(R.id.tabAlarms);
+ tabReminders = findViewById(R.id.tabReminders);
+ tabGuardians = findViewById(R.id.tabGuardians);
+ tabCalendar = findViewById(R.id.tabCalendar);
+
+ iconAlarms = findViewById(R.id.iconAlarms);
+ iconReminders = findViewById(R.id.iconReminders);
+ iconGuardians = findViewById(R.id.iconGuardians);
+ iconCalendar = findViewById(R.id.iconCalendar);
+
+ textAlarms = findViewById(R.id.textAlarms);
+ textReminders = findViewById(R.id.textReminders);
+ textGuardians = findViewById(R.id.textGuardians);
+ textCalendar = findViewById(R.id.textCalendar);
+
+ ImageView profileButton = findViewById(R.id.profileButton);
+
+ tabAlarms.setOnClickListener(v -> switchTab(Tab.ALARMS));
+ tabReminders.setOnClickListener(v -> switchTab(Tab.REMINDERS));
+ tabGuardians.setOnClickListener(v -> handleGuardianTabClick());
+ tabCalendar.setOnClickListener(v -> switchTab(Tab.CALENDAR));
+ profileButton.setOnClickListener(v -> showProfileMenu());
+
+ requestNotificationPermissions();
+ seedSampleAlarms();
+
+ applyUserModeLayout();
+ }
+
+ private void applyUserModeLayout() {
+ if (isGuardian) {
+ tabAlarms.setVisibility(View.GONE);
+ tabReminders.setVisibility(View.GONE);
+ tabCalendar.setVisibility(View.VISIBLE);
+
+ // Guardian starts on their panel
+ switchTab(Tab.GUARDIANS);
+ } else {
+ tabAlarms.setVisibility(View.VISIBLE);
+ tabReminders.setVisibility(View.VISIBLE);
+ tabCalendar.setVisibility(View.GONE);
+
+ // Regular user starts on Alarms
+ switchTab(Tab.ALARMS);
+ }
+ }
+
+ private void redirectToLogin() {
+ Intent intent = new Intent(this, LoginActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ startActivity(intent);
+ finish();
+ }
+
+ // ... existing requestNotificationPermissions ...
+
+ private void handleGuardianTabClick() {
+ // Guardians are always allowed
+ if (isGuardian) {
+ switchTab(Tab.GUARDIANS);
+ } else {
+ checkIfUserHasGuardians();
+ }
+ }
+
+ // ... existing checkIfUserHasGuardians, showAddGuardianDialog,
+ // showGuardianOptionsDialog, settings ...
+
+ // ... showSettingsMenu ...
+
+ // ... toggleDarkMode, showUserInfo, logout, showProfileMenu,
+ // applyThemeFromPrefs, openSupportEmail ...
+
+ private void switchTab(Tab tab) {
+ if (tab == currentTab) {
+ return;
+ }
+ currentTab = tab;
+
+ View oldContent = tabContainer.getChildCount() > 0 ? tabContainer.getChildAt(0) : null;
+ if (oldContent != null) {
+ oldContent.animate()
+ .alpha(0f)
+ .setDuration(150)
+ .withEndAction(() -> {
+ tabContainer.removeAllViews();
+ loadAndShowTab(tab);
+ })
+ .start();
+ } else {
+ loadAndShowTab(tab);
+ }
+ }
+
+ private void loadAndShowTab(Tab tab) {
+ View content;
+ switch (tab) {
+ case REMINDERS:
+ if (remindersView == null) {
+ remindersView = inflater.inflate(R.layout.layout_tab_reminders, tabContainer, false);
+ }
+ content = remindersView;
+ break;
+ case GUARDIANS:
+ if (guardiansView == null) {
+ guardiansView = inflater.inflate(R.layout.layout_tab_guardians, tabContainer, false);
+ }
+ loadGuardianData(guardiansView);
+ content = guardiansView;
+ break;
+ case CALENDAR:
+ if (calendarView == null) {
+ calendarView = inflater.inflate(R.layout.layout_tab_calendar, tabContainer, false);
+ }
+ setupCalendar(calendarView);
+ content = calendarView;
+ break;
+ case ALARMS:
+ default:
+ if (alarmsView == null) {
+ alarmsView = inflater.inflate(R.layout.layout_tab_alarms, tabContainer, false);
+ }
+ // ... setup alarmsView listeners ...
+ Button addAlarm = alarmsView.findViewById(R.id.btnAddAlarm);
+ ImageView btnSettings = alarmsView.findViewById(R.id.btnSettings);
+ alarmListContainer = alarmsView.findViewById(R.id.alarmListContainer);
+ if (addAlarm != null) {
+ addAlarm.setOnClickListener(v -> showAddAlarmDialog());
+ }
+ if (btnSettings != null) {
+ btnSettings.setOnClickListener(v -> showSettingsMenu());
+ }
+ renderAlarms();
+ content = alarmsView;
+ break;
+ }
+
+ content.setAlpha(0f);
+ tabContainer.addView(content);
+ content.animate()
+ .alpha(1f)
+ .setDuration(120)
+ .start();
+
+ updateTabHighlight(tab);
+ }
+
+ private void setupCalendar(View view) {
+ LinearLayout calendarGrid = view.findViewById(R.id.calendarGrid);
+ TextView monthTitle = view.findViewById(R.id.textMonthTitle);
+ LinearLayout legendContainer = view.findViewById(R.id.legendContainer);
+
+ calendarGrid.removeAllViews();
+ legendContainer.removeAllViews();
+
+ Calendar calendar = Calendar.getInstance();
+ int currentDay = calendar.get(Calendar.DAY_OF_MONTH);
+ int currentMonth = calendar.get(Calendar.MONTH);
+
+ String[] months = { "Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro",
+ "Outubro", "Novembro", "Dezembro" };
+ monthTitle.setText(months[currentMonth] + " " + calendar.get(Calendar.YEAR));
+
+ // Colors for dots
+ int[] dotColors = {
+ ContextCompat.getColor(this, R.color.primary),
+ ContextCompat.getColor(this, R.color.guardian_purple_end),
+ ContextCompat.getColor(this, R.color.delete),
+ 0xFFFFA500, // Orange
+ 0xFF00C853 // Green
+ };
+
+ // Map medications to colors
+ java.util.Map medColors = new java.util.HashMap<>();
+ int colorIndex = 0;
+ for (AlarmItem alarm : alarms) {
+ medColors.put(alarm.title, dotColors[colorIndex % dotColors.length]);
+ colorIndex++;
+
+ // Add to legend
+ View legendItem = new TextView(this);
+ ((TextView) legendItem).setText("• " + alarm.title);
+ ((TextView) legendItem).setTextColor(medColors.get(alarm.title));
+ ((TextView) legendItem).setTextSize(14);
+ ((TextView) legendItem).setPadding(0, 0, 0, 8);
+ legendContainer.addView(legendItem);
+ }
+
+ calendar.set(Calendar.DAY_OF_MONTH, 1);
+ int firstDayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1; // 0=Sunday
+ int daysInMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
+
+ SharedPreferences logPrefs = getSharedPreferences("medication_log", MODE_PRIVATE);
+ java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyyMMdd", java.util.Locale.getDefault());
+
+ LinearLayout currentWeekRow = null;
+
+ // Empty cells for dias before 1st
+ currentWeekRow = new LinearLayout(this);
+ currentWeekRow.setOrientation(LinearLayout.HORIZONTAL);
+ calendarGrid.addView(currentWeekRow);
+
+ for (int i = 0; i < firstDayOfWeek; i++) {
+ View empty = new View(this);
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, 50, 1.0f);
+ empty.setLayoutParams(params);
+ currentWeekRow.addView(empty);
+ }
+
+ int dayOfWeekCounter = firstDayOfWeek;
+
+ for (int day = 1; day <= daysInMonth; day++) {
+ if (dayOfWeekCounter == 0) {
+ currentWeekRow = new LinearLayout(this);
+ currentWeekRow.setOrientation(LinearLayout.HORIZONTAL);
+ calendarGrid.addView(currentWeekRow);
+ }
+
+ View dayCell = inflater.inflate(R.layout.item_calendar_day, currentWeekRow, false);
+ TextView numText = dayCell.findViewById(R.id.textDayNumber);
+ LinearLayout dots = dayCell.findViewById(R.id.dotsContainer);
+
+ numText.setText(String.valueOf(day));
+
+ if (day == currentDay) {
+ numText.setBackground(ContextCompat.getDrawable(this, R.drawable.bg_calendar_day_selected));
+ numText.setTextColor(ContextCompat.getColor(this, R.color.primary));
+ } else {
+ numText.setBackground(null);
+ }
+
+ // Verify taken meds for this day
+ calendar.set(Calendar.DAY_OF_MONTH, day);
+ String dateKey = sdf.format(calendar.getTime());
+
+ for (AlarmItem alarm : alarms) {
+ String key = "med_" + alarm.title.replaceAll("\\s+", "_") + "_" + dateKey;
+ if (logPrefs.getBoolean(key + "_taken", false)) {
+ // Add dot
+ View dot = new View(this);
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(12, 12);
+ params.setMargins(2, 0, 2, 0);
+ dot.setLayoutParams(params);
+ dot.setBackground(ContextCompat.getDrawable(this, R.drawable.bg_dot));
+ dot.getBackground().setTint(medColors.get(alarm.title));
+ dots.addView(dot);
+ }
+ }
+
+ currentWeekRow.addView(dayCell);
+
+ dayOfWeekCounter++;
+ if (dayOfWeekCounter >= 7) {
+ dayOfWeekCounter = 0;
+ }
+ }
+
+ // Fill remaining cells
+ if (dayOfWeekCounter != 0) {
+ while (dayOfWeekCounter < 7) {
+ View empty = new View(this);
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, 50, 1.0f);
+ empty.setLayoutParams(params);
+ currentWeekRow.addView(empty);
+ dayOfWeekCounter++;
+ }
+ }
+ }
+
+ private void updateTabHighlight(Tab tab) {
+ resetTab(tabAlarms, iconAlarms, textAlarms);
+ resetTab(tabReminders, iconReminders, textReminders);
+ resetTab(tabGuardians, iconGuardians, textGuardians);
+ resetTab(tabCalendar, iconCalendar, textCalendar);
+
+ switch (tab) {
+ case ALARMS:
+ highlightTab(tabAlarms, iconAlarms, textAlarms);
+ break;
+ case REMINDERS:
+ highlightTab(tabReminders, iconReminders, textReminders);
+ break;
+ case GUARDIANS:
+ highlightTab(tabGuardians, iconGuardians, textGuardians);
+ break;
+ case CALENDAR:
+ highlightTab(tabCalendar, iconCalendar, textCalendar);
+ break;
+ }
+ }
+
+ private void highlightTab(LinearLayout layout, ImageView icon, TextView text) {
+ layout.setBackground(ContextCompat.getDrawable(this, R.drawable.bg_tab_active));
+ icon.setColorFilter(ContextCompat.getColor(this, R.color.primary));
+ text.setTextColor(ContextCompat.getColor(this, R.color.primary));
+ }
+
+ private void resetTab(LinearLayout layout, ImageView icon, TextView text) {
+ layout.setBackground(null);
+ int neutral = ContextCompat.getColor(this, R.color.neutral_medium);
+ icon.setColorFilter(neutral);
+ text.setTextColor(neutral);
+ }
+
+ private void seedSampleAlarms() {
+ alarms.add(new AlarmItem("Tomar medicamentos", "08:00 • 100mg", "Seg • Ter • Qua • Qui • Sex", true));
+ alarms.add(new AlarmItem("Almoço", "12:30 • 1 comprimido", "Seg • Ter • Qua • Qui • Sex • Sáb • Dom", true));
+ }
+
+ private void renderAlarms() {
+ if (alarmListContainer == null) {
+ return;
+ }
+ alarmListContainer.removeAllViews();
+ if (alarms.isEmpty()) {
+ TextView empty = new TextView(this);
+ empty.setText("Sem alarmes. Toque em \"+ Novo\" para criar um.");
+ empty.setTextColor(ContextCompat.getColor(this, R.color.neutral_medium));
+ empty.setPadding(0, 24, 0, 24);
+ alarmListContainer.addView(empty);
+ return;
+ }
+ for (int i = 0; i < alarms.size(); i++) {
+ final int index = i;
+ AlarmItem item = alarms.get(i);
+ View card = inflater.inflate(R.layout.item_alarm_card, alarmListContainer, false);
+ TextView title = card.findViewById(R.id.textAlarmTitle);
+ TextView time = card.findViewById(R.id.textAlarmTime);
+ TextView days = card.findViewById(R.id.textAlarmDays);
+ Switch toggle = card.findViewById(R.id.switchAlarm);
+ ImageButton edit = card.findViewById(R.id.buttonEditAlarm);
+ ImageButton delete = card.findViewById(R.id.buttonDeleteAlarm);
+ Button confirmBtn = card.findViewById(R.id.btnConfirmMedication);
+
+ title.setText(item.title);
+ time.setText(item.time);
+ days.setText(item.days);
+ toggle.setChecked(item.enabled);
+
+ if (item.takenToday) {
+ confirmBtn.setText("✓ Tomado");
+ confirmBtn.setBackgroundColor(ContextCompat.getColor(this, R.color.neutral_medium));
+ confirmBtn.setEnabled(false);
+ } else {
+ confirmBtn.setText("✓ Confirmar");
+ confirmBtn.setBackground(ContextCompat.getDrawable(this, R.drawable.bg_new_button));
+ confirmBtn.setEnabled(true);
+ }
+
+ confirmBtn.setOnClickListener(v -> {
+ item.takenToday = true;
+ item.lastTakenTimestamp = System.currentTimeMillis();
+ saveMedicationConfirmation(item);
+ renderAlarms();
+ showConfirmationDialog(item);
+ });
+
+ toggle.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ item.enabled = isChecked;
+ Toast.makeText(this,
+ item.title + (isChecked ? " ativado" : " desativado"),
+ Toast.LENGTH_SHORT).show();
+ });
+
+ edit.setOnClickListener(v -> showEditAlarmDialog(item, index));
+
+ delete.setOnClickListener(v -> {
+ new AlertDialog.Builder(this)
+ .setTitle("Remover Alarme")
+ .setMessage("Tem certeza que deseja remover \"" + item.title + "\"?")
+ .setPositiveButton("Remover", (dialog, which) -> {
+ alarms.remove(index);
+ renderAlarms();
+ Toast.makeText(this, "Alarme removido", Toast.LENGTH_SHORT).show();
+ })
+ .setNegativeButton("Cancelar", null)
+ .show();
+ });
+
+ alarmListContainer.addView(card);
+ }
+ }
+
+ private void showConfirmationDialog(AlarmItem item) {
+ java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("HH:mm", java.util.Locale.getDefault());
+ String timeStr = sdf.format(new java.util.Date(item.lastTakenTimestamp));
+
+ View dialogView = inflater.inflate(R.layout.dialog_add_alarm, null, false);
+
+ new AlertDialog.Builder(this)
+ .setTitle("✓ Medicamento Confirmado")
+ .setMessage("Confirmou que tomou:\n\n" +
+ "💊 " + item.title + "\n" +
+ "🕐 Às " + timeStr + "\n\n" +
+ "O responsável será notificado automaticamente.")
+ .setPositiveButton("OK", null)
+ .show();
+
+ Toast.makeText(this, "Responsável notificado!", Toast.LENGTH_LONG).show();
+ }
+
+ private void saveMedicationConfirmation(AlarmItem item) {
+ SharedPreferences confirmPrefs = getSharedPreferences("medication_log", MODE_PRIVATE);
+ String key = "med_" + item.title.replaceAll("\\s+", "_") + "_" +
+ new java.text.SimpleDateFormat("yyyyMMdd", java.util.Locale.getDefault())
+ .format(new java.util.Date());
+ confirmPrefs.edit()
+ .putLong(key, item.lastTakenTimestamp)
+ .putBoolean(key + "_taken", true)
+ .putString(key + "_title", item.title)
+ .apply();
+ }
+
+ private void loadGuardianData(View guardiansView) {
+ SharedPreferences confirmPrefs = getSharedPreferences("medication_log", MODE_PRIVATE);
+ java.text.SimpleDateFormat dateFormat = new java.text.SimpleDateFormat("yyyyMMdd",
+ java.util.Locale.getDefault());
+ String today = dateFormat.format(new java.util.Date());
+
+ int takenCount = 0;
+ int totalCount = alarms.size();
+
+ for (AlarmItem alarm : alarms) {
+ String key = "med_" + alarm.title.replaceAll("\\s+", "_") + "_" + today;
+ if (confirmPrefs.getBoolean(key + "_taken", false)) {
+ takenCount++;
+ }
+ }
+
+ TextView takenText = guardiansView.findViewById(R.id.textTakenCount);
+ TextView pendingText = guardiansView.findViewById(R.id.textPendingCount);
+ TextView adherenceText = guardiansView.findViewById(R.id.textAdherenceRate);
+
+ if (takenText != null) {
+ takenText.setText(String.valueOf(takenCount));
+ }
+ if (pendingText != null) {
+ pendingText.setText(String.valueOf(totalCount - takenCount));
+ }
+ if (adherenceText != null) {
+ int adherence = totalCount > 0 ? (takenCount * 100 / totalCount) : 0;
+ adherenceText.setText(adherence + "%");
+ }
+
+ LinearLayout statusContainer = guardiansView.findViewById(R.id.statusContainer);
+ if (statusContainer != null) {
+ statusContainer.removeAllViews();
+
+ for (AlarmItem alarm : alarms) {
+ String key = "med_" + alarm.title.replaceAll("\\s+", "_") + "_" + today;
+ boolean taken = confirmPrefs.getBoolean(key + "_taken", false);
+ long timestamp = confirmPrefs.getLong(key, 0);
+
+ View statusCard = inflater.inflate(R.layout.item_alarm_card, statusContainer, false);
+ TextView titleView = statusCard.findViewById(R.id.textAlarmTitle);
+ TextView timeView = statusCard.findViewById(R.id.textAlarmTime);
+ TextView daysView = statusCard.findViewById(R.id.textAlarmDays);
+ Button confirmBtn = statusCard.findViewById(R.id.btnConfirmMedication);
+ Switch toggleSwitch = statusCard.findViewById(R.id.switchAlarm);
+ ImageButton deleteBtn = statusCard.findViewById(R.id.buttonDeleteAlarm);
+
+ titleView.setText(alarm.title);
+ timeView.setText(alarm.time);
+
+ toggleSwitch.setVisibility(View.GONE);
+ deleteBtn.setVisibility(View.GONE);
+ confirmBtn.setVisibility(View.GONE);
+
+ if (taken) {
+ java.text.SimpleDateFormat timeFormat = new java.text.SimpleDateFormat("HH:mm",
+ java.util.Locale.getDefault());
+ String takenTime = timeFormat.format(new java.util.Date(timestamp));
+ daysView.setText("✓ Tomado às " + takenTime);
+ daysView.setTextColor(ContextCompat.getColor(this, R.color.primary));
+ daysView.setBackgroundColor(ContextCompat.getColor(this, R.color.accent));
+ } else {
+ daysView.setText("⚠ Pendente");
+ daysView.setTextColor(ContextCompat.getColor(this, R.color.delete));
+ daysView.setBackgroundColor(ContextCompat.getColor(this, R.color.background_surface));
+ }
+
+ statusContainer.addView(statusCard);
+ }
+ }
+ }
+
+ private void showAddAlarmDialog() {
+ View dialogView = inflater.inflate(R.layout.dialog_add_alarm, null, false);
+ EditText titleInput = dialogView.findViewById(R.id.inputAlarmTitle);
+ TextView timeDisplay = dialogView.findViewById(R.id.textSelectedTime);
+ EditText detailsInput = dialogView.findViewById(R.id.inputAlarmDetails);
+
+ final int[] selectedHour = { 8 };
+ final int[] selectedMinute = { 0 };
+
+ timeDisplay.setText(String.format("%02d:%02d", selectedHour[0], selectedMinute[0]));
+ timeDisplay.setTextColor(ContextCompat.getColor(this, R.color.neutral_dark));
+
+ View timePickerLayout = (View) timeDisplay.getParent();
+ if (timePickerLayout != null) {
+ timePickerLayout.setOnClickListener(v -> {
+ android.app.TimePickerDialog picker = new android.app.TimePickerDialog(
+ this,
+ (view, hourOfDay, minute) -> {
+ selectedHour[0] = hourOfDay;
+ selectedMinute[0] = minute;
+ timeDisplay.setText(String.format("%02d:%02d", hourOfDay, minute));
+ timeDisplay.setTextColor(ContextCompat.getColor(this, R.color.neutral_dark));
+ },
+ selectedHour[0],
+ selectedMinute[0],
+ true);
+ picker.setTitle("Escolha o horário");
+ picker.show();
+ });
+ }
+
+ AlertDialog dialog = new AlertDialog.Builder(this)
+ .setView(dialogView)
+ .setPositiveButton("Guardar", null)
+ .setNegativeButton("Cancelar", null)
+ .create();
+
+ dialog.setOnShowListener(dialogInterface -> {
+ Button positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ positiveButton.setTextColor(ContextCompat.getColor(this, R.color.primary));
+ positiveButton.setOnClickListener(v -> {
+ String title = titleInput.getText().toString().trim();
+ String details = detailsInput.getText().toString().trim();
+
+ if (TextUtils.isEmpty(title)) {
+ titleInput.setError("Insira o nome do medicamento");
+ titleInput.requestFocus();
+ return;
+ }
+
+ if (TextUtils.isEmpty(details)) {
+ details = "Configurar dias na edição";
+ }
+
+ String timeFormatted = String.format("%02d:%02d", selectedHour[0], selectedMinute[0]);
+ AlarmItem newAlarm = new AlarmItem(title, timeFormatted + " • " + details,
+ "Seg • Ter • Qua • Qui • Sex", true);
+ newAlarm.hour = selectedHour[0];
+ newAlarm.minute = selectedMinute[0];
+ alarms.add(newAlarm);
+ scheduleAlarm(newAlarm, alarms.size() - 1);
+ renderAlarms();
+ Toast.makeText(this, "Alarme criado e agendado!", Toast.LENGTH_SHORT).show();
+ dialog.dismiss();
+ });
+
+ Button negativeButton = dialog.getButton(AlertDialog.BUTTON_NEGATIVE);
+ negativeButton.setTextColor(ContextCompat.getColor(this, R.color.neutral_medium));
+ });
+
+ dialog.show();
+ }
+
+ private void showEditAlarmDialog(AlarmItem item, int index) {
+ editingAlarmIndex = index;
+
+ String[] options = {
+ "⏰ Alterar horário",
+ "🔔 Escolher toque",
+ "🎵 Testar toque atual",
+ "📝 Editar título"
+ };
+
+ new AlertDialog.Builder(this)
+ .setTitle("Editar: " + item.title)
+ .setItems(options, (dialog, which) -> {
+ switch (which) {
+ case 0:
+ showChangeTimeDialog(item, index);
+ break;
+ case 1:
+ openRingtonePicker(item);
+ break;
+ case 2:
+ testRingtone(item);
+ break;
+ case 3:
+ showEditTitleDialog(item, index);
+ break;
+ }
+ })
+ .setNegativeButton("Fechar", null)
+ .show();
+ }
+
+ private void showChangeTimeDialog(AlarmItem item, int index) {
+ android.app.TimePickerDialog picker = new android.app.TimePickerDialog(
+ this,
+ (view, hourOfDay, minute) -> {
+ item.hour = hourOfDay;
+ item.minute = minute;
+ item.time = String.format("%02d:%02d • %s", hourOfDay, minute,
+ item.time.contains("•") ? item.time.split("•")[1].trim() : "");
+ scheduleAlarm(item, index);
+ renderAlarms();
+ Toast.makeText(this, "Horário atualizado!", Toast.LENGTH_SHORT).show();
+ },
+ item.hour,
+ item.minute,
+ true);
+ picker.setTitle("Novo horário");
+ picker.show();
+ }
+
+ private void showEditTitleDialog(AlarmItem item, int index) {
+ EditText input = new EditText(this);
+ input.setText(item.title);
+ input.setHint("Nome do medicamento");
+ input.setPadding(50, 40, 50, 40);
+
+ new AlertDialog.Builder(this)
+ .setTitle("Editar nome")
+ .setView(input)
+ .setPositiveButton("Guardar", (dialog, which) -> {
+ String newTitle = input.getText().toString().trim();
+ if (!TextUtils.isEmpty(newTitle)) {
+ item.title = newTitle;
+ renderAlarms();
+ Toast.makeText(this, "Nome atualizado!", Toast.LENGTH_SHORT).show();
+ }
+ })
+ .setNegativeButton("Cancelar", null)
+ .show();
+ }
+
+ private void openRingtonePicker(AlarmItem item) {
+ Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
+ intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM);
+ intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, "Escolher toque para " + item.title);
+ intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
+ intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false);
+
+ Uri currentUri = null;
+ if (item.ringtoneUri != null && !item.ringtoneUri.isEmpty()) {
+ try {
+ currentUri = Uri.parse(item.ringtoneUri);
+ } catch (Exception e) {
+ currentUri = null;
+ }
+ }
+
+ if (currentUri == null) {
+ currentUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
+ }
+
+ intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, currentUri);
+
+ try {
+ startActivityForResult(intent, REQUEST_RINGTONE_PICKER);
+ } catch (Exception e) {
+ Toast.makeText(this, "Erro ao abrir seletor de toques", Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if (requestCode == REQUEST_RINGTONE_PICKER && resultCode == RESULT_OK && editingAlarmIndex >= 0) {
+ if (data == null) {
+ Toast.makeText(this, "Nenhum toque selecionado", Toast.LENGTH_SHORT).show();
+ editingAlarmIndex = -1;
+ return;
+ }
+
+ Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
+
+ if (uri != null && editingAlarmIndex < alarms.size()) {
+ AlarmItem item = alarms.get(editingAlarmIndex);
+ item.ringtoneUri = uri.toString();
+
+ try {
+ Ringtone ringtone = RingtoneManager.getRingtone(this, uri);
+ if (ringtone != null) {
+ item.ringtoneName = ringtone.getTitle(this);
+ if (item.ringtoneName == null || item.ringtoneName.isEmpty()) {
+ item.ringtoneName = "Toque personalizado";
+ }
+ } else {
+ item.ringtoneName = "Toque do sistema";
+ }
+ } catch (Exception e) {
+ item.ringtoneName = "Toque selecionado";
+ }
+
+ scheduleAlarm(item, editingAlarmIndex);
+
+ Toast.makeText(this, "✓ Toque guardado: " + item.ringtoneName + "\nToque em 'Testar' para ouvir",
+ Toast.LENGTH_LONG).show();
+ } else if (uri == null && editingAlarmIndex < alarms.size()) {
+ AlarmItem item = alarms.get(editingAlarmIndex);
+ item.ringtoneUri = null;
+ item.ringtoneName = "Toque padrão do sistema";
+ Toast.makeText(this, "Usando toque padrão", Toast.LENGTH_SHORT).show();
+ }
+ editingAlarmIndex = -1;
+ }
+ }
+
+ private Ringtone currentlyPlayingRingtone = null;
+
+ private void testRingtone(AlarmItem item) {
+ if (currentlyPlayingRingtone != null && currentlyPlayingRingtone.isPlaying()) {
+ currentlyPlayingRingtone.stop();
+ currentlyPlayingRingtone = null;
+ Toast.makeText(this, "Toque parado", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ Uri ringtoneUri;
+ if (item.ringtoneUri != null) {
+ ringtoneUri = Uri.parse(item.ringtoneUri);
+ } else {
+ ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
+ }
+
+ try {
+ currentlyPlayingRingtone = RingtoneManager.getRingtone(this, ringtoneUri);
+ currentlyPlayingRingtone.play();
+
+ Toast.makeText(this, "🔊 Tocando: " + item.ringtoneName + "\nToque novamente para parar", Toast.LENGTH_LONG)
+ .show();
+
+ new android.os.Handler().postDelayed(() -> {
+ if (currentlyPlayingRingtone != null && currentlyPlayingRingtone.isPlaying()) {
+ currentlyPlayingRingtone.stop();
+ currentlyPlayingRingtone = null;
+ }
+ }, 5000);
+ } catch (Exception e) {
+ Toast.makeText(this, "Erro ao reproduzir toque", Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void scheduleAlarm(AlarmItem item, int alarmId) {
+ AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
+ if (alarmManager == null)
+ return;
+
+ Intent intent = new Intent(this, AlarmReceiver.class);
+ intent.putExtra("alarm_title", item.title);
+ intent.putExtra("alarm_message", "Hora de tomar: " + item.title);
+ intent.putExtra("ringtone_uri", item.ringtoneUri);
+ intent.putExtra("alarm_id", alarmId);
+
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ this,
+ alarmId,
+ intent,
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
+
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(Calendar.HOUR_OF_DAY, item.hour);
+ calendar.set(Calendar.MINUTE, item.minute);
+ calendar.set(Calendar.SECOND, 0);
+
+ if (calendar.getTimeInMillis() < System.currentTimeMillis()) {
+ calendar.add(Calendar.DAY_OF_MONTH, 1);
+ }
+
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ alarmManager.setExactAndAllowWhileIdle(
+ AlarmManager.RTC_WAKEUP,
+ calendar.getTimeInMillis(),
+ pendingIntent);
+ } else {
+ alarmManager.setExact(
+ AlarmManager.RTC_WAKEUP,
+ calendar.getTimeInMillis(),
+ pendingIntent);
+ }
+ Toast.makeText(this, "Alarme programado para " + item.hour + ":" + String.format("%02d", item.minute),
+ Toast.LENGTH_SHORT).show();
+ } catch (SecurityException e) {
+ Toast.makeText(this, "Erro: Permissão de alarme necessária", Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private static class AlarmItem {
+ String title;
+ String time;
+ String days;
+ boolean enabled;
+ int hour;
+ int minute;
+ boolean takenToday;
+ long lastTakenTimestamp;
+ String ringtoneUri;
+ String ringtoneName;
+
+ AlarmItem(String title, String time, String days, boolean enabled) {
+ this.title = title;
+ this.time = time;
+ this.days = days;
+ this.enabled = enabled;
+ this.hour = 8;
+ this.minute = 0;
+ this.takenToday = false;
+ this.lastTakenTimestamp = 0;
+ this.ringtoneUri = null;
+ this.ringtoneName = "Toque padrão";
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bg_alarm_card.xml b/app/src/main/res/drawable/bg_alarm_card.xml
new file mode 100644
index 0000000..14132a4
--- /dev/null
+++ b/app/src/main/res/drawable/bg_alarm_card.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_avatar.xml b/app/src/main/res/drawable/bg_avatar.xml
new file mode 100644
index 0000000..13d99d5
--- /dev/null
+++ b/app/src/main/res/drawable/bg_avatar.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_bottom_bar.xml b/app/src/main/res/drawable/bg_bottom_bar.xml
new file mode 100644
index 0000000..4ce056a
--- /dev/null
+++ b/app/src/main/res/drawable/bg_bottom_bar.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_calendar_day_selected.xml b/app/src/main/res/drawable/bg_calendar_day_selected.xml
new file mode 100644
index 0000000..044c63a
--- /dev/null
+++ b/app/src/main/res/drawable/bg_calendar_day_selected.xml
@@ -0,0 +1,13 @@
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_chip.xml b/app/src/main/res/drawable/bg_chip.xml
new file mode 100644
index 0000000..976119e
--- /dev/null
+++ b/app/src/main/res/drawable/bg_chip.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_day_chip.xml b/app/src/main/res/drawable/bg_day_chip.xml
new file mode 100644
index 0000000..dd500c4
--- /dev/null
+++ b/app/src/main/res/drawable/bg_day_chip.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_dot.xml b/app/src/main/res/drawable/bg_dot.xml
new file mode 100644
index 0000000..5b5b2ed
--- /dev/null
+++ b/app/src/main/res/drawable/bg_dot.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_guardian_card.xml b/app/src/main/res/drawable/bg_guardian_card.xml
new file mode 100644
index 0000000..2a2b3bc
--- /dev/null
+++ b/app/src/main/res/drawable/bg_guardian_card.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_guardian_chip.xml b/app/src/main/res/drawable/bg_guardian_chip.xml
new file mode 100644
index 0000000..d6815e0
--- /dev/null
+++ b/app/src/main/res/drawable/bg_guardian_chip.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_guardian_hero.xml b/app/src/main/res/drawable/bg_guardian_hero.xml
new file mode 100644
index 0000000..e4f3f32
--- /dev/null
+++ b/app/src/main/res/drawable/bg_guardian_hero.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_header_gradient.xml b/app/src/main/res/drawable/bg_header_gradient.xml
new file mode 100644
index 0000000..1514839
--- /dev/null
+++ b/app/src/main/res/drawable/bg_header_gradient.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_hero_card.xml b/app/src/main/res/drawable/bg_hero_card.xml
new file mode 100644
index 0000000..b16ba79
--- /dev/null
+++ b/app/src/main/res/drawable/bg_hero_card.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_icon_button.xml b/app/src/main/res/drawable/bg_icon_button.xml
new file mode 100644
index 0000000..7f34c07
--- /dev/null
+++ b/app/src/main/res/drawable/bg_icon_button.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_new_button.xml b/app/src/main/res/drawable/bg_new_button.xml
new file mode 100644
index 0000000..d8d9f22
--- /dev/null
+++ b/app/src/main/res/drawable/bg_new_button.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_reminder_card.xml b/app/src/main/res/drawable/bg_reminder_card.xml
new file mode 100644
index 0000000..320bf5d
--- /dev/null
+++ b/app/src/main/res/drawable/bg_reminder_card.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_reminder_hero.xml b/app/src/main/res/drawable/bg_reminder_hero.xml
new file mode 100644
index 0000000..b657106
--- /dev/null
+++ b/app/src/main/res/drawable/bg_reminder_hero.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_reminder_info_card.xml b/app/src/main/res/drawable/bg_reminder_info_card.xml
new file mode 100644
index 0000000..b8ae69e
--- /dev/null
+++ b/app/src/main/res/drawable/bg_reminder_info_card.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_search.xml b/app/src/main/res/drawable/bg_search.xml
new file mode 100644
index 0000000..02d1b61
--- /dev/null
+++ b/app/src/main/res/drawable/bg_search.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_service_card.xml b/app/src/main/res/drawable/bg_service_card.xml
new file mode 100644
index 0000000..b7ef8b0
--- /dev/null
+++ b/app/src/main/res/drawable/bg_service_card.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_service_icon.xml b/app/src/main/res/drawable/bg_service_icon.xml
new file mode 100644
index 0000000..d96bf64
--- /dev/null
+++ b/app/src/main/res/drawable/bg_service_icon.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_service_icon_alt.xml b/app/src/main/res/drawable/bg_service_icon_alt.xml
new file mode 100644
index 0000000..a5864aa
--- /dev/null
+++ b/app/src/main/res/drawable/bg_service_icon_alt.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_tab_active.xml b/app/src/main/res/drawable/bg_tab_active.xml
new file mode 100644
index 0000000..e543855
--- /dev/null
+++ b/app/src/main/res/drawable/bg_tab_active.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..ca3826a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/logo_bem_temp.xml b/app/src/main/res/drawable/logo_bem_temp.xml
new file mode 100644
index 0000000..5e10ada
--- /dev/null
+++ b/app/src/main/res/drawable/logo_bem_temp.xml
@@ -0,0 +1,23 @@
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_alarm_ringing.xml b/app/src/main/res/layout/activity_alarm_ringing.xml
new file mode 100644
index 0000000..e1c6cfe
--- /dev/null
+++ b/app/src/main/res/layout/activity_alarm_ringing.xml
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_invite_code.xml b/app/src/main/res/layout/activity_invite_code.xml
new file mode 100644
index 0000000..865228a
--- /dev/null
+++ b/app/src/main/res/layout/activity_invite_code.xml
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
new file mode 100644
index 0000000..cafee06
--- /dev/null
+++ b/app/src/main/res/layout/activity_login.xml
@@ -0,0 +1,150 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..e2a5712
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,154 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_add_alarm.xml b/app/src/main/res/layout/dialog_add_alarm.xml
new file mode 100644
index 0000000..98d25cc
--- /dev/null
+++ b/app/src/main/res/layout/dialog_add_alarm.xml
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_alarm_card.xml b/app/src/main/res/layout/item_alarm_card.xml
new file mode 100644
index 0000000..71bd8a0
--- /dev/null
+++ b/app/src/main/res/layout/item_alarm_card.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_calendar_day.xml b/app/src/main/res/layout/item_calendar_day.xml
new file mode 100644
index 0000000..20dc82b
--- /dev/null
+++ b/app/src/main/res/layout/item_calendar_day.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/layout_tab_alarms.xml b/app/src/main/res/layout/layout_tab_alarms.xml
new file mode 100644
index 0000000..63de103
--- /dev/null
+++ b/app/src/main/res/layout/layout_tab_alarms.xml
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/layout_tab_calendar.xml b/app/src/main/res/layout/layout_tab_calendar.xml
new file mode 100644
index 0000000..c9ed07d
--- /dev/null
+++ b/app/src/main/res/layout/layout_tab_calendar.xml
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/layout_tab_guardians.xml b/app/src/main/res/layout/layout_tab_guardians.xml
new file mode 100644
index 0000000..64476b5
--- /dev/null
+++ b/app/src/main/res/layout/layout_tab_guardians.xml
@@ -0,0 +1,692 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/layout_tab_reminders.xml b/app/src/main/res/layout/layout_tab_reminders.xml
new file mode 100644
index 0000000..4f662dc
--- /dev/null
+++ b/app/src/main/res/layout/layout_tab_reminders.xml
@@ -0,0 +1,411 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..c4a603d
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..c4a603d
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..fa088c2
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..070e76e
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..7223765
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..af31e54
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..24c52f9
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..839ae6f
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..31ffbf3
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..170e732
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b079dc8
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..33bfd4d
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..37c2628
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..ede599e
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..1e7177a
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..02820c0
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..969723d
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..dab231f
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..1b4ea52
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,22 @@
+
+
+ #FF000000
+ #FFFFFFFF
+
+ #00B894
+ #5FE0C5
+ #1E2A28
+ #5E7671
+ #EEFFF7
+ #D7E8E1
+ #14000000
+ #FF6F6F
+ #00C7FF
+ #0084FF
+ #D2E5FA
+ #EAF4FF
+ #B968FF
+ #7A55FF
+ #E4D8FF
+ #F9F2FF
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..fc6ddcb
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+ Bem+
+ Eliminar alarme
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..0ac3a74
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..4df9255
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/com/example/bem/ExampleUnitTest.java b/app/src/test/java/com/example/bem/ExampleUnitTest.java
new file mode 100644
index 0000000..90bbc36
--- /dev/null
+++ b/app/src/test/java/com/example/bem/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.example.bem;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..c8d5778
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,14 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath("com.google.gms:google-services:4.4.0")
+ }
+}
+
+plugins {
+ alias(libs.plugins.android.application) apply false
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..4387edc
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,21 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. For more details, visit
+# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..01b2a64
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,24 @@
+[versions]
+agp = "8.13.2"
+junit = "4.13.2"
+junitVersion = "1.3.0"
+espressoCore = "3.7.0"
+appcompat = "1.7.1"
+material = "1.13.0"
+activity = "1.11.0"
+constraintlayout = "2.2.1"
+firebaseAuth = "24.0.1"
+
+[libraries]
+junit = { group = "junit", name = "junit", version.ref = "junit" }
+ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
+espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
+appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
+constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
+firebase-auth = { group = "com.google.firebase", name = "firebase-auth", version.ref = "firebaseAuth" }
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..353b7e7
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Nov 25 16:03:27 WET 2025
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..4f906e0
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..ac1b06f
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..a51423f
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,23 @@
+pluginManagement {
+ repositories {
+ google {
+ content {
+ includeGroupByRegex("com\\.android.*")
+ includeGroupByRegex("com\\.google.*")
+ includeGroupByRegex("androidx.*")
+ }
+ }
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "Bem+"
+include(":app")