From ea0bf79905f558eb58d5cf0abe567a61a1cabe84 Mon Sep 17 00:00:00 2001 From: TuyenLe Date: Tue, 13 May 2025 23:25:07 +0700 Subject: [PATCH] Add login/register --- app/build.gradle.kts | 6 + app/google-services.json | 121 ++++++++++++-- app/src/main/AndroidManifest.xml | 11 +- .../com/example/todoappv2/LoginActivity.java | 151 ++++++++++++++++++ .../com/example/todoappv2/MainActivity.java | 88 ++++++++++ .../example/todoappv2/RegisterActivity.java | 90 +++++++++++ app/src/main/res/drawable/ic_google.xml | 10 ++ app/src/main/res/drawable/ic_logout.xml | 10 ++ app/src/main/res/layout/activity_login.xml | 117 ++++++++++++++ app/src/main/res/layout/activity_register.xml | 135 ++++++++++++++++ app/src/main/res/layout/nav_header.xml | 54 +++---- app/src/main/res/menu/drawer_menu.xml | 9 ++ app/src/main/res/values/strings.xml | 2 + build.gradle.kts | 1 + gradle/libs.versions.toml | 2 + 15 files changed, 763 insertions(+), 44 deletions(-) create mode 100644 app/src/main/java/com/example/todoappv2/LoginActivity.java create mode 100644 app/src/main/java/com/example/todoappv2/RegisterActivity.java create mode 100644 app/src/main/res/drawable/ic_google.xml create mode 100644 app/src/main/res/drawable/ic_logout.xml create mode 100644 app/src/main/res/layout/activity_login.xml create mode 100644 app/src/main/res/layout/activity_register.xml 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" }