Compare commits

...

7 Commits

Author SHA1 Message Date
a1a01e27df depois do ollama 2026-06-18 11:49:48 +01:00
31034a3465 depois do ollama 2026-06-18 11:26:04 +01:00
75ba56cf74 depois do ollama 2026-06-18 11:20:10 +01:00
55560a1bfb antes do ollama 2026-06-18 11:15:49 +01:00
04ce7ece4f a acabar 2026-06-18 10:19:35 +01:00
4356a7432e a acabar 2026-06-15 17:09:47 +01:00
196487a2e2 a acabar 2026-06-15 15:32:58 +01:00
27 changed files with 415 additions and 210 deletions

View File

@@ -3,6 +3,39 @@
<component name="deploymentTargetSelector"> <component name="deploymentTargetSelector">
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-06-18T10:01:02.387533Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=RZCX40Q6DDY" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
<SelectionState runConfigName="main">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-06-03T14:39:06.554406Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/230415/.android/avd/Pixel_9a.avd" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
<SelectionState runConfigName="unitTest">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-06-03T14:39:06.554406Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/230415/.android/avd/Pixel_9a.avd" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
<SelectionState runConfigName="androidTest">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-06-03T14:39:06.554406Z"> <DropdownSelection timestamp="2026-06-03T14:39:06.554406Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">

View File

@@ -7,8 +7,10 @@ import android.util.Patterns;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.Toast; import android.widget.Toast;
import com.example.lifegrid.utils.CustomToast;
import androidx.activity.EdgeToEdge; import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
@@ -58,6 +60,9 @@ public class CriarContaActivity extends AppCompatActivity {
// Configura o botão de registo para acionar a validação e criação de conta // Configura o botão de registo para acionar a validação e criação de conta
loginButton2.setOnClickListener(v -> criarConta()); loginButton2.setOnClickListener(v -> criarConta());
ImageView btnBack = findViewById(R.id.btnBack);
btnBack.setOnClickListener(v -> finish());
} }
/** /**
@@ -83,9 +88,7 @@ public class CriarContaActivity extends AppCompatActivity {
FirebaseUser user = firebaseAuth.getCurrentUser(); FirebaseUser user = firebaseAuth.getCurrentUser();
if (user != null) { if (user != null) {
// Conta criada com sucesso - redirecionar para TelaInicialActivity // Conta criada com sucesso - redirecionar para TelaInicialActivity
Toast.makeText(this, CustomToast.success(this, "Conta criada com sucesso! Bem-vindo, " + nome);
"Conta criada com sucesso! Bem-vindo, " + nome,
Toast.LENGTH_SHORT).show();
// Redirecionar para TelaInicialActivity apenas após sucesso // Redirecionar para TelaInicialActivity apenas após sucesso
Intent intent = new Intent(CriarContaActivity.this, TelaInicialActivity.class); Intent intent = new Intent(CriarContaActivity.this, TelaInicialActivity.class);
@@ -107,7 +110,7 @@ public class CriarContaActivity extends AppCompatActivity {
errorMessage = "Palavra-passe muito fraca. Use uma palavra-passe mais forte."; errorMessage = "Palavra-passe muito fraca. Use uma palavra-passe mais forte.";
} }
} }
Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show(); CustomToast.error(this, errorMessage);
} }
}); });
} }

View File

@@ -9,6 +9,7 @@ import android.widget.Spinner;
import android.widget.Switch; import android.widget.Switch;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.example.lifegrid.utils.CustomToast;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
@@ -36,7 +37,7 @@ public class DefinicoesActivity extends AppCompatActivity {
private Button btnSaveSettings; private Button btnSaveSettings;
private EditText etUsername; private EditText etUsername;
private Switch switchNotifications; private Switch switchNotifications;
private Spinner spinnerCurrency;
private ImageView ivProfilePicture; private ImageView ivProfilePicture;
private TextView tvChangePhoto; private TextView tvChangePhoto;
@@ -62,7 +63,7 @@ public class DefinicoesActivity extends AppCompatActivity {
btnSaveSettings = findViewById(R.id.btnSaveSettings); btnSaveSettings = findViewById(R.id.btnSaveSettings);
etUsername = findViewById(R.id.etUsername); etUsername = findViewById(R.id.etUsername);
switchNotifications = findViewById(R.id.switchNotifications); switchNotifications = findViewById(R.id.switchNotifications);
spinnerCurrency = findViewById(R.id.spinnerCurrency);
ivProfilePicture = findViewById(R.id.ivProfilePicture); ivProfilePicture = findViewById(R.id.ivProfilePicture);
tvChangePhoto = findViewById(R.id.tvChangePhoto); tvChangePhoto = findViewById(R.id.tvChangePhoto);
@@ -90,6 +91,9 @@ public class DefinicoesActivity extends AppCompatActivity {
String savedName = prefs.getString("username", ""); String savedName = prefs.getString("username", "");
String savedPhotoUri = prefs.getString("profile_photo_uri", ""); String savedPhotoUri = prefs.getString("profile_photo_uri", "");
boolean savedNotifications = prefs.getBoolean("notifications_enabled", true);
switchNotifications.setChecked(savedNotifications);
if (!savedPhotoUri.isEmpty()) { if (!savedPhotoUri.isEmpty()) {
selectedImageUri = Uri.parse(savedPhotoUri); selectedImageUri = Uri.parse(savedPhotoUri);
try { try {
@@ -108,13 +112,7 @@ public class DefinicoesActivity extends AppCompatActivity {
} }
} }
TextView tvDocuments = findViewById(R.id.tvDocuments);
if (tvDocuments != null) {
tvDocuments.setOnClickListener(v -> {
Intent intent = new Intent(DefinicoesActivity.this, DocumentosActivity.class);
startActivity(intent);
});
}
TextView tvTerms = findViewById(R.id.tvTerms); TextView tvTerms = findViewById(R.id.tvTerms);
if (tvTerms != null) { if (tvTerms != null) {
@@ -149,6 +147,7 @@ public class DefinicoesActivity extends AppCompatActivity {
btnSaveSettings.setOnClickListener(v -> { btnSaveSettings.setOnClickListener(v -> {
SharedPreferences.Editor editor = getSharedPreferences("LifeGridPrefs", Context.MODE_PRIVATE).edit(); SharedPreferences.Editor editor = getSharedPreferences("LifeGridPrefs", Context.MODE_PRIVATE).edit();
editor.putString("username", etUsername.getText().toString().trim()); editor.putString("username", etUsername.getText().toString().trim());
editor.putBoolean("notifications_enabled", switchNotifications.isChecked());
if (selectedImageUri != null) { if (selectedImageUri != null) {
editor.putString("profile_photo_uri", selectedImageUri.toString()); editor.putString("profile_photo_uri", selectedImageUri.toString());
try { try {
@@ -159,7 +158,7 @@ public class DefinicoesActivity extends AppCompatActivity {
} }
editor.apply(); editor.apply();
Toast.makeText(this, "Definições guardadas com sucesso!", Toast.LENGTH_SHORT).show(); CustomToast.success(this, "Definições guardadas com sucesso!");
finish(); finish();
}); });
} }

View File

@@ -8,6 +8,7 @@ import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.example.lifegrid.utils.CustomToast;
import androidx.activity.EdgeToEdge; import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -98,7 +99,7 @@ public class DocumentosActivity extends AppCompatActivity {
@Override @Override
public void onCancelled(@NonNull DatabaseError error) { public void onCancelled(@NonNull DatabaseError error) {
Toast.makeText(DocumentosActivity.this, "Erro ao carregar documentos.", Toast.LENGTH_SHORT).show(); CustomToast.error(DocumentosActivity.this, "Erro ao carregar documentos.");
} }
}); });
} }

View File

@@ -4,26 +4,26 @@ import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.net.Uri; import android.net.Uri;
import android.util.Base64;
import android.util.Log; import android.util.Log;
import com.google.ai.client.generativeai.GenerativeModel; import org.json.JSONArray;
import com.google.ai.client.generativeai.java.GenerativeModelFutures;
import com.google.ai.client.generativeai.type.Content;
import com.google.ai.client.generativeai.type.GenerateContentResponse;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
public class InvoiceScannerHelper { public class InvoiceScannerHelper {
private static final String API_KEY = "AIzaSyCoUZSXfEk43LfPtkCCjsnQ_ZMWX7NG1xQ"; // Substitua pela sua chave API private static final String API_URL = "https://apichat.epvc.pt/api/chat";
private static final String MODEL_NAME = "qwen3.6";
private static final String PROMPT = "Analisa esta fatura. Devolve APENAS um objeto JSON com os campos: 'valor' (Double, apenas números e ponto decimal), 'descricao' (String curta, nome do estabelecimento ou produto), 'categoria' (String, ex: Alimentação, Transporte, Lazer, Saúde, Casa, Educação, Outros), e 'data' (String formato DD/MM/AAAA). Se não conseguires ler algum, coloca valor padrão ou string vazia."; private static final String PROMPT = "Analisa esta fatura. Devolve APENAS um objeto JSON com os campos: 'valor' (Double, apenas números e ponto decimal), 'descricao' (String curta, nome do estabelecimento ou produto), 'categoria' (String, ex: Alimentação, Transporte, Lazer, Saúde, Casa, Educação, Outros), e 'data' (String formato DD/MM/AAAA). Se não conseguires ler algum, coloca valor padrão ou string vazia.";
public interface ScanCallback { public interface ScanCallback {
@@ -32,75 +32,119 @@ public class InvoiceScannerHelper {
} }
public static void scanInvoice(Context context, Uri imageUri, ScanCallback callback) { public static void scanInvoice(Context context, Uri imageUri, ScanCallback callback) {
if (API_KEY == null || API_KEY.isEmpty() || API_KEY.contains("CHAVE_API_KEY")) {
callback.onError("Chave API do Gemini não configurada.");
return;
}
Executor executor = Executors.newSingleThreadExecutor(); Executor executor = Executors.newSingleThreadExecutor();
try (InputStream imageStream = context.getContentResolver().openInputStream(imageUri)) { executor.execute(() -> {
Bitmap bitmap = BitmapFactory.decodeStream(imageStream); try (InputStream imageStream = context.getContentResolver().openInputStream(imageUri)) {
Bitmap originalBitmap = BitmapFactory.decodeStream(imageStream);
if (bitmap == null) { if (originalBitmap == null) {
callback.onError("Não foi possível carregar a imagem da fatura."); callback.onError("Não foi possível carregar a imagem da fatura.");
return; return;
}
GenerativeModel gm = new GenerativeModel(
"gemini-2.5-flash",
API_KEY
);
GenerativeModelFutures model = GenerativeModelFutures.from(gm);
Content content = new Content.Builder()
.addText(PROMPT)
.addImage(bitmap)
.build();
ListenableFuture<GenerateContentResponse> response = model.generateContent(content);
Futures.addCallback(response, new FutureCallback<GenerateContentResponse>() {
@Override
public void onSuccess(GenerateContentResponse result) {
try {
String textResponse = result.getText();
if (textResponse != null) {
textResponse = textResponse.replace("```json", "").replace("```", "").trim();
JSONObject json = new JSONObject(textResponse);
double valor = json.optDouble("valor", 0.0);
String descricao = json.optString("descricao", "");
String categoria = json.optString("categoria", "Outros");
String data = json.optString("data", "");
callback.onSuccess(valor, descricao, categoria, data);
} else {
callback.onError("Resposta vazia da IA.");
}
} catch (JSONException e) {
Log.e("InvoiceScanner", "Erro ao fazer parse do JSON: " + result.getText(), e);
callback.onError("Erro ao ler dados da fatura.");
} finally {
if (executor instanceof java.util.concurrent.ExecutorService) {
((java.util.concurrent.ExecutorService) executor).shutdown();
}
}
} }
@Override // Redimensionar a imagem para otimizar o envio
public void onFailure(Throwable t) { Bitmap bitmap = scaleBitmap(originalBitmap, 1024);
Log.e("InvoiceScanner", "Falha na API do Gemini", t); String base64Image = bitmapToBase64(bitmap);
callback.onError("Falha ao comunicar com a IA: " + t.getMessage());
if (executor instanceof java.util.concurrent.ExecutorService) {
((java.util.concurrent.ExecutorService) executor).shutdown();
}
}
}, executor);
} catch (Exception e) { // Criar o payload JSON para o Ollama
Log.e("InvoiceScanner", "Erro geral", e); JSONObject payload = new JSONObject();
callback.onError("Erro ao processar imagem: " + e.getMessage()); payload.put("model", MODEL_NAME);
if (executor instanceof java.util.concurrent.ExecutorService) { payload.put("stream", false);
((java.util.concurrent.ExecutorService) executor).shutdown(); payload.put("format", "json");
JSONArray messages = new JSONArray();
JSONObject message = new JSONObject();
message.put("role", "user");
message.put("content", PROMPT);
JSONArray images = new JSONArray();
images.put(base64Image);
message.put("images", images);
messages.put(message);
payload.put("messages", messages);
// Enviar a requisição HTTP
URL url = new URL(API_URL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
conn.setRequestProperty("Accept", "application/json");
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setConnectTimeout(60000); // 60 segundos de timeout
conn.setReadTimeout(60000);
try (OutputStream os = conn.getOutputStream()) {
byte[] input = payload.toString().getBytes("utf-8");
os.write(input, 0, input.length);
}
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
StringBuilder response = new StringBuilder();
String responseLine;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
br.close();
JSONObject responseJson = new JSONObject(response.toString());
JSONObject messageObj = responseJson.getJSONObject("message");
String contentStr = messageObj.getString("content");
// Tratar a extração robusta do JSON
int start = contentStr.indexOf("{");
int end = contentStr.lastIndexOf("}");
if (start != -1 && end != -1 && end > start) {
String jsonContent = contentStr.substring(start, end + 1);
JSONObject invoiceJson = new JSONObject(jsonContent);
double valor = invoiceJson.optDouble("valor", 0.0);
String descricao = invoiceJson.optString("descricao", "");
String categoria = invoiceJson.optString("categoria", "Outros");
String data = invoiceJson.optString("data", "");
callback.onSuccess(valor, descricao, categoria, data);
} else {
callback.onError("Não foi possível extrair os dados formatados da fatura.");
}
} else {
callback.onError("Erro ao comunicar com a IA: Servidor respondeu com código " + responseCode);
}
} catch (Exception e) {
Log.e("InvoiceScanner", "Erro geral", e);
callback.onError("Erro ao processar fatura: " + e.getMessage());
} finally {
if (executor instanceof java.util.concurrent.ExecutorService) {
((java.util.concurrent.ExecutorService) executor).shutdown();
}
} }
});
}
private static Bitmap scaleBitmap(Bitmap bitmap, int maxDimension) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
if (width <= maxDimension && height <= maxDimension) {
return bitmap;
} }
float ratio = (float) width / (float) height;
int newWidth, newHeight;
if (width > height) {
newWidth = maxDimension;
newHeight = Math.round(maxDimension / ratio);
} else {
newHeight = maxDimension;
newWidth = Math.round(maxDimension * ratio);
}
return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
}
private static String bitmapToBase64(Bitmap bitmap) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream);
byte[] byteArray = outputStream.toByteArray();
return Base64.encodeToString(byteArray, Base64.NO_WRAP);
} }
} }

View File

@@ -14,6 +14,7 @@ import android.widget.EditText;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.example.lifegrid.utils.CustomToast;
import androidx.activity.EdgeToEdge; import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -95,7 +96,7 @@ public class LoginActivity extends AppCompatActivity {
if (TextUtils.isEmpty(email)) { if (TextUtils.isEmpty(email)) {
emailEditText.setError("Por favor, digite o seu e-mail primeiro."); emailEditText.setError("Por favor, digite o seu e-mail primeiro.");
emailEditText.requestFocus(); emailEditText.requestFocus();
Toast.makeText(LoginActivity.this, "Digite o seu e-mail para recuperar a palavra-passe.", Toast.LENGTH_SHORT).show(); CustomToast.info(LoginActivity.this, "Digite o seu e-mail para recuperar a palavra-passe.");
return; return;
} }
@@ -145,7 +146,7 @@ public class LoginActivity extends AppCompatActivity {
String welcome = user != null && !TextUtils.isEmpty(user.getEmail()) String welcome = user != null && !TextUtils.isEmpty(user.getEmail())
? "Bem-vindo, " + user.getEmail() ? "Bem-vindo, " + user.getEmail()
: "Login realizado com sucesso!"; : "Login realizado com sucesso!";
Toast.makeText(this, welcome, Toast.LENGTH_SHORT).show(); CustomToast.success(this, welcome);
// Redirecionar para TelaInicialActivity após login bem-sucedido // Redirecionar para TelaInicialActivity após login bem-sucedido
Intent intent = new Intent(LoginActivity.this, TelaInicialActivity.class); Intent intent = new Intent(LoginActivity.this, TelaInicialActivity.class);
@@ -153,9 +154,8 @@ public class LoginActivity extends AppCompatActivity {
startActivity(intent); startActivity(intent);
finish(); // Fechar LoginActivity para não poder voltar com back button finish(); // Fechar LoginActivity para não poder voltar com back button
} else { } else {
Toast.makeText(this, CustomToast.error(this,
task.getException() != null ? task.getException().getMessage() : "Falha no login", task.getException() != null ? task.getException().getMessage() : "Falha no login");
Toast.LENGTH_LONG).show();
} }
}); });
} }
@@ -173,13 +173,11 @@ public class LoginActivity extends AppCompatActivity {
.addOnCompleteListener(this, task -> { .addOnCompleteListener(this, task -> {
toggleLoading(false); toggleLoading(false);
if (task.isSuccessful()) { if (task.isSuccessful()) {
Toast.makeText(this, CustomToast.success(this,
"Email de recuperação enviado para " + email, "Email de recuperação enviado para " + email);
Toast.LENGTH_LONG).show();
} else { } else {
Toast.makeText(this, CustomToast.error(this,
task.getException() != null ? task.getException().getMessage() : "Erro ao enviar email", task.getException() != null ? task.getException().getMessage() : "Erro ao enviar email");
Toast.LENGTH_LONG).show();
} }
}); });
} }

View File

@@ -8,6 +8,8 @@ import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.Toast; import android.widget.Toast;
import android.widget.ImageView;
import com.example.lifegrid.utils.CustomToast;
import androidx.activity.EdgeToEdge; import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
@@ -58,6 +60,9 @@ public class RecupearPasswordActivity extends AppCompatActivity {
} }
loginButton3.setOnClickListener(v -> recuperarPassword()); loginButton3.setOnClickListener(v -> recuperarPassword());
ImageView btnBack = findViewById(R.id.btnBack);
btnBack.setOnClickListener(v -> finish());
} }
/** /**
@@ -92,9 +97,8 @@ public class RecupearPasswordActivity extends AppCompatActivity {
toggleLoading(false); toggleLoading(false);
if (task.isSuccessful()) { if (task.isSuccessful()) {
// Email enviado com sucesso // Email enviado com sucesso
Toast.makeText(this, CustomToast.success(this,
"Email de recuperação enviado para " + email + "\nVerifique sua caixa de entrada.", "Email de recuperação enviado para " + email + "\nVerifique sua caixa de entrada.");
Toast.LENGTH_LONG).show();
// Limpar o campo após sucesso // Limpar o campo após sucesso
emailEditText4.setText(""); emailEditText4.setText("");
} else { } else {
@@ -113,7 +117,7 @@ public class RecupearPasswordActivity extends AppCompatActivity {
errorMessage = "Muitas tentativas. Tente novamente mais tarde."; errorMessage = "Muitas tentativas. Tente novamente mais tarde.";
} }
} }
Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show(); CustomToast.error(this, errorMessage);
} }
}); });
} }

View File

@@ -25,6 +25,7 @@ import android.widget.AdapterView;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.Toast; import android.widget.Toast;
import com.example.lifegrid.utils.CustomToast;
import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser; import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.DatabaseReference;
@@ -155,7 +156,7 @@ public class TelaInicialActivity extends AppCompatActivity {
} }
private void processInvoiceImage(android.net.Uri imageUri) { private void processInvoiceImage(android.net.Uri imageUri) {
Toast.makeText(this, "A processar fatura com IA...", Toast.LENGTH_LONG).show(); CustomToast.info(this, "A processar fatura com IA...");
InvoiceScannerHelper.scanInvoice(this, imageUri, new InvoiceScannerHelper.ScanCallback() { InvoiceScannerHelper.scanInvoice(this, imageUri, new InvoiceScannerHelper.ScanCallback() {
@Override @Override
public void onSuccess(double valor, String descricao, String categoria, String data) { public void onSuccess(double valor, String descricao, String categoria, String data) {
@@ -174,49 +175,26 @@ public class TelaInicialActivity extends AppCompatActivity {
} }
transFragment.showNovaTransacaoDialog(valor, descricao, categoria, data); transFragment.showNovaTransacaoDialog(valor, descricao, categoria, data);
// Save document to Firebase deleteLocalInvoiceFile(imageUri);
uploadDocumentoToFirebase(imageUri, descricao, data);
}); });
} }
@Override @Override
public void onError(String error) { public void onError(String error) {
runOnUiThread(() -> Toast.makeText(TelaInicialActivity.this, error, Toast.LENGTH_LONG).show()); runOnUiThread(() -> {
CustomToast.error(TelaInicialActivity.this, error);
deleteLocalInvoiceFile(imageUri);
});
} }
}); });
} }
private void uploadDocumentoToFirebase(android.net.Uri imageUri, String descricao, String data) { private void deleteLocalInvoiceFile(android.net.Uri uri) {
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); try {
if (user == null) { getContentResolver().delete(uri, null, null);
Toast.makeText(this, "Utilizador não autenticado.", Toast.LENGTH_SHORT).show(); } catch (Exception e) {
return; e.printStackTrace();
} }
Toast.makeText(this, "A guardar documento na Cloud...", Toast.LENGTH_SHORT).show();
String fileName = "invoice_" + System.currentTimeMillis() + ".jpg";
com.google.firebase.storage.StorageReference storageRef = com.google.firebase.storage.FirebaseStorage.getInstance().getReference()
.child("users/" + user.getUid() + "/documentos/" + fileName);
storageRef.putFile(imageUri).addOnSuccessListener(taskSnapshot -> {
storageRef.getDownloadUrl().addOnSuccessListener(uri -> {
String downloadUrl = uri.toString();
DatabaseReference dbRef = FirebaseDatabase.getInstance().getReference("users")
.child(user.getUid()).child("documentos").push();
com.example.lifegrid.models.Documento doc = new com.example.lifegrid.models.Documento(descricao, data, downloadUrl);
dbRef.setValue(doc).addOnCompleteListener(task -> {
if (task.isSuccessful()) {
Toast.makeText(TelaInicialActivity.this, "Documento guardado com sucesso!", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(TelaInicialActivity.this, "Erro ao guardar dados na Cloud.", Toast.LENGTH_SHORT).show();
}
});
});
}).addOnFailureListener(e -> {
Toast.makeText(this, "Erro ao enviar imagem para a Cloud.", Toast.LENGTH_SHORT).show();
});
} }
@Override @Override

View File

@@ -10,6 +10,7 @@ import android.view.ViewGroup;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.example.lifegrid.utils.CustomToast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@@ -47,7 +48,7 @@ public class DocumentoAdapter extends RecyclerView.Adapter<DocumentoAdapter.Docu
if (url != null && !url.isEmpty()) { if (url != null && !url.isEmpty()) {
downloadFile(url, holder.tvDescricao.getText().toString()); downloadFile(url, holder.tvDescricao.getText().toString());
} else { } else {
Toast.makeText(context, "URL inválido para o documento.", Toast.LENGTH_SHORT).show(); CustomToast.error(context, "URL inválido para o documento.");
} }
}); });
} }
@@ -70,11 +71,11 @@ public class DocumentoAdapter extends RecyclerView.Adapter<DocumentoAdapter.Docu
DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
if (manager != null) { if (manager != null) {
manager.enqueue(request); manager.enqueue(request);
Toast.makeText(context, "Transferência iniciada. Verifique as notificações.", Toast.LENGTH_SHORT).show(); CustomToast.info(context, "Transferência iniciada. Verifique as notificações.");
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
Toast.makeText(context, "Erro ao iniciar transferência.", Toast.LENGTH_SHORT).show(); CustomToast.error(context, "Erro ao iniciar transferência.");
} }
} }

View File

@@ -8,6 +8,7 @@ import android.widget.ImageView;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.example.lifegrid.utils.CustomToast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@@ -106,7 +107,7 @@ public class MetaAdapter extends RecyclerView.Adapter<MetaAdapter.MetaViewHolder
double valorAdicional = Double.parseDouble(input); double valorAdicional = Double.parseDouble(input);
listener.onAddValueClick(meta, valorAdicional, holder.etAdicionarValor); listener.onAddValueClick(meta, valorAdicional, holder.etAdicionarValor);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Toast.makeText(v.getContext(), "Valor inválido", Toast.LENGTH_SHORT).show(); CustomToast.error(v.getContext(), "Valor inválido");
} }
} }
} }

View File

@@ -19,6 +19,7 @@ import android.widget.EditText;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.Toast; import android.widget.Toast;
import com.example.lifegrid.utils.CustomToast;
import java.util.Calendar; import java.util.Calendar;
import java.util.Locale; import java.util.Locale;
@@ -159,18 +160,18 @@ public class AtivosFragment extends Fragment {
@Override @Override
public void onCancelled(@NonNull DatabaseError error) { public void onCancelled(@NonNull DatabaseError error) {
Toast.makeText(requireContext(), "Erro ao carregar ativos.", Toast.LENGTH_SHORT).show(); CustomToast.error(requireContext(), "Erro ao carregar ativos.");
} }
}); });
} }
private void refreshAtivoPrice(Ativos ativo, String key) { private void refreshAtivoPrice(Ativos ativo, String key) {
if (ativo.getTicker() == null || ativo.getTicker().isEmpty()) { if (ativo.getTicker() == null || ativo.getTicker().isEmpty()) {
Toast.makeText(requireContext(), "Este ativo não tem um Ticker definido.", Toast.LENGTH_SHORT).show(); CustomToast.info(requireContext(), "Este ativo não tem um Ticker definido.");
return; return;
} }
Toast.makeText(requireContext(), "A atualizar " + ativo.getTicker() + "...", Toast.LENGTH_SHORT).show(); CustomToast.info(requireContext(), "A atualizar " + ativo.getTicker() + "...");
ExecutorService executor = Executors.newSingleThreadExecutor(); ExecutorService executor = Executors.newSingleThreadExecutor();
Handler handler = new Handler(Looper.getMainLooper()); Handler handler = new Handler(Looper.getMainLooper());
@@ -205,14 +206,14 @@ public class AtivosFragment extends Fragment {
handler.post(() -> { handler.post(() -> {
String userId = FirebaseAuth.getInstance().getCurrentUser().getUid(); String userId = FirebaseAuth.getInstance().getCurrentUser().getUid();
databaseReference.child("users").child(userId).child("ativos").child(key).child("precoAtual").setValue(regularMarketPrice); databaseReference.child("users").child(userId).child("ativos").child(key).child("precoAtual").setValue(regularMarketPrice);
Toast.makeText(requireContext(), "Preço atualizado: " + regularMarketPrice + "", Toast.LENGTH_SHORT).show(); CustomToast.success(requireContext(), "Preço atualizado: " + regularMarketPrice + "");
}); });
} else { } else {
handler.post(() -> Toast.makeText(requireContext(), "Erro ao contactar a API.", Toast.LENGTH_SHORT).show()); handler.post(() -> CustomToast.error(requireContext(), "Erro ao contactar a API."));
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
handler.post(() -> Toast.makeText(requireContext(), "Ativo não encontrado ou erro de rede.", Toast.LENGTH_SHORT).show()); handler.post(() -> CustomToast.error(requireContext(), "Ativo não encontrado ou erro de rede."));
} }
}); });
} }
@@ -283,9 +284,9 @@ public class AtivosFragment extends Fragment {
boolean isCriptomoeda = "Criptomoedas".equals(tipo); boolean isCriptomoeda = "Criptomoedas".equals(tipo);
if (nome.isEmpty() || quantidade.isEmpty() || precoCompra.isEmpty() || dataCompra.isEmpty() || spinnerTipoAtivo.getSelectedItemPosition() == 0) { if (nome.isEmpty() || quantidade.isEmpty() || precoCompra.isEmpty() || dataCompra.isEmpty() || spinnerTipoAtivo.getSelectedItemPosition() == 0) {
Toast.makeText(requireContext(), "Por favor, preencha os campos obrigatórios.", Toast.LENGTH_SHORT).show(); CustomToast.info(requireContext(), "Por favor, preencha os campos obrigatórios.");
} else if (isCriptomoeda && ticker.isEmpty()) { } else if (isCriptomoeda && ticker.isEmpty()) {
Toast.makeText(requireContext(), "Por favor, preencha o Símbolo da Criptomoeda.", Toast.LENGTH_SHORT).show(); CustomToast.info(requireContext(), "Por favor, preencha o Símbolo da Criptomoeda.");
} else { } else {
Ativos ativos = new Ativos(nome, quantidade, Double.parseDouble(precoCompra), Double.parseDouble(precoCompra), dataCompra, tipo, ticker); Ativos ativos = new Ativos(nome, quantidade, Double.parseDouble(precoCompra), Double.parseDouble(precoCompra), dataCompra, tipo, ticker);
@@ -298,7 +299,7 @@ public class AtivosFragment extends Fragment {
dialog.dismiss(); dialog.dismiss();
Toast.makeText(requireContext(), "Ativo adicionado com sucesso!", Toast.LENGTH_SHORT).show(); CustomToast.success(requireContext(), "Ativo adicionado com sucesso!");
} }
}); });

View File

@@ -10,6 +10,7 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.example.lifegrid.utils.CustomToast;
import com.example.lifegrid.R; import com.example.lifegrid.R;
import com.example.lifegrid.models.Transacao; import com.example.lifegrid.models.Transacao;
@@ -135,9 +136,7 @@ public class GraficosFragment extends Fragment {
@Override @Override
public void onCancelled(@NonNull DatabaseError error) { public void onCancelled(@NonNull DatabaseError error) {
if (getContext() != null) { CustomToast.error(requireContext(), "Erro ao carregar transações.");
Toast.makeText(getContext(), "Erro ao carregar transações.", Toast.LENGTH_SHORT).show();
}
} }
}); });
} }

View File

@@ -10,6 +10,7 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.example.lifegrid.utils.CustomToast;
import com.example.lifegrid.R; import com.example.lifegrid.R;
import com.example.lifegrid.models.Transacao; import com.example.lifegrid.models.Transacao;
@@ -218,7 +219,7 @@ public class HomeFragment extends Fragment {
@Override @Override
public void onCancelled(@NonNull DatabaseError error) { public void onCancelled(@NonNull DatabaseError error) {
if (isAdded()) { if (isAdded()) {
Toast.makeText(getContext(), "Erro ao carregar dados", Toast.LENGTH_SHORT).show(); CustomToast.error(requireContext(), "Erro ao carregar dados");
} }
} }
}); });

View File

@@ -16,6 +16,7 @@ import android.widget.EditText;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.example.lifegrid.utils.CustomToast;
import java.util.Calendar; import java.util.Calendar;
import java.util.Locale; import java.util.Locale;
@@ -87,7 +88,7 @@ public class MetasFragment extends Fragment {
.setPositiveButton("Sim", (dialog, which) -> { .setPositiveButton("Sim", (dialog, which) -> {
if (databaseReference != null && userId != null && meta.getId() != null) { if (databaseReference != null && userId != null && meta.getId() != null) {
databaseReference.child("users").child(userId).child("metas").child(meta.getId()).removeValue(); databaseReference.child("users").child(userId).child("metas").child(meta.getId()).removeValue();
Toast.makeText(requireContext(), "Meta excluída.", Toast.LENGTH_SHORT).show(); CustomToast.success(requireContext(), "Meta excluída.");
} }
}) })
.setNegativeButton("Não", null) .setNegativeButton("Não", null)
@@ -117,8 +118,17 @@ public class MetasFragment extends Fragment {
databaseReference.child("users").child(userId).child("transacoes").child(key).setValue(transacao); databaseReference.child("users").child(userId).child("transacoes").child(key).setValue(transacao);
} }
double targetVal = 0;
try {
targetVal = Double.parseDouble(meta.getValor().replace(",", "."));
} catch (Exception ignored) {}
editText.setText(""); editText.setText("");
Toast.makeText(requireContext(), "Valor adicionado e despesa registada!", Toast.LENGTH_SHORT).show(); if (targetVal > 0 && novoValor >= targetVal) {
CustomToast.success(requireContext(), "Parabéns! Alcançou a sua meta: " + meta.getNome() + "!");
} else {
CustomToast.success(requireContext(), "Valor adicionado e despesa registada!");
}
} }
} }
}); });
@@ -161,7 +171,7 @@ public class MetasFragment extends Fragment {
@Override @Override
public void onCancelled(@NonNull DatabaseError error) { public void onCancelled(@NonNull DatabaseError error) {
Toast.makeText(requireContext(), "Erro ao carregar metas.", Toast.LENGTH_SHORT).show(); CustomToast.error(requireContext(), "Erro ao carregar metas.");
} }
}); });
} }
@@ -212,7 +222,7 @@ public class MetasFragment extends Fragment {
if (nome.isEmpty() || categoria.isEmpty() || valor.isEmpty() || data.isEmpty()) { if (nome.isEmpty() || categoria.isEmpty() || valor.isEmpty() || data.isEmpty()) {
Toast.makeText(requireContext(), "Por favor, preencha todos os campos.", Toast.LENGTH_SHORT).show(); CustomToast.info(requireContext(), "Por favor, preencha todos os campos.");
} else { } else {
// Aqui seria a lógica para guardar na Firebase // Aqui seria a lógica para guardar na Firebase
DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference(); DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference();
@@ -223,7 +233,7 @@ public class MetasFragment extends Fragment {
dialog.dismiss(); dialog.dismiss();
Toast.makeText(requireContext(), "Meta criada com sucesso!", Toast.LENGTH_SHORT).show(); CustomToast.success(requireContext(), "Meta criada com sucesso!");
} }
}); });

View File

@@ -20,6 +20,7 @@ import android.widget.ImageView;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.example.lifegrid.utils.CustomToast;
import java.util.Calendar; import java.util.Calendar;
import java.util.Locale; import java.util.Locale;
@@ -108,7 +109,7 @@ public class TransacoesFragment extends Fragment {
.setPositiveButton("Sim", (dialog, which) -> { .setPositiveButton("Sim", (dialog, which) -> {
if (databaseReference != null && userId != null && transacao.getId() != null) { if (databaseReference != null && userId != null && transacao.getId() != null) {
databaseReference.child("users").child(userId).child("transacoes").child(transacao.getId()).removeValue(); databaseReference.child("users").child(userId).child("transacoes").child(transacao.getId()).removeValue();
Toast.makeText(requireContext(), "Transação excluída.", Toast.LENGTH_SHORT).show(); CustomToast.success(requireContext(), "Transação excluída.");
} }
}) })
.setNegativeButton("Não", null) .setNegativeButton("Não", null)
@@ -144,7 +145,7 @@ public class TransacoesFragment extends Fragment {
@Override @Override
public void onCancelled(@NonNull DatabaseError error) { public void onCancelled(@NonNull DatabaseError error) {
Toast.makeText(requireContext(), "Erro ao carregar transações.", Toast.LENGTH_SHORT).show(); CustomToast.error(requireContext(), "Erro ao carregar transações.");
} }
}); });
} }
@@ -295,7 +296,7 @@ public class TransacoesFragment extends Fragment {
if (valor.isEmpty() || descricao.isEmpty() || data.isEmpty() || spinnerCategoria.getSelectedItemPosition() == 0) { if (valor.isEmpty() || descricao.isEmpty() || data.isEmpty() || spinnerCategoria.getSelectedItemPosition() == 0) {
Toast.makeText(requireContext(), "Por favor, preencha todos os campos.", Toast.LENGTH_SHORT).show(); CustomToast.info(requireContext(), "Por favor, preencha todos os campos.");
} else { } else {
DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference(); DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference();
String userId = FirebaseAuth.getInstance().getCurrentUser().getUid(); String userId = FirebaseAuth.getInstance().getCurrentUser().getUid();
@@ -305,7 +306,7 @@ public class TransacoesFragment extends Fragment {
dialog.dismiss(); dialog.dismiss();
Toast.makeText(requireContext(), "Transação adicionada com sucesso!", Toast.LENGTH_SHORT).show(); CustomToast.success(requireContext(), "Transação adicionada com sucesso!");
} }
}); });

View File

@@ -0,0 +1,67 @@
package com.example.lifegrid.utils;
import android.content.Context;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.example.lifegrid.R;
public class CustomToast {
public enum Type {
SUCCESS,
ERROR,
INFO
}
public static void show(Context context, String message, Type type) {
if (context == null) return;
try {
LayoutInflater inflater = LayoutInflater.from(context);
View layout = inflater.inflate(R.layout.custom_toast, null);
ImageView iconView = layout.findViewById(R.id.toast_icon);
TextView textView = layout.findViewById(R.id.toast_text);
textView.setText(message);
switch (type) {
case SUCCESS:
iconView.setImageResource(R.drawable.ic_toast_success);
break;
case ERROR:
iconView.setImageResource(R.drawable.ic_toast_error);
break;
case INFO:
default:
iconView.setImageResource(R.drawable.ic_toast_info);
break;
}
Toast toast = new Toast(context.getApplicationContext());
toast.setDuration(Toast.LENGTH_SHORT);
toast.setView(layout);
toast.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, 150);
toast.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void success(Context context, String message) {
show(context, message, Type.SUCCESS);
}
public static void error(Context context, String message) {
show(context, message, Type.ERROR);
}
public static void info(Context context, String message) {
show(context, message, Type.INFO);
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#1A1A1A" />
<corners android:radius="24dp" />
<stroke android:width="1dp" android:color="#333333" />
</shape>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#E74C3C"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2V7h2v6z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#3498DB"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2V7h2v2z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#2ECC71"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/>
</vector>

View File

@@ -8,6 +8,19 @@
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".CriarContaActivity"> tools:context=".CriarContaActivity">
<ImageView
android:id="@+id/btnBack"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:src="@drawable/diagonalarrowleftdownoutline_110924"
app:tint="@color/preto"
android:rotation="45"
android:padding="4dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView <ImageView
android:id="@+id/logoImageView" android:id="@+id/logoImageView"
android:layout_width="213dp" android:layout_width="213dp"

View File

@@ -149,33 +149,6 @@
android:checked="true" /> android:checked="true" />
</LinearLayout> </LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#E0E0E0"
android:layout_marginVertical="12dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="Moeda Base"
android:textColor="@color/preto"
android:textSize="16sp" />
<Spinner
android:id="@+id/spinnerCurrency"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:background="@drawable/rounded_input_bg"
android:entries="@array/moedas_array" />
</LinearLayout>
</LinearLayout> </LinearLayout>
<!-- Extras Section --> <!-- Extras Section -->
@@ -196,21 +169,6 @@
android:padding="16dp" android:padding="16dp"
android:layout_marginBottom="32dp"> android:layout_marginBottom="32dp">
<TextView
android:id="@+id/tvDocuments"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Meus Documentos (Faturas)"
android:textColor="@color/preto"
android:textSize="16sp"
android:textStyle="bold"
android:paddingVertical="8dp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#E0E0E0"
android:layout_marginVertical="4dp" />
<TextView <TextView
android:id="@+id/tvTerms" android:id="@+id/tvTerms"

View File

@@ -8,6 +8,19 @@
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".RecupearPasswordActivity"> tools:context=".RecupearPasswordActivity">
<ImageView
android:id="@+id/btnBack"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:src="@drawable/diagonalarrowleftdownoutline_110924"
app:tint="@color/preto"
android:rotation="45"
android:padding="4dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button <Button
android:id="@+id/loginButton3" android:id="@+id/loginButton3"
android:layout_width="315dp" android:layout_width="315dp"

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginHorizontal="24dp"
android:background="@drawable/custom_toast_bg"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="16dp"
android:paddingVertical="10dp">
<ImageView
android:id="@+id/toast_icon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="10dp"
android:contentDescription="Toast status icon" />
<TextView
android:id="@+id/toast_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="260dp"
android:textColor="#FFFFFF"
android:textSize="14sp"
android:fontFamily="sans-serif-medium" />
</LinearLayout>

View File

@@ -7,7 +7,7 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/adicionarCardView" android:id="@+id/adicionarCardView"
android:layout_width="364dp" android:layout_width="391dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginTop="544dp" android:layout_marginTop="544dp"
android:background="@drawable/cardview_background" android:background="@drawable/cardview_background"

View File

@@ -54,18 +54,19 @@
tools:text="Criptomoedas" /> tools:text="Criptomoedas" />
<!-- Quantity and Purchase Price --> <!-- Quantity and Purchase Price -->
<!-- Delete Button -->
<TextView <TextView
android:id="@+id/tvInfo" android:id="@+id/tvInfo"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="12dp"
android:textColor="#6b7280" android:textColor="#6b7280"
android:textSize="14sp" android:textSize="14sp"
app:layout_constraintStart_toStartOf="@id/tvNome" app:layout_constraintStart_toStartOf="@id/tvNome"
app:layout_constraintTop_toBottomOf="@id/tvTipo" app:layout_constraintTop_toBottomOf="@id/tvTipo"
tools:text="12 unidades x 16.10€" /> tools:text="12 unidades x 16.10€" />
<!-- Delete Button -->
<ImageView <ImageView
android:id="@+id/btnDelete" android:id="@+id/btnDelete"
android:layout_width="28dp" android:layout_width="28dp"
@@ -111,6 +112,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:layout_marginEnd="8dp"
android:textColor="#16a34a" android:textColor="#16a34a"
android:textSize="14sp" android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"

View File

@@ -5,5 +5,22 @@
<!-- <item name="colorPrimary">@color/my_light_primary</item> --> <!-- <item name="colorPrimary">@color/my_light_primary</item> -->
</style> </style>
<style name="Theme.LifeGrid" parent="Base.Theme.LifeGrid" /> <style name="Theme.LifeGrid" parent="Base.Theme.LifeGrid">
<item name="android:spinnerItemStyle">@style/SpinnerItemStyle</item>
<item name="android:spinnerDropDownItemStyle">@style/SpinnerDropDownItemStyle</item>
<item name="android:spinnerStyle">@style/SpinnerStyle</item>
<item name="spinnerStyle">@style/SpinnerStyle</item>
</style>
<style name="SpinnerStyle" parent="@android:style/Widget.Spinner">
<item name="android:popupBackground">@color/branco</item>
</style>
<style name="SpinnerItemStyle" parent="@android:style/Widget.TextView.SpinnerItem">
<item name="android:textColor">@color/preto</item>
</style>
<style name="SpinnerDropDownItemStyle" parent="@android:style/Widget.DropDownItem.Spinner">
<item name="android:textColor">@color/preto</item>
</style>
</resources> </resources>