diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 1750292..7cec868 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,5 +1,6 @@
plugins {
alias(libs.plugins.android.application)
+ id("com.google.gms.google-services")
}
android {
@@ -62,6 +63,11 @@ dependencies {
implementation("com.github.bumptech.glide:glide:4.16.0")
annotationProcessor("com.github.bumptech.glide:compiler:4.16.0")
+ // Firebase
+ implementation(platform("com.google.firebase:firebase-bom:32.7.2"))
+ implementation("com.google.firebase:firebase-auth")
+ implementation("com.google.android.gms:play-services-auth:20.7.0")
+
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
diff --git a/app/google-services.json b/app/google-services.json
index 7605248..d86e750 100644
--- a/app/google-services.json
+++ b/app/google-services.json
@@ -1,41 +1,144 @@
{
"project_info": {
- "project_number": "396348569226",
- "project_id": "to-do-app-49217",
- "storage_bucket": "to-do-app-49217.firebasestorage.app"
+ "project_number": "767282860838",
+ "project_id": "to-do-1d14f",
+ "storage_bucket": "to-do-1d14f.firebasestorage.app"
},
"client": [
{
"client_info": {
- "mobilesdk_app_id": "1:396348569226:android:1a57f94b687fdf83a21f73",
+ "mobilesdk_app_id": "1:767282860838:android:7414e6a2a5491489d6e7e7",
+ "android_client_info": {
+ "package_name": "com.example.login"
+ }
+ },
+ "oauth_client": [
+ {
+ "client_id": "767282860838-8kn6msc75sl7fc2qk6tbb79mnlacck98.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ],
+ "api_key": [
+ {
+ "current_key": "AIzaSyC2uu3l_afXE62nLGJBlWZcJjMJqvSnKNw"
+ }
+ ],
+ "services": {
+ "appinvite_service": {
+ "other_platform_oauth_client": [
+ {
+ "client_id": "767282860838-8kn6msc75sl7fc2qk6tbb79mnlacck98.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ]
+ }
+ }
+ },
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:767282860838:android:fc85b1d75558ae89d6e7e7",
+ "android_client_info": {
+ "package_name": "com.example.todoapp"
+ }
+ },
+ "oauth_client": [
+ {
+ "client_id": "767282860838-8n21vh7f2plpsjogn518lpam46usdcid.apps.googleusercontent.com",
+ "client_type": 1,
+ "android_info": {
+ "package_name": "com.example.todoapp",
+ "certificate_hash": "4bff53fcb2a58c2562df2312aab097de9f6715ae"
+ }
+ },
+ {
+ "client_id": "767282860838-8kn6msc75sl7fc2qk6tbb79mnlacck98.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ],
+ "api_key": [
+ {
+ "current_key": "AIzaSyC2uu3l_afXE62nLGJBlWZcJjMJqvSnKNw"
+ }
+ ],
+ "services": {
+ "appinvite_service": {
+ "other_platform_oauth_client": [
+ {
+ "client_id": "767282860838-8kn6msc75sl7fc2qk6tbb79mnlacck98.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ]
+ }
+ }
+ },
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:767282860838:android:ba1adf879e8432cfd6e7e7",
"android_client_info": {
"package_name": "com.example.todoappv2"
}
},
"oauth_client": [
{
- "client_id": "396348569226-d3fd7d7s6tm4vl6ijfbpjt8gvbslrj9v.apps.googleusercontent.com",
+ "client_id": "767282860838-m9hb1tq84usudk3po63f34s5smo9jvgb.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.example.todoappv2",
- "certificate_hash": "9c04ba4ed1a9957a60183031dde2beb7c8e39e75"
+ "certificate_hash": "4bff53fcb2a58c2562df2312aab097de9f6715ae"
+ }
+ },
+ {
+ "client_id": "767282860838-8kn6msc75sl7fc2qk6tbb79mnlacck98.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ],
+ "api_key": [
+ {
+ "current_key": "AIzaSyC2uu3l_afXE62nLGJBlWZcJjMJqvSnKNw"
+ }
+ ],
+ "services": {
+ "appinvite_service": {
+ "other_platform_oauth_client": [
+ {
+ "client_id": "767282860838-8kn6msc75sl7fc2qk6tbb79mnlacck98.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ]
+ }
+ }
+ },
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:767282860838:android:816f9590c5136c3bd6e7e7",
+ "android_client_info": {
+ "package_name": "com.example.ver1"
+ }
+ },
+ "oauth_client": [
+ {
+ "client_id": "767282860838-bc3drpltfpb14d7g7ec5fg94e8pa0jle.apps.googleusercontent.com",
+ "client_type": 1,
+ "android_info": {
+ "package_name": "com.example.ver1",
+ "certificate_hash": "4bff53fcb2a58c2562df2312aab097de9f6715ae"
}
},
{
- "client_id": "396348569226-sfsf7pftqp1p4bcpb57h9ilt50mbkdqb.apps.googleusercontent.com",
+ "client_id": "767282860838-8kn6msc75sl7fc2qk6tbb79mnlacck98.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
- "current_key": "AIzaSyDtCC8nXM8q4RtDXRi7VbEzTc2I6UFSmrI"
+ "current_key": "AIzaSyC2uu3l_afXE62nLGJBlWZcJjMJqvSnKNw"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
- "client_id": "396348569226-sfsf7pftqp1p4bcpb57h9ilt50mbkdqb.apps.googleusercontent.com",
+ "client_id": "767282860838-8kn6msc75sl7fc2qk6tbb79mnlacck98.apps.googleusercontent.com",
"client_type": 3
}
]
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 62e81e2..b2ed0f1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -20,15 +20,22 @@
android:theme="@style/Theme.TodoAppV2"
tools:targetApi="31">
-
+
+
+
+
{
+ String email = emailEditText.getText().toString().trim();
+ String password = passwordEditText.getText().toString().trim();
+
+ if (email.isEmpty() || password.isEmpty()) {
+ Toast.makeText(this, "Email và mật khẩu không được để trống", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ mAuth.signInWithEmailAndPassword(email, password)
+ .addOnCompleteListener(task -> {
+ if (task.isSuccessful()) {
+ Toast.makeText(this, "Đăng nhập thành công!", Toast.LENGTH_SHORT).show();
+ startActivity(new Intent(this, MainActivity.class));
+ finish();
+ } else {
+ Toast.makeText(this, "Đăng nhập thất bại: " + task.getException().getMessage(), Toast.LENGTH_SHORT).show();
+ }
+ });
+ });
+
+ // Register navigation
+ findViewById(R.id.goToRegister).setOnClickListener(v -> {
+ startActivity(new Intent(this, RegisterActivity.class));
+ });
+
+ // Configure Google Sign-In
+ GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
+ .requestIdToken(getString(R.string.default_web_client_id))
+ .requestEmail()
+ .build();
+
+ mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
+
+ // Google Sign-In button
+ googleLoginButton.setOnClickListener(v -> signInWithGoogle());
+ }
+
+ private void signInWithGoogle() {
+ mGoogleSignInClient.signOut().addOnCompleteListener(this, task -> {
+ Intent signInIntent = mGoogleSignInClient.getSignInIntent();
+ startActivityForResult(signInIntent, RC_SIGN_IN);
+ });
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if (requestCode == RC_SIGN_IN) {
+ Task task = GoogleSignIn.getSignedInAccountFromIntent(data);
+ try {
+ GoogleSignInAccount account = task.getResult(ApiException.class);
+ String idToken = account.getIdToken();
+ if (idToken != null) {
+ firebaseAuthWithGoogle(idToken);
+ } else {
+ Toast.makeText(this, "Không lấy được ID token", Toast.LENGTH_SHORT).show();
+ }
+ } catch (ApiException e) {
+ Log.e(TAG, "Đăng nhập Google thất bại", e);
+ Toast.makeText(this, "Đăng nhập Google thất bại: " + e.getMessage(), Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+
+ private void firebaseAuthWithGoogle(String idToken) {
+ AuthCredential credential = GoogleAuthProvider.getCredential(idToken, null);
+ mAuth.signInWithCredential(credential)
+ .addOnCompleteListener(this, task -> {
+ if (task.isSuccessful()) {
+ FirebaseUser user = mAuth.getCurrentUser();
+ Toast.makeText(this, "Đăng nhập thành công: " + user.getDisplayName(), Toast.LENGTH_SHORT).show();
+ startActivity(new Intent(this, MainActivity.class));
+ finish();
+ } else {
+ Log.e(TAG, "Firebase Auth thất bại", task.getException());
+ Toast.makeText(this, "Xác thực Firebase thất bại", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ // Check if user is signed in
+ FirebaseUser currentUser = mAuth.getCurrentUser();
+ if (currentUser != null) {
+ startActivity(new Intent(this, MainActivity.class));
+ finish();
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/todoappv2/MainActivity.java b/app/src/main/java/com/example/todoappv2/MainActivity.java
index 41c6fb5..4b184de 100644
--- a/app/src/main/java/com/example/todoappv2/MainActivity.java
+++ b/app/src/main/java/com/example/todoappv2/MainActivity.java
@@ -57,6 +57,11 @@
import android.graphics.BitmapFactory;
import java.io.ByteArrayOutputStream;
import android.util.Base64;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.auth.FirebaseUser;
+import com.google.android.gms.auth.api.signin.GoogleSignIn;
+import com.google.android.gms.auth.api.signin.GoogleSignInClient;
+import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
private TodoViewModel todoViewModel;
@@ -75,6 +80,10 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
private static final String KEY_PROFILE_IMAGE = "profile_image";
private SharedPreferences sharedPreferences;
private Uri currentProfileImageUri;
+ private FirebaseAuth mAuth;
+ private GoogleSignInClient mGoogleSignInClient;
+ private TextView navUsername, navEmail;
+ private ShapeableImageView navProfileImage;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -202,6 +211,30 @@ protected void onCreate(Bundle savedInstanceState) {
// Set default selected item
navigationView.setCheckedItem(R.id.nav_home);
+
+ // Initialize Firebase Auth
+ mAuth = FirebaseAuth.getInstance();
+
+ // Configure Google Sign In
+ GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
+ .requestIdToken(getString(R.string.default_web_client_id))
+ .requestEmail()
+ .build();
+ mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
+
+ // Setup navigation header views
+ navUsername = headerView.findViewById(R.id.nav_header_name);
+ navEmail = headerView.findViewById(R.id.nav_header_email);
+ navProfileImage = headerView.findViewById(R.id.nav_header_image);
+
+ // Make the image circular
+ navProfileImage.setShapeAppearanceModel(navProfileImage.getShapeAppearanceModel()
+ .toBuilder()
+ .setAllCornerSizes(50f)
+ .build());
+
+ // Update navigation header with user info
+ updateNavigationHeader();
}
private void showDeleteConfirmationDialog(TodoWithCategories todoWithCategories) {
@@ -461,6 +494,36 @@ private Bitmap getResizedBitmap(Bitmap bitmap, int maxSize) {
return Bitmap.createScaledBitmap(bitmap, width, height, true);
}
+ private void updateNavigationHeader() {
+ FirebaseUser currentUser = mAuth.getCurrentUser();
+ if (currentUser != null) {
+ // Update name
+ String displayName = currentUser.getDisplayName();
+ if (displayName != null && !displayName.isEmpty()) {
+ navUsername.setText(displayName);
+ } else {
+ navUsername.setText("User");
+ }
+
+ // Update email
+ String email = currentUser.getEmail();
+ if (email != null) {
+ navEmail.setText(email);
+ }
+
+ // Update profile image
+ if (currentUser.getPhotoUrl() != null) {
+ Glide.with(this)
+ .load(currentUser.getPhotoUrl())
+ .placeholder(R.drawable.default_profile)
+ .error(R.drawable.default_profile)
+ .into(navProfileImage);
+ } else {
+ navProfileImage.setImageResource(R.drawable.default_profile);
+ }
+ }
+ }
+
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
int id = item.getItemId();
@@ -476,6 +539,17 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) {
startActivity(new Intent(this, CategoryManagementActivity.class));
} else if (id == R.id.nav_about) {
startActivity(new Intent(this, AboutActivity.class));
+ } else if (id == R.id.nav_logout) {
+ // Sign out from Firebase
+ mAuth.signOut();
+ // Sign out from Google
+ mGoogleSignInClient.signOut().addOnCompleteListener(this, task -> {
+ // Navigate to login screen
+ Intent intent = new Intent(MainActivity.this, LoginActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ startActivity(intent);
+ finish();
+ });
}
drawerLayout.closeDrawer(GravityCompat.START);
@@ -490,4 +564,18 @@ public void onBackPressed() {
super.onBackPressed();
}
}
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ // Check if user is signed in
+ FirebaseUser currentUser = mAuth.getCurrentUser();
+ if (currentUser == null) {
+ // If not signed in, launch the Login Activity
+ Intent intent = new Intent(this, LoginActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ startActivity(intent);
+ finish();
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/todoappv2/RegisterActivity.java b/app/src/main/java/com/example/todoappv2/RegisterActivity.java
new file mode 100644
index 0000000..97ca21b
--- /dev/null
+++ b/app/src/main/java/com/example/todoappv2/RegisterActivity.java
@@ -0,0 +1,90 @@
+package com.example.todoappv2;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.google.android.gms.tasks.OnCompleteListener;
+import com.google.android.gms.tasks.Task;
+import com.google.android.material.button.MaterialButton;
+import com.google.android.material.textfield.TextInputEditText;
+import com.google.firebase.auth.AuthResult;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.auth.FirebaseUser;
+
+public class RegisterActivity extends AppCompatActivity {
+ private static final String TAG = "RegisterActivity";
+
+ private TextInputEditText emailEditText, passwordEditText, confirmPasswordEditText;
+ private MaterialButton registerButton, googleLoginButton;
+ private FirebaseAuth mAuth;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_register);
+
+ mAuth = FirebaseAuth.getInstance();
+
+ // Initialize views
+ emailEditText = findViewById(R.id.emailEditText);
+ passwordEditText = findViewById(R.id.passwordEditText);
+ confirmPasswordEditText = findViewById(R.id.confirmPasswordEditText);
+ registerButton = findViewById(R.id.registerButton);
+ googleLoginButton = findViewById(R.id.googleLoginButton);
+
+ // Register button click
+ registerButton.setOnClickListener(v -> {
+ String email = emailEditText.getText().toString().trim();
+ String password = passwordEditText.getText().toString().trim();
+ String confirmPassword = confirmPasswordEditText.getText().toString().trim();
+
+ if (email.isEmpty() || password.isEmpty() || confirmPassword.isEmpty()) {
+ Toast.makeText(this, "Vui lòng điền đầy đủ thông tin", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ if (!password.equals(confirmPassword)) {
+ Toast.makeText(this, "Mật khẩu xác nhận không khớp", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ mAuth.createUserWithEmailAndPassword(email, password)
+ .addOnCompleteListener(this, new OnCompleteListener() {
+ @Override
+ public void onComplete(@NonNull Task task) {
+ if (task.isSuccessful()) {
+ Log.d(TAG, "createUserWithEmail:success");
+ FirebaseUser user = mAuth.getCurrentUser();
+ Toast.makeText(RegisterActivity.this, "Đăng ký thành công!", Toast.LENGTH_SHORT).show();
+ Intent intent = new Intent(RegisterActivity.this, LoginActivity.class);
+ intent.putExtra("username", email);
+ intent.putExtra("password", password);
+ startActivity(intent);
+ finish();
+ } else {
+ Log.w(TAG, "createUserWithEmail:failure", task.getException());
+ Toast.makeText(RegisterActivity.this, "Đăng ký thất bại: " + task.getException().getMessage(),
+ Toast.LENGTH_LONG).show();
+ }
+ }
+ });
+ });
+
+ // Login navigation
+ findViewById(R.id.goToLogin).setOnClickListener(v -> {
+ startActivity(new Intent(this, LoginActivity.class));
+ finish();
+ });
+
+ // Google login navigation
+ googleLoginButton.setOnClickListener(v -> {
+ startActivity(new Intent(this, LoginActivity.class));
+ finish();
+ });
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_google.xml b/app/src/main/res/drawable/ic_google.xml
new file mode 100644
index 0000000..8a4a48f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_google.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_logout.xml b/app/src/main/res/drawable/ic_logout.xml
new file mode 100644
index 0000000..bc2f158
--- /dev/null
+++ b/app/src/main/res/drawable/ic_logout.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
new file mode 100644
index 0000000..f8c362e
--- /dev/null
+++ b/app/src/main/res/layout/activity_login.xml
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_register.xml b/app/src/main/res/layout/activity_register.xml
new file mode 100644
index 0000000..b6d87c6
--- /dev/null
+++ b/app/src/main/res/layout/activity_register.xml
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/nav_header.xml b/app/src/main/res/layout/nav_header.xml
index 25d30ca..c556437 100644
--- a/app/src/main/res/layout/nav_header.xml
+++ b/app/src/main/res/layout/nav_header.xml
@@ -9,49 +9,37 @@
android:padding="16dp"
android:theme="@style/ThemeOverlay.AppCompat.Dark">
-
-
-
-
-
-
-
+
+ android:text="User" />
+ android:text="user@example.com" />
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/drawer_menu.xml b/app/src/main/res/menu/drawer_menu.xml
index 44073d5..7508c3d 100644
--- a/app/src/main/res/menu/drawer_menu.xml
+++ b/app/src/main/res/menu/drawer_menu.xml
@@ -22,4 +22,13 @@
android:icon="@drawable/ic_info"
android:title="@string/nav_about" />
+
+ -
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9ed61bc..cf6eb10 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -91,4 +91,6 @@
Focus
Categories
About
+ 767282860838-8kn6msc75sl7fc2qk6tbb79mnlacck98.apps.googleusercontent.com
+ Profile image
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 3756278..ade54f3 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,4 +1,5 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
+ id("com.google.gms.google-services") version "4.4.1" apply false
}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index c0ae0e0..39bd261 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -8,6 +8,7 @@ material = "1.12.0"
activity = "1.10.1"
constraintlayout = "2.2.1"
roomCommonJvm = "2.7.1"
+firebaseAuth = "23.2.0"
[libraries]
junit = { group = "junit", name = "junit", version.ref = "junit" }
@@ -18,6 +19,7 @@ material = { group = "com.google.android.material", name = "material", version.r
activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
room-common-jvm = { group = "androidx.room", name = "room-common-jvm", version.ref = "roomCommonJvm" }
+firebase-auth = { group = "com.google.firebase", name = "firebase-auth", version.ref = "firebaseAuth" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }