Compare commits
9 Commits
533f164b0f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a1a01e27df | |||
| 31034a3465 | |||
| 75ba56cf74 | |||
| 55560a1bfb | |||
| 04ce7ece4f | |||
| 4356a7432e | |||
| 196487a2e2 | |||
| 53f77a852e | |||
| 21c1e472ba |
33
.idea/deploymentTargetSelector.xml
generated
33
.idea/deploymentTargetSelector.xml
generated
@@ -3,6 +3,39 @@
|
||||
<component name="deploymentTargetSelector">
|
||||
<selectionStates>
|
||||
<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" />
|
||||
<DropdownSelection timestamp="2026-06-03T14:39:06.554406Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
|
||||
@@ -7,8 +7,10 @@ import android.util.Patterns;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Toast;
|
||||
import com.example.lifegrid.utils.CustomToast;
|
||||
|
||||
import androidx.activity.EdgeToEdge;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
@@ -16,24 +18,9 @@ import androidx.core.graphics.Insets;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
|
||||
import androidx.credentials.Credential;
|
||||
import androidx.credentials.CustomCredential;
|
||||
import androidx.credentials.GetCredentialRequest;
|
||||
import androidx.credentials.GetCredentialResponse;
|
||||
import androidx.credentials.exceptions.GetCredentialException;
|
||||
import androidx.credentials.exceptions.NoCredentialException;
|
||||
import androidx.credentials.CredentialManager;
|
||||
|
||||
import com.google.android.libraries.identity.googleid.GetGoogleIdOption;
|
||||
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential;
|
||||
import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException;
|
||||
import com.google.firebase.FirebaseApp;
|
||||
import com.google.firebase.auth.AuthCredential;
|
||||
import com.google.firebase.auth.FirebaseAuth;
|
||||
import com.google.firebase.auth.FirebaseUser;
|
||||
import com.google.firebase.auth.GoogleAuthProvider;
|
||||
|
||||
import com.google.firebase.auth.GoogleAuthProvider;
|
||||
|
||||
/**
|
||||
* CriarContaActivity é a atividade responsável por gerir o registo de novos utilizadores na aplicação LifeGrid.
|
||||
@@ -47,10 +34,8 @@ public class CriarContaActivity extends AppCompatActivity {
|
||||
private EditText passwordEditText3;
|
||||
private EditText passwordEditText2;
|
||||
private Button loginButton2;
|
||||
private Button googleButton2;
|
||||
private ProgressBar loadingProgressBar;
|
||||
private FirebaseAuth firebaseAuth;
|
||||
private CredentialManager credentialManager;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -68,15 +53,16 @@ public class CriarContaActivity extends AppCompatActivity {
|
||||
passwordEditText3 = findViewById(R.id.passwordEditText3);
|
||||
passwordEditText2 = findViewById(R.id.passwordEditText2);
|
||||
loginButton2 = findViewById(R.id.loginButton2);
|
||||
googleButton2 = findViewById(R.id.googleButton2);
|
||||
loadingProgressBar = findViewById(R.id.loadingProgressBar);
|
||||
|
||||
FirebaseApp.initializeApp(this);
|
||||
firebaseAuth = FirebaseAuth.getInstance();
|
||||
credentialManager = CredentialManager.create(this);
|
||||
|
||||
// Configura o botão de registo para acionar a validação e criação de conta
|
||||
loginButton2.setOnClickListener(v -> criarConta());
|
||||
|
||||
ImageView btnBack = findViewById(R.id.btnBack);
|
||||
btnBack.setOnClickListener(v -> finish());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,9 +88,7 @@ public class CriarContaActivity extends AppCompatActivity {
|
||||
FirebaseUser user = firebaseAuth.getCurrentUser();
|
||||
if (user != null) {
|
||||
// Conta criada com sucesso - redirecionar para TelaInicialActivity
|
||||
Toast.makeText(this,
|
||||
"Conta criada com sucesso! Bem-vindo, " + nome,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
CustomToast.success(this, "Conta criada com sucesso! Bem-vindo, " + nome);
|
||||
|
||||
// Redirecionar para TelaInicialActivity apenas após sucesso
|
||||
Intent intent = new Intent(CriarContaActivity.this, TelaInicialActivity.class);
|
||||
@@ -126,7 +110,7 @@ public class CriarContaActivity extends AppCompatActivity {
|
||||
errorMessage = "Palavra-passe muito fraca. Use uma palavra-passe mais forte.";
|
||||
}
|
||||
}
|
||||
Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show();
|
||||
CustomToast.error(this, errorMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -191,35 +175,9 @@ public class CriarContaActivity extends AppCompatActivity {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void firebaseAuthWithGoogle(String idToken) {
|
||||
AuthCredential credential = GoogleAuthProvider.getCredential(idToken, null);
|
||||
firebaseAuth.signInWithCredential(credential)
|
||||
.addOnCompleteListener(this, task -> {
|
||||
toggleLoading(false);
|
||||
if (task.isSuccessful()) {
|
||||
FirebaseUser user = firebaseAuth.getCurrentUser();
|
||||
String welcome = user != null && !TextUtils.isEmpty(user.getDisplayName())
|
||||
? "Conta criada com sucesso! Bem-vindo, " + user.getDisplayName()
|
||||
: "Conta criada com Google com sucesso!";
|
||||
Toast.makeText(this, welcome, Toast.LENGTH_SHORT).show();
|
||||
|
||||
// Redirecionar para TelaInicialActivity após criação bem-sucedida
|
||||
Intent intent = new Intent(CriarContaActivity.this, TelaInicialActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
} else {
|
||||
Toast.makeText(this,
|
||||
task.getException() != null ? task.getException().getMessage() : "Falha ao criar conta com Google",
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void toggleLoading(boolean show) {
|
||||
loadingProgressBar.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
loginButton2.setEnabled(!show);
|
||||
googleButton2.setEnabled(!show);
|
||||
nomeEditText.setEnabled(!show);
|
||||
emailEditText2.setEnabled(!show);
|
||||
passwordEditText3.setEnabled(!show);
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.widget.Spinner;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import com.example.lifegrid.utils.CustomToast;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.content.FileProvider;
|
||||
@@ -36,7 +37,7 @@ public class DefinicoesActivity extends AppCompatActivity {
|
||||
private Button btnSaveSettings;
|
||||
private EditText etUsername;
|
||||
private Switch switchNotifications;
|
||||
private Spinner spinnerCurrency;
|
||||
|
||||
|
||||
private ImageView ivProfilePicture;
|
||||
private TextView tvChangePhoto;
|
||||
@@ -62,7 +63,7 @@ public class DefinicoesActivity extends AppCompatActivity {
|
||||
btnSaveSettings = findViewById(R.id.btnSaveSettings);
|
||||
etUsername = findViewById(R.id.etUsername);
|
||||
switchNotifications = findViewById(R.id.switchNotifications);
|
||||
spinnerCurrency = findViewById(R.id.spinnerCurrency);
|
||||
|
||||
|
||||
ivProfilePicture = findViewById(R.id.ivProfilePicture);
|
||||
tvChangePhoto = findViewById(R.id.tvChangePhoto);
|
||||
@@ -90,6 +91,9 @@ public class DefinicoesActivity extends AppCompatActivity {
|
||||
String savedName = prefs.getString("username", "");
|
||||
String savedPhotoUri = prefs.getString("profile_photo_uri", "");
|
||||
|
||||
boolean savedNotifications = prefs.getBoolean("notifications_enabled", true);
|
||||
switchNotifications.setChecked(savedNotifications);
|
||||
|
||||
if (!savedPhotoUri.isEmpty()) {
|
||||
selectedImageUri = Uri.parse(savedPhotoUri);
|
||||
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);
|
||||
if (tvTerms != null) {
|
||||
@@ -149,6 +147,7 @@ public class DefinicoesActivity extends AppCompatActivity {
|
||||
btnSaveSettings.setOnClickListener(v -> {
|
||||
SharedPreferences.Editor editor = getSharedPreferences("LifeGridPrefs", Context.MODE_PRIVATE).edit();
|
||||
editor.putString("username", etUsername.getText().toString().trim());
|
||||
editor.putBoolean("notifications_enabled", switchNotifications.isChecked());
|
||||
if (selectedImageUri != null) {
|
||||
editor.putString("profile_photo_uri", selectedImageUri.toString());
|
||||
try {
|
||||
@@ -159,7 +158,7 @@ public class DefinicoesActivity extends AppCompatActivity {
|
||||
}
|
||||
editor.apply();
|
||||
|
||||
Toast.makeText(this, "Definições guardadas com sucesso!", Toast.LENGTH_SHORT).show();
|
||||
CustomToast.success(this, "Definições guardadas com sucesso!");
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import com.example.lifegrid.utils.CustomToast;
|
||||
|
||||
import androidx.activity.EdgeToEdge;
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -98,7 +99,7 @@ public class DocumentosActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
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.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,26 +4,26 @@ import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.ai.client.generativeai.GenerativeModel;
|
||||
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.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
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.Executors;
|
||||
|
||||
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.";
|
||||
|
||||
public interface ScanCallback {
|
||||
@@ -32,75 +32,119 @@ public class InvoiceScannerHelper {
|
||||
}
|
||||
|
||||
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.execute(() -> {
|
||||
try (InputStream imageStream = context.getContentResolver().openInputStream(imageUri)) {
|
||||
Bitmap bitmap = BitmapFactory.decodeStream(imageStream);
|
||||
Bitmap originalBitmap = BitmapFactory.decodeStream(imageStream);
|
||||
|
||||
if (bitmap == null) {
|
||||
if (originalBitmap == null) {
|
||||
callback.onError("Não foi possível carregar a imagem da fatura.");
|
||||
return;
|
||||
}
|
||||
|
||||
GenerativeModel gm = new GenerativeModel(
|
||||
"gemini-2.5-flash",
|
||||
API_KEY
|
||||
);
|
||||
GenerativeModelFutures model = GenerativeModelFutures.from(gm);
|
||||
// Redimensionar a imagem para otimizar o envio
|
||||
Bitmap bitmap = scaleBitmap(originalBitmap, 1024);
|
||||
String base64Image = bitmapToBase64(bitmap);
|
||||
|
||||
Content content = new Content.Builder()
|
||||
.addText(PROMPT)
|
||||
.addImage(bitmap)
|
||||
.build();
|
||||
// Criar o payload JSON para o Ollama
|
||||
JSONObject payload = new JSONObject();
|
||||
payload.put("model", MODEL_NAME);
|
||||
payload.put("stream", false);
|
||||
payload.put("format", "json");
|
||||
|
||||
ListenableFuture<GenerateContentResponse> response = model.generateContent(content);
|
||||
JSONArray messages = new JSONArray();
|
||||
JSONObject message = new JSONObject();
|
||||
message.put("role", "user");
|
||||
message.put("content", PROMPT);
|
||||
|
||||
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", "");
|
||||
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("Resposta vazia da IA.");
|
||||
callback.onError("Não foi possível extrair os dados formatados da fatura.");
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e("InvoiceScanner", "Erro ao fazer parse do JSON: " + result.getText(), e);
|
||||
callback.onError("Erro ao ler dados 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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
Log.e("InvoiceScanner", "Falha na API do Gemini", t);
|
||||
callback.onError("Falha ao comunicar com a IA: " + t.getMessage());
|
||||
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);
|
||||
}
|
||||
}, executor);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e("InvoiceScanner", "Erro geral", e);
|
||||
callback.onError("Erro ao processar imagem: " + e.getMessage());
|
||||
if (executor instanceof java.util.concurrent.ExecutorService) {
|
||||
((java.util.concurrent.ExecutorService) executor).shutdown();
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import com.example.lifegrid.utils.CustomToast;
|
||||
|
||||
import androidx.activity.EdgeToEdge;
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -22,28 +23,9 @@ import androidx.core.graphics.Insets;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
|
||||
import androidx.credentials.ClearCredentialStateRequest;
|
||||
import androidx.credentials.CredentialManager;
|
||||
|
||||
import androidx.credentials.Credential;
|
||||
import androidx.credentials.CredentialManagerCallback;
|
||||
import androidx.credentials.CustomCredential;
|
||||
import androidx.credentials.GetCredentialRequest;
|
||||
import androidx.credentials.GetCredentialResponse;
|
||||
import androidx.credentials.exceptions.ClearCredentialException;
|
||||
import androidx.credentials.exceptions.GetCredentialException;
|
||||
import androidx.credentials.exceptions.NoCredentialException;
|
||||
|
||||
import com.google.android.libraries.identity.googleid.GetGoogleIdOption;
|
||||
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential;
|
||||
import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException;
|
||||
import com.google.firebase.FirebaseApp;
|
||||
import com.google.firebase.auth.AuthCredential;
|
||||
import com.google.firebase.auth.FirebaseAuth;
|
||||
import com.google.firebase.auth.FirebaseUser;
|
||||
import com.google.firebase.auth.GoogleAuthProvider;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* LoginActivity é o ecrã inicial da aplicação onde o utilizador fornece as suas credenciais.
|
||||
@@ -61,16 +43,10 @@ public class LoginActivity extends AppCompatActivity {
|
||||
private EditText passwordEditText;
|
||||
private TextView passesquecerTextView;
|
||||
private Button loginButton;
|
||||
private Button googleButton;
|
||||
private TextView ouTextView;
|
||||
private ProgressBar loadingProgressBar;
|
||||
private FirebaseAuth firebaseAuth;
|
||||
private CredentialManager credentialManager;
|
||||
private FirebaseAuth mAuth;
|
||||
|
||||
private static final String TAG = "LoginActivity - Google Sign In";
|
||||
private static final String GOOGLE_ID_TOKEN_CREDENTIAL = "1019731295596-i3q6aprqj6s55g6s97tpopbk4foutold.apps.googleusercontent.com";
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -91,17 +67,13 @@ public class LoginActivity extends AppCompatActivity {
|
||||
passwordEditText = findViewById(R.id.passwordEditText);
|
||||
passesquecerTextView = findViewById(R.id.passesquecerTextView);
|
||||
loginButton = findViewById(R.id.loginButton);
|
||||
googleButton = findViewById(R.id.googleButton);
|
||||
ouTextView = findViewById(R.id.ouTextView);
|
||||
loadingProgressBar = findViewById(R.id.loadingProgressBar);
|
||||
|
||||
FirebaseApp.initializeApp(this);
|
||||
firebaseAuth = FirebaseAuth.getInstance();
|
||||
mAuth = FirebaseAuth.getInstance();
|
||||
credentialManager = CredentialManager.create(getBaseContext());
|
||||
|
||||
loginButton.setOnClickListener(v -> validarLogin());
|
||||
googleButton.setOnClickListener(v -> launchCredentialManager());
|
||||
|
||||
criarContaTextView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
@@ -111,17 +83,45 @@ public class LoginActivity extends AppCompatActivity {
|
||||
}
|
||||
});
|
||||
|
||||
// Inicialmente desabilitar o TextView de recuperar palavra-passe
|
||||
passesquecerTextView.setEnabled(false);
|
||||
passesquecerTextView.setAlpha(0.5f); // Visualmente mais claro quando desabilitado
|
||||
|
||||
passesquecerTextView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
String email = emailEditText.getText().toString().trim();
|
||||
Intent intent = new Intent(LoginActivity.this, RecupearPasswordActivity.class);
|
||||
if (!TextUtils.isEmpty(email)) {
|
||||
intent.putExtra("email", email);
|
||||
|
||||
// Validar se tem e-mail antes de abrir a atividade de recuperação
|
||||
if (TextUtils.isEmpty(email)) {
|
||||
emailEditText.setError("Por favor, digite o seu e-mail primeiro.");
|
||||
emailEditText.requestFocus();
|
||||
CustomToast.info(LoginActivity.this, "Digite o seu e-mail para recuperar a palavra-passe.");
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(LoginActivity.this, RecupearPasswordActivity.class);
|
||||
intent.putExtra("email", email);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
// Adicionar TextWatcher para habilitar/desabilitar o TextView dinamicamente
|
||||
emailEditText.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
String email = s.toString().trim();
|
||||
boolean hasEmail = !TextUtils.isEmpty(email);
|
||||
passesquecerTextView.setEnabled(hasEmail);
|
||||
passesquecerTextView.setAlpha(hasEmail ? 1.0f : 0.5f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,7 +146,7 @@ public class LoginActivity extends AppCompatActivity {
|
||||
String welcome = user != null && !TextUtils.isEmpty(user.getEmail())
|
||||
? "Bem-vindo, " + user.getEmail()
|
||||
: "Login realizado com sucesso!";
|
||||
Toast.makeText(this, welcome, Toast.LENGTH_SHORT).show();
|
||||
CustomToast.success(this, welcome);
|
||||
|
||||
// Redirecionar para TelaInicialActivity após login bem-sucedido
|
||||
Intent intent = new Intent(LoginActivity.this, TelaInicialActivity.class);
|
||||
@@ -154,9 +154,8 @@ public class LoginActivity extends AppCompatActivity {
|
||||
startActivity(intent);
|
||||
finish(); // Fechar LoginActivity para não poder voltar com back button
|
||||
} else {
|
||||
Toast.makeText(this,
|
||||
task.getException() != null ? task.getException().getMessage() : "Falha no login",
|
||||
Toast.LENGTH_LONG).show();
|
||||
CustomToast.error(this,
|
||||
task.getException() != null ? task.getException().getMessage() : "Falha no login");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -174,13 +173,11 @@ public class LoginActivity extends AppCompatActivity {
|
||||
.addOnCompleteListener(this, task -> {
|
||||
toggleLoading(false);
|
||||
if (task.isSuccessful()) {
|
||||
Toast.makeText(this,
|
||||
"Email de recuperação enviado para " + email,
|
||||
Toast.LENGTH_LONG).show();
|
||||
CustomToast.success(this,
|
||||
"Email de recuperação enviado para " + email);
|
||||
} else {
|
||||
Toast.makeText(this,
|
||||
task.getException() != null ? task.getException().getMessage() : "Erro ao enviar email",
|
||||
Toast.LENGTH_LONG).show();
|
||||
CustomToast.error(this,
|
||||
task.getException() != null ? task.getException().getMessage() : "Erro ao enviar email");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -214,113 +211,13 @@ public class LoginActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
|
||||
private void firebaseAuthWithGoogle(String idToken) {
|
||||
AuthCredential credential = GoogleAuthProvider.getCredential(idToken, null);
|
||||
firebaseAuth.signInWithCredential(credential)
|
||||
.addOnCompleteListener(this, task -> {
|
||||
toggleLoading(false);
|
||||
if (task.isSuccessful()) {
|
||||
FirebaseUser user = firebaseAuth.getCurrentUser();
|
||||
String welcome = user != null && !TextUtils.isEmpty(user.getDisplayName())
|
||||
? "Bem-vindo, " + user.getDisplayName()
|
||||
: "Login com Google realizado com sucesso!";
|
||||
Toast.makeText(this, welcome, Toast.LENGTH_SHORT).show();
|
||||
|
||||
// Redirecionar para TelaInicialActivity após login bem-sucedido
|
||||
Intent intent = new Intent(LoginActivity.this, TelaInicialActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
} else {
|
||||
Toast.makeText(this,
|
||||
task.getException() != null ? task.getException().getMessage() : "Falha no login com Google",
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void toggleLoading(boolean show) {
|
||||
loadingProgressBar.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
loginButton.setEnabled(!show);
|
||||
googleButton.setEnabled(!show);
|
||||
criarContaTextView.setEnabled(!show);
|
||||
passesquecerTextView.setEnabled(!show);
|
||||
}
|
||||
|
||||
private void launchCredentialManager() {
|
||||
// [START create_credential_manager_request]
|
||||
// Instancia um pedido de início de sessão do Google
|
||||
GetGoogleIdOption googleIdOption = new GetGoogleIdOption.Builder()
|
||||
.setFilterByAuthorizedAccounts(false)
|
||||
.setServerClientId(getString(R.string.default_web_client_id))
|
||||
.build();
|
||||
|
||||
// Cria o pedido do Gestor de Credenciais
|
||||
GetCredentialRequest request = new GetCredentialRequest.Builder()
|
||||
.addCredentialOption(googleIdOption)
|
||||
.build();
|
||||
// [END create_credential_manager_request]
|
||||
|
||||
// Lança a interface do Gestor de Credenciais
|
||||
credentialManager.getCredentialAsync(
|
||||
LoginActivity.this,
|
||||
request,
|
||||
new CancellationSignal(),
|
||||
androidx.core.content.ContextCompat.getMainExecutor(LoginActivity.this),
|
||||
new CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() {
|
||||
@Override
|
||||
public void onResult(GetCredentialResponse result) {
|
||||
// Extrai a credencial do resultado devolvido pelo Gestor de Credenciais
|
||||
handleSignIn(result.getCredential());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(GetCredentialException e) {
|
||||
Log.e(TAG, "Couldn't retrieve user's credentials: " + e.getLocalizedMessage());
|
||||
Toast.makeText(LoginActivity.this, "Falha ao abrir Google Sign In.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void handleSignIn(Credential credential) {
|
||||
// Verifica se a credencial é do tipo Google ID
|
||||
if (credential instanceof CustomCredential customCredential
|
||||
&& credential.getType().equals(GOOGLE_ID_TOKEN_CREDENTIAL)) {
|
||||
// Cria o token do Google ID
|
||||
Bundle credentialData = customCredential.getData();
|
||||
GoogleIdTokenCredential googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credentialData);
|
||||
|
||||
// Inicia a sessão no Firebase usando o token
|
||||
firebaseAuthWithGoogle(googleIdTokenCredential.getIdToken());
|
||||
} else {
|
||||
Log.w(TAG, "Credential is not of type Google ID!");
|
||||
}
|
||||
}
|
||||
|
||||
private void signOut() {
|
||||
// Termina a sessão no Firebase
|
||||
mAuth.signOut();
|
||||
|
||||
// Quando o utilizador termina a sessão, limpa o estado atual da credencial do utilizador de todos os provedores de credenciais.
|
||||
ClearCredentialStateRequest clearRequest = new ClearCredentialStateRequest();
|
||||
credentialManager.clearCredentialStateAsync(
|
||||
clearRequest,
|
||||
new CancellationSignal(),
|
||||
Executors.newSingleThreadExecutor(),
|
||||
new CredentialManagerCallback<>() {
|
||||
@Override
|
||||
public void onResult(@NonNull Void result) {
|
||||
//updateUI(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull ClearCredentialException e) {
|
||||
Log.e(TAG, "Couldn't clear user credentials: " + e.getLocalizedMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
@@ -8,6 +8,8 @@ import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ImageView;
|
||||
import com.example.lifegrid.utils.CustomToast;
|
||||
|
||||
import androidx.activity.EdgeToEdge;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
@@ -51,9 +53,16 @@ public class RecupearPasswordActivity extends AppCompatActivity {
|
||||
String emailPassado = getIntent().getStringExtra("email");
|
||||
if (!TextUtils.isEmpty(emailPassado)) {
|
||||
emailEditText4.setText(emailPassado);
|
||||
// Tornar o campo não editável quando vier do login
|
||||
emailEditText4.setEnabled(false);
|
||||
emailEditText4.setFocusable(false);
|
||||
emailEditText4.setClickable(false);
|
||||
}
|
||||
|
||||
loginButton3.setOnClickListener(v -> recuperarPassword());
|
||||
|
||||
ImageView btnBack = findViewById(R.id.btnBack);
|
||||
btnBack.setOnClickListener(v -> finish());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,11 +97,10 @@ public class RecupearPasswordActivity extends AppCompatActivity {
|
||||
toggleLoading(false);
|
||||
if (task.isSuccessful()) {
|
||||
// Email enviado com sucesso
|
||||
Toast.makeText(this,
|
||||
"Email de recuperação enviado para " + email + "\nVerifique sua caixa de entrada.",
|
||||
Toast.LENGTH_LONG).show();
|
||||
// Fechar atividade para voltar ao Login após sucesso
|
||||
finish();
|
||||
CustomToast.success(this,
|
||||
"Email de recuperação enviado para " + email + "\nVerifique sua caixa de entrada.");
|
||||
// Limpar o campo após sucesso
|
||||
emailEditText4.setText("");
|
||||
} else {
|
||||
// Erro ao enviar email
|
||||
String errorMessage = "Erro ao enviar email de recuperação.";
|
||||
@@ -109,7 +117,7 @@ public class RecupearPasswordActivity extends AppCompatActivity {
|
||||
errorMessage = "Muitas tentativas. Tente novamente mais tarde.";
|
||||
}
|
||||
}
|
||||
Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show();
|
||||
CustomToast.error(this, errorMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
import com.example.lifegrid.utils.CustomToast;
|
||||
import com.google.firebase.auth.FirebaseAuth;
|
||||
import com.google.firebase.auth.FirebaseUser;
|
||||
import com.google.firebase.database.DatabaseReference;
|
||||
@@ -155,7 +156,7 @@ public class TelaInicialActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
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() {
|
||||
@Override
|
||||
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);
|
||||
|
||||
// Save document to Firebase
|
||||
uploadDocumentoToFirebase(imageUri, descricao, data);
|
||||
deleteLocalInvoiceFile(imageUri);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
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) {
|
||||
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
|
||||
if (user == null) {
|
||||
Toast.makeText(this, "Utilizador não autenticado.", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
private void deleteLocalInvoiceFile(android.net.Uri uri) {
|
||||
try {
|
||||
getContentResolver().delete(uri, null, null);
|
||||
} catch (Exception e) {
|
||||
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
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import com.example.lifegrid.utils.CustomToast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
@@ -47,7 +48,7 @@ public class DocumentoAdapter extends RecyclerView.Adapter<DocumentoAdapter.Docu
|
||||
if (url != null && !url.isEmpty()) {
|
||||
downloadFile(url, holder.tvDescricao.getText().toString());
|
||||
} 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);
|
||||
if (manager != null) {
|
||||
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) {
|
||||
e.printStackTrace();
|
||||
Toast.makeText(context, "Erro ao iniciar transferência.", Toast.LENGTH_SHORT).show();
|
||||
CustomToast.error(context, "Erro ao iniciar transferência.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import com.example.lifegrid.utils.CustomToast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
@@ -106,7 +107,7 @@ public class MetaAdapter extends RecyclerView.Adapter<MetaAdapter.MetaViewHolder
|
||||
double valorAdicional = Double.parseDouble(input);
|
||||
listener.onAddValueClick(meta, valorAdicional, holder.etAdicionarValor);
|
||||
} catch (NumberFormatException e) {
|
||||
Toast.makeText(v.getContext(), "Valor inválido", Toast.LENGTH_SHORT).show();
|
||||
CustomToast.error(v.getContext(), "Valor inválido");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
import com.example.lifegrid.utils.CustomToast;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
@@ -159,18 +160,18 @@ public class AtivosFragment extends Fragment {
|
||||
|
||||
@Override
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
Toast.makeText(requireContext(), "A atualizar " + ativo.getTicker() + "...", Toast.LENGTH_SHORT).show();
|
||||
CustomToast.info(requireContext(), "A atualizar " + ativo.getTicker() + "...");
|
||||
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
@@ -205,14 +206,14 @@ public class AtivosFragment extends Fragment {
|
||||
handler.post(() -> {
|
||||
String userId = FirebaseAuth.getInstance().getCurrentUser().getUid();
|
||||
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 {
|
||||
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) {
|
||||
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);
|
||||
|
||||
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()) {
|
||||
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 {
|
||||
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();
|
||||
Toast.makeText(requireContext(), "Ativo adicionado com sucesso!", Toast.LENGTH_SHORT).show();
|
||||
CustomToast.success(requireContext(), "Ativo adicionado com sucesso!");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import com.example.lifegrid.utils.CustomToast;
|
||||
|
||||
import com.example.lifegrid.R;
|
||||
import com.example.lifegrid.models.Transacao;
|
||||
@@ -135,9 +136,7 @@ public class GraficosFragment extends Fragment {
|
||||
|
||||
@Override
|
||||
public void onCancelled(@NonNull DatabaseError error) {
|
||||
if (getContext() != null) {
|
||||
Toast.makeText(getContext(), "Erro ao carregar transações.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
CustomToast.error(requireContext(), "Erro ao carregar transações.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import com.example.lifegrid.utils.CustomToast;
|
||||
|
||||
import com.example.lifegrid.R;
|
||||
import com.example.lifegrid.models.Transacao;
|
||||
@@ -218,7 +219,7 @@ public class HomeFragment extends Fragment {
|
||||
@Override
|
||||
public void onCancelled(@NonNull DatabaseError error) {
|
||||
if (isAdded()) {
|
||||
Toast.makeText(getContext(), "Erro ao carregar dados", Toast.LENGTH_SHORT).show();
|
||||
CustomToast.error(requireContext(), "Erro ao carregar dados");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@ import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import com.example.lifegrid.utils.CustomToast;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
@@ -87,7 +88,7 @@ public class MetasFragment extends Fragment {
|
||||
.setPositiveButton("Sim", (dialog, which) -> {
|
||||
if (databaseReference != null && userId != null && meta.getId() != null) {
|
||||
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)
|
||||
@@ -117,8 +118,17 @@ public class MetasFragment extends Fragment {
|
||||
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("");
|
||||
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
|
||||
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()) {
|
||||
Toast.makeText(requireContext(), "Por favor, preencha todos os campos.", Toast.LENGTH_SHORT).show();
|
||||
CustomToast.info(requireContext(), "Por favor, preencha todos os campos.");
|
||||
} else {
|
||||
// Aqui seria a lógica para guardar na Firebase
|
||||
DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference();
|
||||
@@ -223,7 +233,7 @@ public class MetasFragment extends Fragment {
|
||||
|
||||
|
||||
dialog.dismiss();
|
||||
Toast.makeText(requireContext(), "Meta criada com sucesso!", Toast.LENGTH_SHORT).show();
|
||||
CustomToast.success(requireContext(), "Meta criada com sucesso!");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.widget.ImageView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import com.example.lifegrid.utils.CustomToast;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
@@ -108,7 +109,7 @@ public class TransacoesFragment extends Fragment {
|
||||
.setPositiveButton("Sim", (dialog, which) -> {
|
||||
if (databaseReference != null && userId != null && transacao.getId() != null) {
|
||||
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)
|
||||
@@ -144,7 +145,7 @@ public class TransacoesFragment extends Fragment {
|
||||
|
||||
@Override
|
||||
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) {
|
||||
Toast.makeText(requireContext(), "Por favor, preencha todos os campos.", Toast.LENGTH_SHORT).show();
|
||||
CustomToast.info(requireContext(), "Por favor, preencha todos os campos.");
|
||||
} else {
|
||||
DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference();
|
||||
String userId = FirebaseAuth.getInstance().getCurrentUser().getUid();
|
||||
@@ -305,7 +306,7 @@ public class TransacoesFragment extends Fragment {
|
||||
|
||||
|
||||
dialog.dismiss();
|
||||
Toast.makeText(requireContext(), "Transação adicionada com sucesso!", Toast.LENGTH_SHORT).show();
|
||||
CustomToast.success(requireContext(), "Transação adicionada com sucesso!");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
6
app/src/main/res/drawable/custom_toast_bg.xml
Normal file
6
app/src/main/res/drawable/custom_toast_bg.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_toast_error.xml
Normal file
9
app/src/main/res/drawable/ic_toast_error.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_toast_info.xml
Normal file
9
app/src/main/res/drawable/ic_toast_info.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_toast_success.xml
Normal file
9
app/src/main/res/drawable/ic_toast_success.xml
Normal 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>
|
||||
@@ -8,6 +8,19 @@
|
||||
android:layout_height="match_parent"
|
||||
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
|
||||
android:id="@+id/logoImageView"
|
||||
android:layout_width="213dp"
|
||||
@@ -119,31 +132,7 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/passwordEditText2" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/ouTextView2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="22dp"
|
||||
android:text="_____________________ OU _____________________"
|
||||
android:textColor="@color/cinzaescuro"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginButton2" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/googleButton2"
|
||||
android:layout_width="316dp"
|
||||
android:layout_height="49dp"
|
||||
android:layout_marginTop="14dp"
|
||||
android:backgroundTint="#FFFFFF"
|
||||
android:text="Continuar com o Google"
|
||||
android:textColor="#0F0E0E"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/ouTextView2"
|
||||
app:strokeColor="@color/cinza"
|
||||
app:strokeWidth="1sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
@@ -205,6 +194,6 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/googleButton2" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginButton2" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -149,33 +149,6 @@
|
||||
android:checked="true" />
|
||||
</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>
|
||||
|
||||
<!-- Extras Section -->
|
||||
@@ -196,21 +169,6 @@
|
||||
android:padding="16dp"
|
||||
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
|
||||
android:id="@+id/tvTerms"
|
||||
|
||||
@@ -62,33 +62,7 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/passesquecerTextView" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/googleButton"
|
||||
android:layout_width="316dp"
|
||||
android:layout_height="49dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:backgroundTint="#FFFFFF"
|
||||
android:text="Continuar com o Google"
|
||||
android:textColor="#0F0E0E"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.483"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginButton"
|
||||
app:strokeColor="@color/cinza"
|
||||
app:strokeWidth="1sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/ouTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="_____________________ OU _____________________"
|
||||
android:textColor="@color/cinzaescuro"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.492"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/googleButton" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/passwordEditText"
|
||||
@@ -156,7 +130,7 @@
|
||||
android:textColor="#121111"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/ouTextView" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginButton" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/loadingProgressBar"
|
||||
|
||||
@@ -8,6 +8,19 @@
|
||||
android:layout_height="match_parent"
|
||||
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
|
||||
android:id="@+id/loginButton3"
|
||||
android:layout_width="315dp"
|
||||
@@ -26,7 +39,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="40dp"
|
||||
android:text="Email"
|
||||
android:textColor="@color/preto"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.104"
|
||||
@@ -42,9 +54,7 @@
|
||||
android:ems="10"
|
||||
android:hint="seu@email.com"
|
||||
android:inputType="textEmailAddress"
|
||||
android:textColor="@color/preto"
|
||||
android:textColorHint="@color/cinzaescuro"
|
||||
android:textSize="15sp"
|
||||
android:textSize="15dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.493"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@@ -67,8 +77,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="Recuperar password"
|
||||
android:textColor="@color/preto"
|
||||
android:textSize="26sp"
|
||||
android:textSize="26dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.23"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@@ -80,8 +89,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="Insira o seu email para recuperar a password"
|
||||
android:textColor="@color/preto"
|
||||
android:textSize="16sp"
|
||||
android:textSize="16dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.425"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
||||
28
app/src/main/res/layout/custom_toast.xml
Normal file
28
app/src/main/res/layout/custom_toast.xml
Normal 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>
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/adicionarCardView"
|
||||
android:layout_width="364dp"
|
||||
android:layout_width="391dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="544dp"
|
||||
android:background="@drawable/cardview_background"
|
||||
|
||||
@@ -54,18 +54,19 @@
|
||||
tools:text="Criptomoedas" />
|
||||
|
||||
<!-- Quantity and Purchase Price -->
|
||||
|
||||
<!-- Delete Button -->
|
||||
<TextView
|
||||
android:id="@+id/tvInfo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:textColor="#6b7280"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintStart_toStartOf="@id/tvNome"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvTipo"
|
||||
tools:text="12 unidades x 16.10€" />
|
||||
|
||||
<!-- Delete Button -->
|
||||
<ImageView
|
||||
android:id="@+id/btnDelete"
|
||||
android:layout_width="28dp"
|
||||
@@ -111,6 +112,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:textColor="#16a34a"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
||||
@@ -5,5 +5,22 @@
|
||||
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
|
||||
</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>
|
||||
Reference in New Issue
Block a user