Compare commits
16 Commits
09b260d9c3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a1a01e27df | |||
| 31034a3465 | |||
| 75ba56cf74 | |||
| 55560a1bfb | |||
| 04ce7ece4f | |||
| 4356a7432e | |||
| 196487a2e2 | |||
| 53f77a852e | |||
| 21c1e472ba | |||
| 533f164b0f | |||
| 9204ddd4ce | |||
| 65dce225de | |||
| 148c104b04 | |||
| 53b791a5ce | |||
| 413019cfac | |||
| e7ead2a066 |
37
.idea/deploymentTargetSelector.xml
generated
37
.idea/deploymentTargetSelector.xml
generated
@@ -4,10 +4,43 @@
|
|||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
<DropdownSelection timestamp="2026-04-23T16:33:30.001147200Z">
|
<DropdownSelection timestamp="2026-06-18T10:01:02.387533Z">
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\Rodrigo\.android\avd\Medium_Phone.avd" />
|
<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">
|
||||||
|
<handle>
|
||||||
|
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/230415/.android/avd/Pixel_9a.avd" />
|
||||||
</handle>
|
</handle>
|
||||||
</Target>
|
</Target>
|
||||||
</DropdownSelection>
|
</DropdownSelection>
|
||||||
|
|||||||
@@ -68,4 +68,5 @@ dependencies {
|
|||||||
androidTestImplementation(libs.ui.test.junit4)
|
androidTestImplementation(libs.ui.test.junit4)
|
||||||
debugImplementation(libs.ui.tooling)
|
debugImplementation(libs.ui.tooling)
|
||||||
debugImplementation(libs.ui.test.manifest)
|
debugImplementation(libs.ui.test.manifest)
|
||||||
|
implementation("com.github.PhilJay:MPAndroidChart:v3.1.0")
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,9 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".DefinicoesActivity"
|
android:name=".DefinicoesActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
<activity
|
||||||
|
android:name=".TermosPoliticasActivity"
|
||||||
|
android:exported="false" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".TelaInicialActivity"
|
android:name=".TelaInicialActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -16,24 +18,9 @@ import androidx.core.graphics.Insets;
|
|||||||
import androidx.core.view.ViewCompat;
|
import androidx.core.view.ViewCompat;
|
||||||
import androidx.core.view.WindowInsetsCompat;
|
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.FirebaseApp;
|
||||||
import com.google.firebase.auth.AuthCredential;
|
|
||||||
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.auth.GoogleAuthProvider;
|
|
||||||
|
|
||||||
import com.google.firebase.auth.GoogleAuthProvider;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CriarContaActivity é a atividade responsável por gerir o registo de novos utilizadores na aplicação LifeGrid.
|
* 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 passwordEditText3;
|
||||||
private EditText passwordEditText2;
|
private EditText passwordEditText2;
|
||||||
private Button loginButton2;
|
private Button loginButton2;
|
||||||
private Button googleButton2;
|
|
||||||
private ProgressBar loadingProgressBar;
|
private ProgressBar loadingProgressBar;
|
||||||
private FirebaseAuth firebaseAuth;
|
private FirebaseAuth firebaseAuth;
|
||||||
private CredentialManager credentialManager;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -68,15 +53,16 @@ public class CriarContaActivity extends AppCompatActivity {
|
|||||||
passwordEditText3 = findViewById(R.id.passwordEditText3);
|
passwordEditText3 = findViewById(R.id.passwordEditText3);
|
||||||
passwordEditText2 = findViewById(R.id.passwordEditText2);
|
passwordEditText2 = findViewById(R.id.passwordEditText2);
|
||||||
loginButton2 = findViewById(R.id.loginButton2);
|
loginButton2 = findViewById(R.id.loginButton2);
|
||||||
googleButton2 = findViewById(R.id.googleButton2);
|
|
||||||
loadingProgressBar = findViewById(R.id.loadingProgressBar);
|
loadingProgressBar = findViewById(R.id.loadingProgressBar);
|
||||||
|
|
||||||
FirebaseApp.initializeApp(this);
|
FirebaseApp.initializeApp(this);
|
||||||
firebaseAuth = FirebaseAuth.getInstance();
|
firebaseAuth = FirebaseAuth.getInstance();
|
||||||
credentialManager = CredentialManager.create(this);
|
|
||||||
|
|
||||||
// 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());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -102,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);
|
||||||
@@ -126,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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -191,35 +175,9 @@ public class CriarContaActivity extends AppCompatActivity {
|
|||||||
return true;
|
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) {
|
private void toggleLoading(boolean show) {
|
||||||
loadingProgressBar.setVisibility(show ? View.VISIBLE : View.GONE);
|
loadingProgressBar.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||||
loginButton2.setEnabled(!show);
|
loginButton2.setEnabled(!show);
|
||||||
googleButton2.setEnabled(!show);
|
|
||||||
nomeEditText.setEnabled(!show);
|
nomeEditText.setEnabled(!show);
|
||||||
emailEditText2.setEnabled(!show);
|
emailEditText2.setEnabled(!show);
|
||||||
passwordEditText3.setEnabled(!show);
|
passwordEditText3.setEnabled(!show);
|
||||||
|
|||||||
@@ -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,10 +112,22 @@ public class DefinicoesActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TextView tvDocuments = findViewById(R.id.tvDocuments);
|
|
||||||
if (tvDocuments != null) {
|
|
||||||
tvDocuments.setOnClickListener(v -> {
|
TextView tvTerms = findViewById(R.id.tvTerms);
|
||||||
Intent intent = new Intent(DefinicoesActivity.this, DocumentosActivity.class);
|
if (tvTerms != null) {
|
||||||
|
tvTerms.setOnClickListener(v -> {
|
||||||
|
Intent intent = new Intent(DefinicoesActivity.this, TermosPoliticasActivity.class);
|
||||||
|
intent.putExtra("tipo_documento", "terms");
|
||||||
|
startActivity(intent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TextView tvPrivacy = findViewById(R.id.tvPrivacy);
|
||||||
|
if (tvPrivacy != null) {
|
||||||
|
tvPrivacy.setOnClickListener(v -> {
|
||||||
|
Intent intent = new Intent(DefinicoesActivity.this, TermosPoliticasActivity.class);
|
||||||
|
intent.putExtra("tipo_documento", "privacy");
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -131,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 {
|
||||||
@@ -141,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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -22,28 +23,9 @@ import androidx.core.graphics.Insets;
|
|||||||
import androidx.core.view.ViewCompat;
|
import androidx.core.view.ViewCompat;
|
||||||
import androidx.core.view.WindowInsetsCompat;
|
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.FirebaseApp;
|
||||||
import com.google.firebase.auth.AuthCredential;
|
|
||||||
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.auth.GoogleAuthProvider;
|
|
||||||
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LoginActivity é o ecrã inicial da aplicação onde o utilizador fornece as suas credenciais.
|
* 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 EditText passwordEditText;
|
||||||
private TextView passesquecerTextView;
|
private TextView passesquecerTextView;
|
||||||
private Button loginButton;
|
private Button loginButton;
|
||||||
private Button googleButton;
|
|
||||||
private TextView ouTextView;
|
|
||||||
private ProgressBar loadingProgressBar;
|
private ProgressBar loadingProgressBar;
|
||||||
private FirebaseAuth firebaseAuth;
|
private FirebaseAuth firebaseAuth;
|
||||||
private CredentialManager credentialManager;
|
|
||||||
private FirebaseAuth mAuth;
|
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
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -91,17 +67,13 @@ public class LoginActivity extends AppCompatActivity {
|
|||||||
passwordEditText = findViewById(R.id.passwordEditText);
|
passwordEditText = findViewById(R.id.passwordEditText);
|
||||||
passesquecerTextView = findViewById(R.id.passesquecerTextView);
|
passesquecerTextView = findViewById(R.id.passesquecerTextView);
|
||||||
loginButton = findViewById(R.id.loginButton);
|
loginButton = findViewById(R.id.loginButton);
|
||||||
googleButton = findViewById(R.id.googleButton);
|
|
||||||
ouTextView = findViewById(R.id.ouTextView);
|
|
||||||
loadingProgressBar = findViewById(R.id.loadingProgressBar);
|
loadingProgressBar = findViewById(R.id.loadingProgressBar);
|
||||||
|
|
||||||
FirebaseApp.initializeApp(this);
|
FirebaseApp.initializeApp(this);
|
||||||
firebaseAuth = FirebaseAuth.getInstance();
|
firebaseAuth = FirebaseAuth.getInstance();
|
||||||
mAuth = FirebaseAuth.getInstance();
|
mAuth = FirebaseAuth.getInstance();
|
||||||
credentialManager = CredentialManager.create(getBaseContext());
|
|
||||||
|
|
||||||
loginButton.setOnClickListener(v -> validarLogin());
|
loginButton.setOnClickListener(v -> validarLogin());
|
||||||
googleButton.setOnClickListener(v -> launchCredentialManager());
|
|
||||||
|
|
||||||
criarContaTextView.setOnClickListener(new View.OnClickListener() {
|
criarContaTextView.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -124,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,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);
|
||||||
@@ -182,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();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -202,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();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -242,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) {
|
private void toggleLoading(boolean show) {
|
||||||
loadingProgressBar.setVisibility(show ? View.VISIBLE : View.GONE);
|
loadingProgressBar.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||||
loginButton.setEnabled(!show);
|
loginButton.setEnabled(!show);
|
||||||
googleButton.setEnabled(!show);
|
|
||||||
criarContaTextView.setEnabled(!show);
|
criarContaTextView.setEnabled(!show);
|
||||||
passesquecerTextView.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
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -298,6 +276,9 @@ public class TelaInicialActivity extends AppCompatActivity {
|
|||||||
((HomeFragment) currentFragment).carregarDados(mesSelecionado, anoSelecionado, mesNome);
|
((HomeFragment) currentFragment).carregarDados(mesSelecionado, anoSelecionado, mesNome);
|
||||||
} else if (currentFragment instanceof TransacoesFragment) {
|
} else if (currentFragment instanceof TransacoesFragment) {
|
||||||
((TransacoesFragment) currentFragment).setFiltro(mesSelecionado, anoSelecionado);
|
((TransacoesFragment) currentFragment).setFiltro(mesSelecionado, anoSelecionado);
|
||||||
|
} else if (currentFragment instanceof GraficosFragment) {
|
||||||
|
String mesNome = meses[spinnerMes.getSelectedItemPosition()];
|
||||||
|
((GraficosFragment) currentFragment).setFiltro(mesSelecionado, anoSelecionado, mesNome);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.example.lifegrid;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import androidx.activity.EdgeToEdge;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.core.graphics.Insets;
|
||||||
|
import androidx.core.text.HtmlCompat;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
import androidx.core.view.WindowInsetsCompat;
|
||||||
|
|
||||||
|
public class TermosPoliticasActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private ImageView btnBack;
|
||||||
|
private TextView tvTitle;
|
||||||
|
private TextView tvContent;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
EdgeToEdge.enable(this);
|
||||||
|
setContentView(R.layout.activity_termos_politicas);
|
||||||
|
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
|
||||||
|
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
|
||||||
|
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
|
||||||
|
return insets;
|
||||||
|
});
|
||||||
|
|
||||||
|
btnBack = findViewById(R.id.btnBack);
|
||||||
|
tvTitle = findViewById(R.id.tvTitle);
|
||||||
|
tvContent = findViewById(R.id.tvContent);
|
||||||
|
|
||||||
|
btnBack.setOnClickListener(v -> finish());
|
||||||
|
|
||||||
|
String tipoDocumento = getIntent().getStringExtra("tipo_documento");
|
||||||
|
if (tipoDocumento != null) {
|
||||||
|
if (tipoDocumento.equals("terms")) {
|
||||||
|
tvTitle.setText(R.string.terms_title);
|
||||||
|
String htmlText = getString(R.string.terms_content);
|
||||||
|
tvContent.setText(HtmlCompat.fromHtml(htmlText, HtmlCompat.FROM_HTML_MODE_LEGACY));
|
||||||
|
} else if (tipoDocumento.equals("privacy")) {
|
||||||
|
tvTitle.setText(R.string.privacy_title);
|
||||||
|
String htmlText = getString(R.string.privacy_content);
|
||||||
|
tvContent.setText(HtmlCompat.fromHtml(htmlText, HtmlCompat.FROM_HTML_MODE_LEGACY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -93,7 +93,6 @@ public class AtivosAdapter extends RecyclerView.Adapter<AtivosAdapter.AtivosView
|
|||||||
|
|
||||||
holder.btnRefresh.setOnClickListener(v -> {
|
holder.btnRefresh.setOnClickListener(v -> {
|
||||||
if (listener != null) listener.onRefreshClick(position, key, ativo);
|
if (listener != null) listener.onRefreshClick(position, key, ativo);
|
||||||
Toast.makeText(context, "Ativo atualizado (Exemplo)", Toast.LENGTH_SHORT).show();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import android.app.DatePickerDialog;
|
|||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.drawable.ColorDrawable;
|
import android.graphics.drawable.ColorDrawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
@@ -17,9 +19,19 @@ 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;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
|
||||||
import com.example.lifegrid.R;
|
import com.example.lifegrid.R;
|
||||||
import com.example.lifegrid.adapters.AtivosAdapter;
|
import com.example.lifegrid.adapters.AtivosAdapter;
|
||||||
@@ -87,7 +99,7 @@ public class AtivosFragment extends Fragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRefreshClick(int position, String key, Ativos ativo) {
|
public void onRefreshClick(int position, String key, Ativos ativo) {
|
||||||
// A implementar no futuro
|
refreshAtivoPrice(ativo, key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ativosRecyclerView.setAdapter(ativosAdapter);
|
ativosRecyclerView.setAdapter(ativosAdapter);
|
||||||
@@ -148,7 +160,60 @@ 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) {
|
||||||
|
if (ativo.getTicker() == null || ativo.getTicker().isEmpty()) {
|
||||||
|
CustomToast.info(requireContext(), "Este ativo não tem um Ticker definido.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomToast.info(requireContext(), "A atualizar " + ativo.getTicker() + "...");
|
||||||
|
|
||||||
|
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
Handler handler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
|
executor.execute(() -> {
|
||||||
|
try {
|
||||||
|
String ticker = ativo.getTicker().toUpperCase(Locale.ROOT);
|
||||||
|
URL url = new URL("https://query1.finance.yahoo.com/v8/finance/chart/" + ticker);
|
||||||
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
|
conn.setRequestMethod("GET");
|
||||||
|
conn.setRequestProperty("User-Agent", "Mozilla/5.0");
|
||||||
|
conn.setConnectTimeout(5000);
|
||||||
|
conn.setReadTimeout(5000);
|
||||||
|
|
||||||
|
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
||||||
|
InputStream in = conn.getInputStream();
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
result.append(line);
|
||||||
|
}
|
||||||
|
reader.close();
|
||||||
|
|
||||||
|
JSONObject jsonResponse = new JSONObject(result.toString());
|
||||||
|
JSONObject chart = jsonResponse.getJSONObject("chart");
|
||||||
|
JSONArray resultArr = chart.getJSONArray("result");
|
||||||
|
JSONObject resultObj = resultArr.getJSONObject(0);
|
||||||
|
JSONObject meta = resultObj.getJSONObject("meta");
|
||||||
|
double regularMarketPrice = meta.getDouble("regularMarketPrice");
|
||||||
|
|
||||||
|
handler.post(() -> {
|
||||||
|
String userId = FirebaseAuth.getInstance().getCurrentUser().getUid();
|
||||||
|
databaseReference.child("users").child(userId).child("ativos").child(key).child("precoAtual").setValue(regularMarketPrice);
|
||||||
|
CustomToast.success(requireContext(), "Preço atualizado: " + regularMarketPrice + "€");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
handler.post(() -> CustomToast.error(requireContext(), "Erro ao contactar a API."));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
handler.post(() -> CustomToast.error(requireContext(), "Ativo não encontrado ou erro de rede."));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -183,23 +248,48 @@ public class AtivosFragment extends Fragment {
|
|||||||
|
|
||||||
Button btnAdicionarAtivoDialog = dialogView.findViewById(R.id.btnAdicionarAtivoDialog);
|
Button btnAdicionarAtivoDialog = dialogView.findViewById(R.id.btnAdicionarAtivoDialog);
|
||||||
EditText etNomeAtivo = dialogView.findViewById(R.id.etNomeAtivo);
|
EditText etNomeAtivo = dialogView.findViewById(R.id.etNomeAtivo);
|
||||||
|
EditText etTickerAtivo = dialogView.findViewById(R.id.etTickerAtivo);
|
||||||
|
TextView tvTickerLabel = dialogView.findViewById(R.id.tvTickerLabel);
|
||||||
Spinner spinnerTipoAtivo = dialogView.findViewById(R.id.spinnerTipoAtivo);
|
Spinner spinnerTipoAtivo = dialogView.findViewById(R.id.spinnerTipoAtivo);
|
||||||
EditText etQuantidade = dialogView.findViewById(R.id.etQuantidade);
|
EditText etQuantidade = dialogView.findViewById(R.id.etQuantidade);
|
||||||
EditText etPrecoCompra = dialogView.findViewById(R.id.etPrecoCompra);
|
EditText etPrecoCompra = dialogView.findViewById(R.id.etPrecoCompra);
|
||||||
|
|
||||||
|
spinnerTipoAtivo.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemSelected(android.widget.AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
String selectedType = parent.getItemAtPosition(position).toString();
|
||||||
|
if ("Criptomoedas".equals(selectedType)) {
|
||||||
|
tvTickerLabel.setVisibility(View.VISIBLE);
|
||||||
|
etTickerAtivo.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
tvTickerLabel.setVisibility(View.GONE);
|
||||||
|
etTickerAtivo.setVisibility(View.GONE);
|
||||||
|
etTickerAtivo.setText("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNothingSelected(android.widget.AdapterView<?> parent) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
btnAdicionarAtivoDialog.setOnClickListener(v -> {
|
btnAdicionarAtivoDialog.setOnClickListener(v -> {
|
||||||
String nome = etNomeAtivo.getText().toString().trim();
|
String nome = etNomeAtivo.getText().toString().trim();
|
||||||
|
String ticker = etTickerAtivo.getText().toString().trim();
|
||||||
String tipo = spinnerTipoAtivo.getSelectedItem().toString();
|
String tipo = spinnerTipoAtivo.getSelectedItem().toString();
|
||||||
String quantidade = etQuantidade.getText().toString().trim();
|
String quantidade = etQuantidade.getText().toString().trim();
|
||||||
String precoCompra = etPrecoCompra.getText().toString().trim();
|
String precoCompra = etPrecoCompra.getText().toString().trim();
|
||||||
String dataCompra = etDataCompra.getText().toString().trim();
|
String dataCompra = etDataCompra.getText().toString().trim();
|
||||||
|
|
||||||
Ativos ativos = new Ativos(nome, quantidade, Double.parseDouble(precoCompra), Double.parseDouble(precoCompra), dataCompra, 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()) {
|
||||||
|
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);
|
||||||
|
|
||||||
// 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();
|
||||||
String userId = FirebaseAuth.getInstance().getCurrentUser().getUid();
|
String userId = FirebaseAuth.getInstance().getCurrentUser().getUid();
|
||||||
@@ -209,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!");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,20 +2,61 @@ package com.example.lifegrid.menu;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
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.R;
|
||||||
|
import com.example.lifegrid.models.Transacao;
|
||||||
|
import com.github.mikephil.charting.charts.BarChart;
|
||||||
|
import com.github.mikephil.charting.charts.PieChart;
|
||||||
|
import com.github.mikephil.charting.components.XAxis;
|
||||||
|
import com.github.mikephil.charting.data.BarData;
|
||||||
|
import com.github.mikephil.charting.data.BarDataSet;
|
||||||
|
import com.github.mikephil.charting.data.BarEntry;
|
||||||
|
import com.github.mikephil.charting.data.PieData;
|
||||||
|
import com.github.mikephil.charting.data.PieDataSet;
|
||||||
|
import com.github.mikephil.charting.data.PieEntry;
|
||||||
|
import com.github.mikephil.charting.formatter.IndexAxisValueFormatter;
|
||||||
|
import com.github.mikephil.charting.utils.ColorTemplate;
|
||||||
|
import com.google.firebase.auth.FirebaseAuth;
|
||||||
|
import com.google.firebase.auth.FirebaseUser;
|
||||||
|
import com.google.firebase.database.DataSnapshot;
|
||||||
|
import com.google.firebase.database.DatabaseError;
|
||||||
|
import com.google.firebase.database.DatabaseReference;
|
||||||
|
import com.google.firebase.database.FirebaseDatabase;
|
||||||
|
import com.google.firebase.database.ValueEventListener;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import android.graphics.Color;
|
||||||
|
import com.example.lifegrid.TelaInicialActivity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GraficosFragment tem como único propósito compilar a informação da aplicação sob uma
|
* GraficosFragment tem como único propósito compilar a informação da aplicação sob uma
|
||||||
* componente puramente matemática exibida de forma ilustrativa. Tarta da visão de Dashboard de estatística.
|
* componente puramente matemática exibida de forma ilustrativa. Trata da visão de Dashboard de estatística.
|
||||||
*/
|
*/
|
||||||
public class GraficosFragment extends Fragment {
|
public class GraficosFragment extends Fragment {
|
||||||
|
|
||||||
|
private PieChart pieChartMensal;
|
||||||
|
private BarChart barChartAnual;
|
||||||
|
private TextView tvTituloMensal;
|
||||||
|
private TextView tvTituloAnual;
|
||||||
|
|
||||||
|
private List<Transacao> allTransacoesList = new ArrayList<>();
|
||||||
|
private int currentMes = -1;
|
||||||
|
private String currentAno = "";
|
||||||
|
private String currentMesNome = "";
|
||||||
|
|
||||||
public GraficosFragment() {
|
public GraficosFragment() {
|
||||||
// Construtor público vazio obrigatório
|
// Construtor público vazio obrigatório
|
||||||
}
|
}
|
||||||
@@ -25,13 +66,198 @@ public class GraficosFragment extends Fragment {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
if (getActivity() instanceof TelaInicialActivity) {
|
||||||
|
((TelaInicialActivity) getActivity()).atualizarDadosHome();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
// Inflaciona o layout para este fragmento
|
// Inflaciona o layout para este fragmento
|
||||||
View root = inflater.inflate(R.layout.fragment_graficos, container, false);
|
View root = inflater.inflate(R.layout.fragment_graficos, container, false);
|
||||||
|
|
||||||
|
pieChartMensal = root.findViewById(R.id.pieChartMensal);
|
||||||
|
barChartAnual = root.findViewById(R.id.barChartAnual);
|
||||||
|
tvTituloMensal = root.findViewById(R.id.tvTituloMensal);
|
||||||
|
tvTituloAnual = root.findViewById(R.id.tvTituloAnual);
|
||||||
|
|
||||||
|
configurarGraficos();
|
||||||
|
carregarTransacoes();
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configurarGraficos() {
|
||||||
|
// Config PieChart
|
||||||
|
pieChartMensal.getDescription().setEnabled(false);
|
||||||
|
pieChartMensal.setUsePercentValues(true);
|
||||||
|
pieChartMensal.setEntryLabelColor(Color.BLACK);
|
||||||
|
pieChartMensal.setEntryLabelTextSize(12f);
|
||||||
|
pieChartMensal.setCenterText("Despesas");
|
||||||
|
pieChartMensal.setCenterTextSize(18f);
|
||||||
|
|
||||||
|
// Config BarChart
|
||||||
|
barChartAnual.getDescription().setEnabled(false);
|
||||||
|
barChartAnual.setDrawGridBackground(false);
|
||||||
|
barChartAnual.getAxisRight().setEnabled(false);
|
||||||
|
XAxis xAxis = barChartAnual.getXAxis();
|
||||||
|
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
|
||||||
|
xAxis.setGranularity(1f);
|
||||||
|
xAxis.setCenterAxisLabels(true);
|
||||||
|
|
||||||
|
String[] mesesLabel = new String[]{"Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"};
|
||||||
|
xAxis.setValueFormatter(new IndexAxisValueFormatter(mesesLabel));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void carregarTransacoes() {
|
||||||
|
FirebaseUser currentUser = FirebaseAuth.getInstance().getCurrentUser();
|
||||||
|
if (currentUser == null) return;
|
||||||
|
|
||||||
|
String userId = currentUser.getUid();
|
||||||
|
DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference();
|
||||||
|
|
||||||
|
databaseReference.child("users").child(userId).child("transacoes").addValueEventListener(new ValueEventListener() {
|
||||||
|
@Override
|
||||||
|
public void onDataChange(@NonNull DataSnapshot snapshot) {
|
||||||
|
allTransacoesList.clear();
|
||||||
|
for (DataSnapshot ds : snapshot.getChildren()) {
|
||||||
|
Transacao t = ds.getValue(Transacao.class);
|
||||||
|
if (t != null) {
|
||||||
|
t.setId(ds.getKey());
|
||||||
|
allTransacoesList.add(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
atualizarGraficos();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCancelled(@NonNull DatabaseError error) {
|
||||||
|
CustomToast.error(requireContext(), "Erro ao carregar transações.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFiltro(int mesSelecionado, String anoSelecionado, String mesNome) {
|
||||||
|
this.currentMes = mesSelecionado;
|
||||||
|
this.currentAno = anoSelecionado;
|
||||||
|
this.currentMesNome = mesNome;
|
||||||
|
|
||||||
|
if (tvTituloMensal != null) {
|
||||||
|
tvTituloMensal.setText("Resumo de " + mesNome + " " + anoSelecionado);
|
||||||
}
|
}
|
||||||
}
|
if (tvTituloAnual != null) {
|
||||||
|
tvTituloAnual.setText("Balanço Anual - " + anoSelecionado);
|
||||||
|
}
|
||||||
|
|
||||||
|
atualizarGraficos();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void atualizarGraficos() {
|
||||||
|
if (currentMes == -1 || currentAno.isEmpty()) return;
|
||||||
|
|
||||||
|
atualizarPieChart();
|
||||||
|
atualizarBarChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void atualizarPieChart() {
|
||||||
|
Map<String, Float> categoriasMap = new HashMap<>();
|
||||||
|
|
||||||
|
for (Transacao t : allTransacoesList) {
|
||||||
|
if (t.getData() != null && t.getTipo() != null && t.getTipo().equalsIgnoreCase("Despesa")) {
|
||||||
|
String[] parts = t.getData().split("/");
|
||||||
|
if (parts.length == 3) {
|
||||||
|
try {
|
||||||
|
int mesTransacao = Integer.parseInt(parts[1]);
|
||||||
|
String anoTransacao = parts[2];
|
||||||
|
if (mesTransacao == currentMes && anoTransacao.equals(currentAno)) {
|
||||||
|
float valor = Float.parseFloat(t.getValor());
|
||||||
|
String cat = t.getCategoria();
|
||||||
|
categoriasMap.put(cat, categoriasMap.getOrDefault(cat, 0f) + valor);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PieEntry> entries = new ArrayList<>();
|
||||||
|
for (Map.Entry<String, Float> entry : categoriasMap.entrySet()) {
|
||||||
|
if (entry.getValue() > 0) {
|
||||||
|
entries.add(new PieEntry(entry.getValue(), entry.getKey()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PieDataSet dataSet = new PieDataSet(entries, "");
|
||||||
|
dataSet.setColors(ColorTemplate.MATERIAL_COLORS);
|
||||||
|
dataSet.setSliceSpace(3f);
|
||||||
|
dataSet.setValueTextSize(14f);
|
||||||
|
dataSet.setValueTextColor(Color.WHITE);
|
||||||
|
|
||||||
|
PieData data = new PieData(dataSet);
|
||||||
|
pieChartMensal.setData(data);
|
||||||
|
pieChartMensal.invalidate(); // refresh
|
||||||
|
}
|
||||||
|
|
||||||
|
private void atualizarBarChart() {
|
||||||
|
float[] receitasPorMes = new float[12];
|
||||||
|
float[] despesasPorMes = new float[12];
|
||||||
|
|
||||||
|
for (Transacao t : allTransacoesList) {
|
||||||
|
if (t.getData() != null) {
|
||||||
|
String[] parts = t.getData().split("/");
|
||||||
|
if (parts.length == 3) {
|
||||||
|
try {
|
||||||
|
int mesTransacao = Integer.parseInt(parts[1]) - 1; // 0 a 11
|
||||||
|
String anoTransacao = parts[2];
|
||||||
|
if (anoTransacao.equals(currentAno) && mesTransacao >= 0 && mesTransacao < 12) {
|
||||||
|
float valor = Float.parseFloat(t.getValor());
|
||||||
|
if (t.getTipo() != null && t.getTipo().equalsIgnoreCase("Receita")) {
|
||||||
|
receitasPorMes[mesTransacao] += valor;
|
||||||
|
} else {
|
||||||
|
despesasPorMes[mesTransacao] += valor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BarEntry> entriesReceitas = new ArrayList<>();
|
||||||
|
List<BarEntry> entriesDespesas = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
entriesReceitas.add(new BarEntry(i, receitasPorMes[i]));
|
||||||
|
entriesDespesas.add(new BarEntry(i, despesasPorMes[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
BarDataSet setReceitas = new BarDataSet(entriesReceitas, "Receitas");
|
||||||
|
setReceitas.setColor(Color.parseColor("#2ECC71"));
|
||||||
|
setReceitas.setValueTextSize(10f);
|
||||||
|
|
||||||
|
BarDataSet setDespesas = new BarDataSet(entriesDespesas, "Despesas");
|
||||||
|
setDespesas.setColor(Color.parseColor("#E74C3C"));
|
||||||
|
setDespesas.setValueTextSize(10f);
|
||||||
|
|
||||||
|
BarData data = new BarData(setReceitas, setDespesas);
|
||||||
|
|
||||||
|
// agrupar barras
|
||||||
|
float groupSpace = 0.4f;
|
||||||
|
float barSpace = 0.05f;
|
||||||
|
float barWidth = 0.25f;
|
||||||
|
|
||||||
|
data.setBarWidth(barWidth);
|
||||||
|
barChartAnual.setData(data);
|
||||||
|
barChartAnual.groupBars(0f, groupSpace, barSpace);
|
||||||
|
barChartAnual.getXAxis().setAxisMinimum(0f);
|
||||||
|
barChartAnual.getXAxis().setAxisMaximum(0f + barChartAnual.getBarData().getGroupWidth(groupSpace, barSpace) * 12);
|
||||||
|
|
||||||
|
barChartAnual.invalidate(); // refresh
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,9 +10,11 @@ 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;
|
||||||
|
import com.example.lifegrid.models.Ativos;
|
||||||
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.DataSnapshot;
|
import com.google.firebase.database.DataSnapshot;
|
||||||
@@ -73,9 +75,74 @@ public class HomeFragment extends Fragment {
|
|||||||
.commit();
|
.commit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
root.findViewById(R.id.ativosCardView).setOnClickListener(v -> {
|
||||||
|
Fragment ativosFragment = new AtivosFragment();
|
||||||
|
requireActivity().getSupportFragmentManager().beginTransaction()
|
||||||
|
.replace(R.id.fragmentContainerView, ativosFragment)
|
||||||
|
.commit();
|
||||||
|
});
|
||||||
|
|
||||||
|
carregarDadosAtivos();
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void carregarDadosAtivos() {
|
||||||
|
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
|
||||||
|
if (user == null) return;
|
||||||
|
|
||||||
|
DatabaseReference ativosRef = FirebaseDatabase.getInstance().getReference()
|
||||||
|
.child("users").child(user.getUid()).child("ativos");
|
||||||
|
|
||||||
|
ativosRef.addValueEventListener(new ValueEventListener() {
|
||||||
|
@Override
|
||||||
|
public void onDataChange(@NonNull DataSnapshot snapshot) {
|
||||||
|
if (!isAdded()) return;
|
||||||
|
|
||||||
|
double totalInvestido = 0;
|
||||||
|
double valorTotalPortfolio = 0;
|
||||||
|
|
||||||
|
for (DataSnapshot ds : snapshot.getChildren()) {
|
||||||
|
Ativos ativo = ds.getValue(Ativos.class);
|
||||||
|
if (ativo != null) {
|
||||||
|
double qtd = 0;
|
||||||
|
try {
|
||||||
|
qtd = Double.parseDouble(ativo.getQuantidade().replace(",", "."));
|
||||||
|
} catch (Exception e) {}
|
||||||
|
|
||||||
|
totalInvestido += qtd * ativo.getPrecoCompra();
|
||||||
|
valorTotalPortfolio += qtd * ativo.getPrecoAtual();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tvValor4 != null) {
|
||||||
|
tvValor4.setText(String.format(Locale.getDefault(), "%.2f€", valorTotalPortfolio));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tvTransacoes4 != null) {
|
||||||
|
double roi = valorTotalPortfolio - totalInvestido;
|
||||||
|
double roiPct = 0;
|
||||||
|
if (totalInvestido > 0) {
|
||||||
|
roiPct = (roi / totalInvestido) * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (roi >= 0) {
|
||||||
|
tvTransacoes4.setText(String.format(Locale.getDefault(), "+%.2f%% ROI", roiPct));
|
||||||
|
tvTransacoes4.setTextColor(android.graphics.Color.parseColor("#22C55E"));
|
||||||
|
} else {
|
||||||
|
tvTransacoes4.setText(String.format(Locale.getDefault(), "%.2f%% ROI", roiPct));
|
||||||
|
tvTransacoes4.setTextColor(android.graphics.Color.parseColor("#FF0000"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCancelled(@NonNull DatabaseError error) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
@@ -152,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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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!");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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!");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,18 +7,20 @@ public class Ativos {
|
|||||||
private double precoAtual;
|
private double precoAtual;
|
||||||
private String dataCompra;
|
private String dataCompra;
|
||||||
private String tipo;
|
private String tipo;
|
||||||
|
private String ticker;
|
||||||
|
|
||||||
|
|
||||||
public Ativos() {
|
public Ativos() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Ativos(String nome, String quantidade, double precoCompra, double precoAtual, String dataCompra, String tipo) {
|
public Ativos(String nome, String quantidade, double precoCompra, double precoAtual, String dataCompra, String tipo, String ticker) {
|
||||||
this.nome = nome;
|
this.nome = nome;
|
||||||
this.quantidade = quantidade;
|
this.quantidade = quantidade;
|
||||||
this.precoCompra = precoCompra;
|
this.precoCompra = precoCompra;
|
||||||
this.precoAtual = precoAtual;
|
this.precoAtual = precoAtual;
|
||||||
this.dataCompra = dataCompra;
|
this.dataCompra = dataCompra;
|
||||||
this.tipo = tipo;
|
this.tipo = tipo;
|
||||||
|
this.ticker = ticker;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getNome() {
|
public String getNome() {
|
||||||
@@ -68,4 +70,12 @@ public class Ativos {
|
|||||||
public void setTipo(String tipo) {
|
public void setTipo(String tipo) {
|
||||||
this.tipo = tipo;
|
this.tipo = tipo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getTicker() {
|
||||||
|
return ticker;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTicker(String ticker) {
|
||||||
|
this.ticker = ticker;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
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"
|
||||||
@@ -119,31 +132,7 @@
|
|||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/passwordEditText2" />
|
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
|
<TextView
|
||||||
android:id="@+id/textView"
|
android:id="@+id/textView"
|
||||||
@@ -205,6 +194,6 @@
|
|||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/googleButton2" />
|
app:layout_constraintTop_toBottomOf="@+id/loginButton2" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -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"
|
||||||
@@ -259,6 +217,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="55dp"
|
android:layout_height="55dp"
|
||||||
android:backgroundTint="@color/preto"
|
android:backgroundTint="@color/preto"
|
||||||
|
android:textColor="@color/branco"
|
||||||
android:text="Guardar Alterações"
|
android:text="Guardar Alterações"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
app:cornerRadius="14dp"
|
app:cornerRadius="14dp"
|
||||||
|
|||||||
@@ -62,33 +62,7 @@
|
|||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/passesquecerTextView" />
|
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
|
<EditText
|
||||||
android:id="@+id/passwordEditText"
|
android:id="@+id/passwordEditText"
|
||||||
@@ -156,7 +130,7 @@
|
|||||||
android:textColor="#121111"
|
android:textColor="#121111"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/ouTextView" />
|
app:layout_constraintTop_toBottomOf="@+id/loginButton" />
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/loadingProgressBar"
|
android:id="@+id/loadingProgressBar"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
73
app/src/main/res/layout/activity_termos_politicas.xml
Normal file
73
app/src/main/res/layout/activity_termos_politicas.xml
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/main"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/branco"
|
||||||
|
tools:context=".TermosPoliticasActivity">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/headerLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="16dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/btnBack"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:src="@drawable/diagonalarrowleftdownoutline_110924"
|
||||||
|
app:tint="@color/preto"
|
||||||
|
android:rotation="45"
|
||||||
|
android:padding="4dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
android:contentDescription="Voltar" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Documento"
|
||||||
|
android:textColor="@color/preto"
|
||||||
|
android:textSize="22sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<!-- Scrollable content area -->
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:fillViewport="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/headerLayout">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="24dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvContent"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/preto"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:lineSpacingMultiplier="1.25"
|
||||||
|
android:linksClickable="true"
|
||||||
|
android:autoLink="all" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
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>
|
||||||
@@ -51,6 +51,28 @@
|
|||||||
android:paddingHorizontal="16dp"
|
android:paddingHorizontal="16dp"
|
||||||
android:textColor="@color/preto" />
|
android:textColor="@color/preto" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTickerLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:text="Símbolo / Ticker (Ex: BTC-USD)"
|
||||||
|
android:textColor="@color/preto"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etTickerAtivo"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:background="@drawable/rounded_input_bg"
|
||||||
|
android:hint="BTC-USD, ETH-USD"
|
||||||
|
android:inputType="textCapCharacters"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:textColor="@color/preto"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -92,7 +114,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
android:text="Preço de Compra (€)"
|
android:text="Valor Investido (€)"
|
||||||
android:textColor="@color/preto"
|
android:textColor="@color/preto"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -85,8 +85,8 @@
|
|||||||
android:id="@+id/textView14"
|
android:id="@+id/textView14"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="36dp"
|
android:layout_marginStart="24dp"
|
||||||
android:layout_marginTop="36dp"
|
android:layout_marginTop="32dp"
|
||||||
android:fontFamily="sans-serif"
|
android:fontFamily="sans-serif"
|
||||||
android:text="Valor Total do Portfólio"
|
android:text="Valor Total do Portfólio"
|
||||||
android:textColor="#4A5568"
|
android:textColor="#4A5568"
|
||||||
@@ -98,8 +98,8 @@
|
|||||||
android:id="@+id/textView17"
|
android:id="@+id/textView17"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="36dp"
|
android:layout_marginStart="24dp"
|
||||||
android:layout_marginBottom="36dp"
|
android:layout_marginBottom="40dp"
|
||||||
android:fontFamily="sans-serif-medium"
|
android:fontFamily="sans-serif-medium"
|
||||||
android:text="0.00€"
|
android:text="0.00€"
|
||||||
android:textColor="#1A202C"
|
android:textColor="#1A202C"
|
||||||
|
|||||||
@@ -1,14 +1,81 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:fillViewport="true"
|
||||||
tools:context=".menu.GraficosFragment">
|
tools:context=".menu.GraficosFragment">
|
||||||
|
|
||||||
<!-- TODO: Update blank fragment layout -->
|
<LinearLayout
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/hello_blank_fragment" />
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="100dp"
|
||||||
|
android:paddingTop="16dp">
|
||||||
|
|
||||||
</FrameLayout>
|
<androidx.cardview.widget.CardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
app:cardCornerRadius="16dp"
|
||||||
|
app:cardElevation="0dp"
|
||||||
|
android:backgroundTint="#FFFFFF">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTituloMensal"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Resumo Mensal"
|
||||||
|
android:textColor="#1A202C"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<com.github.mikephil.charting.charts.PieChart
|
||||||
|
android:id="@+id/pieChartMensal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="300dp"
|
||||||
|
android:layout_marginTop="16dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
app:cardCornerRadius="16dp"
|
||||||
|
app:cardElevation="0dp"
|
||||||
|
android:backgroundTint="#FFFFFF">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTituloAnual"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Balanço Anual"
|
||||||
|
android:textColor="#1A202C"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<com.github.mikephil.charting.charts.BarChart
|
||||||
|
android:id="@+id/barChartAnual"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="300dp"
|
||||||
|
android:layout_marginTop="16dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
@@ -247,7 +247,7 @@
|
|||||||
android:layout_marginStart="7dp"
|
android:layout_marginStart="7dp"
|
||||||
android:layout_marginTop="12dp"
|
android:layout_marginTop="12dp"
|
||||||
android:fontFamily="sans-serif"
|
android:fontFamily="sans-serif"
|
||||||
android:text="Valor do Ativos"
|
android:text="Valor de Ativos"
|
||||||
android:textColor="#4A5568"
|
android:textColor="#4A5568"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
|||||||
@@ -12,12 +12,11 @@
|
|||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/adicionarCardView"
|
android:id="@+id/adicionarCardView"
|
||||||
android:layout_width="336dp"
|
android:layout_width="367dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="15dp"
|
|
||||||
android:background="@drawable/cardview_background"
|
android:background="@drawable/cardview_background"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="0.432"
|
app:layout_constraintHorizontal_bias="0.493"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
@@ -28,8 +27,8 @@
|
|||||||
android:layout_marginTop="48dp"
|
android:layout_marginTop="48dp"
|
||||||
android:fontFamily="sans-serif"
|
android:fontFamily="sans-serif"
|
||||||
android:text="Metas Financeiras"
|
android:text="Metas Financeiras"
|
||||||
android:textSize="19sp"
|
|
||||||
android:textColor="@color/preto"
|
android:textColor="@color/preto"
|
||||||
|
android:textSize="19sp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="0.093"
|
app:layout_constraintHorizontal_bias="0.093"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -66,13 +65,13 @@
|
|||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/rvMetas"
|
android:id="@+id/rvMetas"
|
||||||
android:layout_width="327dp"
|
android:layout_width="313dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="597dp"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginTop="100dp"
|
android:layout_marginTop="12dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="0.478"
|
app:layout_constraintHorizontal_bias="0.444"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/novaTransacaoButton" />
|
app:layout_constraintTop_toBottomOf="@+id/novaTransacaoButton" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -43,4 +43,52 @@
|
|||||||
<item>USD ($)</item>
|
<item>USD ($)</item>
|
||||||
<item>BRL (R$)</item>
|
<item>BRL (R$)</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
<string name="terms_title">Termos e Condições</string>
|
||||||
|
<string name="terms_content"><![CDATA[
|
||||||
|
<h3><b>1. Introdução</b></h3>
|
||||||
|
<p>Bem-vindo ao <b>LifeGrid</b>. Ao descarregar, instalar ou utilizar a nossa aplicação, o utilizador concorda e aceita cumprir integralmente os presentes Termos e Condições de Serviço.</p>
|
||||||
|
|
||||||
|
<h3><b>2. Descrição do Serviço</b></h3>
|
||||||
|
<p>O LifeGrid é uma aplicação de gestão de finanças pessoais concebida para ajudar os utilizadores a registar e categorizar transações financeiras, gerir e monitorizar ativos, estabelecer metas financeiras e guardar faturas ou outros documentos comprovativos associados às suas finanças.</p>
|
||||||
|
|
||||||
|
<h3><b>3. Registo e Segurança da Conta</b></h3>
|
||||||
|
<p>Para usufruir de todas as funcionalidades da aplicação, é necessária a criação de uma conta de utilizador através de um endereço de correio eletrónico válido e uma palavra-passe. O utilizador é o único responsável por manter a confidencialidade das suas credenciais de acesso, bem como por qualquer atividade realizada sob a sua conta.</p>
|
||||||
|
|
||||||
|
<h3><b>4. Propriedade Intelectual</b></h3>
|
||||||
|
<p>Todo o design, código-fonte, elementos gráficos, logótipos e conteúdos da aplicação LifeGrid são propriedade exclusiva da equipa de desenvolvimento e protegidos por leis de direitos de autor nacionais e internacionais.</p>
|
||||||
|
|
||||||
|
<h3><b>5. Uso Aceitável e Proibições</b></h3>
|
||||||
|
<p>O utilizador compromete-se a não utilizar o LifeGrid para qualquer fim ilegal, fraudulento ou não autorizado. É estritamente proibido interferir com a segurança dos servidores e bases de dados da infraestrutura (Firebase), ou tentar contornar quaisquer mecanismos de segurança da aplicação.</p>
|
||||||
|
|
||||||
|
<h3><b>6. Limitação de Responsabilidade</b></h3>
|
||||||
|
<p>O LifeGrid é fornecido "tal como está" e "conforme disponível". Não garantimos que a aplicação funcione de forma ininterrupta ou isenta de erros. As decisões financeiras tomadas com base nas informações apresentadas na aplicação são da inteira e exclusiva responsabilidade do utilizador. O LifeGrid não constitui ou substitui qualquer aconselhamento financeiro profissional.</p>
|
||||||
|
|
||||||
|
<h3><b>7. Alterações aos Termos</b></h3>
|
||||||
|
<p>Reservamo-nos o direito de alterar ou atualizar estes Termos e Condições a qualquer momento. Quaisquer atualizações serão publicadas nesta secção e o uso continuado da aplicação constitui a aceitação tácita das novas condições.</p>
|
||||||
|
]]></string>
|
||||||
|
<string name="privacy_title">Política de Privacidade</string>
|
||||||
|
<string name="privacy_content"><![CDATA[
|
||||||
|
<h3><b>1. Informações que Recolhemos</b></h3>
|
||||||
|
<p>Recolhemos os dados que nos faculta ativamente ao registar-se e utilizar a aplicação. Estes incluem:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Dados de Registo: Nome de utilizador e endereço de correio eletrónico.</li>
|
||||||
|
<li>Dados Financeiros: Transações registadas, montantes, categorias, ativos introduzidos e metas estabelecidas.</li>
|
||||||
|
<li>Documentos: Faturas e outros comprovativos fotográficos que decida associar às suas transações.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3><b>2. Finalidade do Tratamento de Dados</b></h3>
|
||||||
|
<p>Os dados recolhidos destinam-se única e exclusivamente a permitir o funcionamento adequado da aplicação LifeGrid, permitindo-lhe gerir a sua carteira, analisar gráficos de despesas/receitas, acompanhar o progresso de metas e guardar comprovativos digitais.</p>
|
||||||
|
|
||||||
|
<h3><b>3. Armazenamento e Segurança dos Dados</b></h3>
|
||||||
|
<p>Para garantir o armazenamento seguro dos seus dados, o LifeGrid utiliza a plataforma cloud <b>Google Firebase</b> (incluindo Firebase Authentication para gestão de identidade, Firebase Realtime Database para sincronização de dados e Firebase Storage para armazenamento de imagens). Implementamos práticas de segurança adequadas para prevenir o acesso não autorizado, alteração ou eliminação dos seus dados.</p>
|
||||||
|
|
||||||
|
<h3><b>4. Partilha de Dados com Terceiros</b></h3>
|
||||||
|
<p>O LifeGrid não partilha, vende, aluga ou comercializa os seus dados pessoais com terceiros para quaisquer fins comerciais ou de marketing. Os dados são transferidos apenas para os servidores do Firebase para efeitos estritos de alojamento e sincronização.</p>
|
||||||
|
|
||||||
|
<h3><b>5. Direitos do Utilizador</b></h3>
|
||||||
|
<p>O utilizador tem controlo total sobre os seus dados, podendo adicionar, editar ou apagar as suas transações, faturas e detalhes de conta a qualquer momento. Se desejar eliminar permanentemente a sua conta e todos os dados associados dos nossos servidores, pode fazê-lo contactando-nos ou solicitando a eliminação.</p>
|
||||||
|
|
||||||
|
<h3><b>6. Alterações a esta Política</b></h3>
|
||||||
|
<p>Podemos atualizar esta Política de Privacidade periodicamente para refletir mudanças nas nossas práticas ou por razões operacionais/legais. Recomendamos a consulta regular desta secção para se manter informado.</p>
|
||||||
|
]]></string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -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>
|
||||||
37
fluxograma_aplicacao.md
Normal file
37
fluxograma_aplicacao.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Sistemas e Aplicações da LifeGrid (Super Simplificado)
|
||||||
|
|
||||||
|
Este diagrama mostra apenas as aplicações, serviços e bases de dados externas utilizadas no funcionamento do **LifeGrid** e como a informação passa entre elas.
|
||||||
|
|
||||||
|
## Fluxo de Informação entre Serviços (Mermaid)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
%% Aplicação Central
|
||||||
|
App[Aplicação Android <br/> LifeGrid]
|
||||||
|
|
||||||
|
%% Serviços de Autenticação
|
||||||
|
App <-->|1. Autenticação e Sessão| Auth[Firebase Authentication]
|
||||||
|
|
||||||
|
%% Inteligência Artificial
|
||||||
|
App -->|2. Imagem da Fatura| Gemini[Gemini API <br/> Google AI SDK]
|
||||||
|
Gemini -->|Dados Extraídos| App
|
||||||
|
|
||||||
|
%% Alojamento de Imagens
|
||||||
|
App -->|3. Ficheiro de Fatura| Storage[Firebase Storage]
|
||||||
|
Storage -->|URL do Ficheiro| App
|
||||||
|
|
||||||
|
%% Base de Dados
|
||||||
|
App <-->|4. Transações, Ativos e Metas| Database[Firebase Realtime Database]
|
||||||
|
|
||||||
|
%% Estilos simples com fundo branco
|
||||||
|
classDef whiteBackground fill:#FFFFFF,stroke:#000000,stroke-width:1.5dp,color:#000000;
|
||||||
|
class App,Auth,Gemini,Storage,Database whiteBackground;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Como a informação passa:
|
||||||
|
1. **Firebase Authentication**: Controla o acesso à aplicação (Login/Registo).
|
||||||
|
2. **Gemini API (Google AI)**: Recebe a imagem da fatura enviada pela app e devolve os dados estruturados (valor, data, descrição).
|
||||||
|
3. **Firebase Storage**: Recebe e armazena os ficheiros de imagem das faturas digitalizadas.
|
||||||
|
4. **Firebase Realtime Database**: Guarda e sincroniza em tempo real todos os dados financeiros (saldos, metas, ativos e referências dos documentos).
|
||||||
@@ -19,6 +19,7 @@ dependencyResolutionManagement {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven { url = uri("https://jitpack.io") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user