diff --git a/app/app/src/main/AndroidManifest.xml b/app/app/src/main/AndroidManifest.xml index b07b3bd..247055f 100644 --- a/app/app/src/main/AndroidManifest.xml +++ b/app/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> + <uses-permission android:name="android.permission.INTERNET" /> <application @@ -40,8 +41,16 @@ android:exported="false" /> <activity - android:name=".CreateDishActivity" /> + android:name=".CreateDishActivity" + android:exported="false" /> + <activity + android:name=".ChangePasswordActivity" + android:exported="false" /> + + <activity + android:name=".EditProfileActivity" + android:exported="false" /> </application> </manifest> \ No newline at end of file diff --git a/app/app/src/main/java/tech/mercantec/easyeat/ChangePasswordActivity.kt b/app/app/src/main/java/tech/mercantec/easyeat/ChangePasswordActivity.kt new file mode 100644 index 0000000..5831967 --- /dev/null +++ b/app/app/src/main/java/tech/mercantec/easyeat/ChangePasswordActivity.kt @@ -0,0 +1,59 @@ +package tech.mercantec.easyeat + +import android.app.ProgressDialog +import android.os.Bundle +import android.widget.Button +import android.widget.EditText +import android.widget.Toast +import androidx.activity.enableEdgeToEdge +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import tech.mercantec.easyeat.helpers.ApiRequestException +import tech.mercantec.easyeat.helpers.changePassword +import kotlin.concurrent.thread + +class ChangePasswordActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_change_password) + + findViewById<Button>(R.id.save).setOnClickListener { + val currentPassword = findViewById<EditText>(R.id.current_password).text.toString() + val newPassword = findViewById<EditText>(R.id.new_password).text.toString() + val confirmPassword = findViewById<EditText>(R.id.confirm_password).text.toString() + + if (newPassword != confirmPassword) { + Toast.makeText(this, "Passwords must match", Toast.LENGTH_LONG).show() + + return@setOnClickListener + } + + val progressDialog = ProgressDialog(this) + progressDialog.setMessage("Loading...") + progressDialog.show() + + thread { + try { + changePassword(this, currentPassword, newPassword) + } catch (e: ApiRequestException) { + runOnUiThread { + Toast.makeText(this, e.message, Toast.LENGTH_LONG).show() + } + + return@thread + } finally { + runOnUiThread { + progressDialog.hide() + } + } + + runOnUiThread { + Toast.makeText(this, "Password changed successfully", Toast.LENGTH_LONG).show() + } + + finish() + } + } + } +} diff --git a/app/app/src/main/java/tech/mercantec/easyeat/EditProfileActivity.kt b/app/app/src/main/java/tech/mercantec/easyeat/EditProfileActivity.kt new file mode 100644 index 0000000..4cb66b1 --- /dev/null +++ b/app/app/src/main/java/tech/mercantec/easyeat/EditProfileActivity.kt @@ -0,0 +1,49 @@ +package tech.mercantec.easyeat + +import android.app.ProgressDialog +import android.os.Bundle +import android.widget.Button +import android.widget.EditText +import android.widget.TextView +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import tech.mercantec.easyeat.helpers.ApiRequestException +import tech.mercantec.easyeat.helpers.updateUser +import kotlin.concurrent.thread + +class EditProfileActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_edit_profile) + + findViewById<EditText>(R.id.username).setText(intent.extras?.getString("username"), TextView.BufferType.EDITABLE) + findViewById<EditText>(R.id.email).setText(intent.extras?.getString("email"), TextView.BufferType.EDITABLE) + + findViewById<Button>(R.id.save).setOnClickListener { + val username = findViewById<EditText>(R.id.username).text.toString().trim() + val email = findViewById<EditText>(R.id.email).text.toString().trim() + + val progressDialog = ProgressDialog(this) + progressDialog.setMessage("Loading...") + progressDialog.show() + + thread { + try { + updateUser(this, username, email) + } catch (e: ApiRequestException) { + runOnUiThread { + Toast.makeText(this, e.message, Toast.LENGTH_LONG).show() + } + + return@thread + } finally { + runOnUiThread { + progressDialog.hide() + } + } + + finish() + } + } + } +} diff --git a/app/app/src/main/java/tech/mercantec/easyeat/helpers/auth.kt b/app/app/src/main/java/tech/mercantec/easyeat/helpers/auth.kt index 6e5e54b..7d483c0 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/helpers/auth.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/helpers/auth.kt @@ -24,6 +24,16 @@ fun login(ctx: Context, email: String, password: String) { } } +fun logout(ctx: Context) { + with (ctx.getSharedPreferences("easyeat", Context.MODE_PRIVATE).edit()) { + remove("user-id") + remove("username") + remove("auth-token") + remove("refresh-token") + apply() + } +} + @Serializable data class CreateUserRequest(val email: String, val userName: String, val password: String) @@ -79,7 +89,23 @@ fun isLoggedIn(ctx: Context): Boolean { data class UserInfoResponse(val id: Int, val userName: String, val email: String) fun getUserInfo(ctx: Context): UserInfoResponse { - val response = requestJson<Unit, UserInfoResponse>(ctx, "GET", "/api/User/get", null) - - return response + return requestJson<Unit, UserInfoResponse>(ctx, "GET", "/api/User/get", null) +} + +@Serializable +data class UpdateUserRequest(val userName: String, val email: String) + +fun updateUser(ctx: Context, username: String, email: String) { + val request = UpdateUserRequest(username, email) + + return requestJson<UpdateUserRequest, Unit>(ctx, "PUT", "/api/User/update", request) +} + +@Serializable +data class ChangePasswordRequest(val oldPassword: String, val newPassword: String) + +fun changePassword(ctx: Context, oldPassword: String, newPassword: String) { + val request = ChangePasswordRequest(oldPassword, newPassword) + + return requestJson<ChangePasswordRequest, Unit>(ctx, "PUT", "/api/User/change-password", request) } diff --git a/app/app/src/main/java/tech/mercantec/easyeat/ui/profile/ProfileFragment.kt b/app/app/src/main/java/tech/mercantec/easyeat/ui/profile/ProfileFragment.kt index 506f555..6c1924d 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/ui/profile/ProfileFragment.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/ui/profile/ProfileFragment.kt @@ -1,15 +1,21 @@ package tech.mercantec.easyeat.ui.profile +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.fragment.app.Fragment +import tech.mercantec.easyeat.ChangePasswordActivity +import tech.mercantec.easyeat.EditProfileActivity +import tech.mercantec.easyeat.LoginActivity +import tech.mercantec.easyeat.WelcomeActivity import tech.mercantec.easyeat.databinding.FragmentProfileBinding import tech.mercantec.easyeat.helpers.ApiRequestException import tech.mercantec.easyeat.helpers.UserInfoResponse import tech.mercantec.easyeat.helpers.getUserInfo +import tech.mercantec.easyeat.helpers.logout import kotlin.concurrent.thread class ProfileFragment : Fragment() { @@ -31,9 +37,28 @@ class ProfileFragment : Fragment() { activity?.runOnUiThread { binding.username.text = userInfo.userName binding.email.text = userInfo.email + + binding.editProfile.setOnClickListener { + val intent = Intent(activity, EditProfileActivity::class.java) + intent.putExtra("username", userInfo.userName) + intent.putExtra("email", userInfo.email) + startActivity(intent) + } } } + binding.changePassword.setOnClickListener { + startActivity(Intent(activity, ChangePasswordActivity::class.java)) + } + + binding.logout.setOnClickListener { + logout(requireContext()) + + val intent = Intent(activity, WelcomeActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + startActivity(intent) + } + return binding.root } } diff --git a/app/app/src/main/res/layout/activity_change_password.xml b/app/app/src/main/res/layout/activity_change_password.xml new file mode 100644 index 0000000..95293be --- /dev/null +++ b/app/app/src/main/res/layout/activity_change_password.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="10dp" + android:orientation="vertical"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="20dp" + android:layout_marginStart="2dp" + android:text="@string/current_password_label" + /> + + <EditText + android:id="@+id/current_password" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/password_hint" + android:autofillHints="password" + android:inputType="textPassword" + /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="20dp" + android:layout_marginStart="2dp" + android:text="@string/new_password_label" + /> + + <EditText + android:id="@+id/new_password" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/password_hint" + android:autofillHints="password" + android:inputType="textPassword" + /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="20dp" + android:layout_marginStart="2dp" + android:text="@string/confirm_new_password_label" + /> + + <EditText + android:id="@+id/confirm_password" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/password_hint" + android:autofillHints="password" + android:inputType="textPassword" + /> + + <Button + android:id="@+id/save" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="40dp" + android:text="@string/save_label" + /> + +</LinearLayout> diff --git a/app/app/src/main/res/layout/activity_edit_profile.xml b/app/app/src/main/res/layout/activity_edit_profile.xml new file mode 100644 index 0000000..39b2b6b --- /dev/null +++ b/app/app/src/main/res/layout/activity_edit_profile.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="10dp" + android:orientation="vertical"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="20dp" + android:layout_marginStart="2dp" + android:text="@string/username_label" + /> + + <EditText + android:id="@+id/username" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/username_hint" + android:autofillHints="username" + android:inputType="text" + /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="20dp" + android:layout_marginStart="2dp" + android:text="@string/email_label" + /> + + <EditText + android:id="@+id/email" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/email_hint" + android:autofillHints="emailAddress" + android:inputType="textEmailAddress" + /> + + <Button + android:id="@+id/save" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="40dp" + android:text="@string/save_label" + /> + +</LinearLayout> diff --git a/app/app/src/main/res/layout/activity_login.xml b/app/app/src/main/res/layout/activity_login.xml index 09b4025..b27b33d 100644 --- a/app/app/src/main/res/layout/activity_login.xml +++ b/app/app/src/main/res/layout/activity_login.xml @@ -17,7 +17,7 @@ <TextView android:layout_marginTop="50sp" - android:layout_marginLeft="2sp" + android:layout_marginStart="2sp" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/email_label" @@ -33,11 +33,11 @@ <TextView android:layout_marginTop="20sp" - android:layout_marginLeft="2sp" + android:layout_marginStart="2sp" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/password_label" - /> + /> <EditText android:id="@+id/password" diff --git a/app/app/src/main/res/layout/fragment_profile.xml b/app/app/src/main/res/layout/fragment_profile.xml index 97bbb99..f2708c8 100644 --- a/app/app/src/main/res/layout/fragment_profile.xml +++ b/app/app/src/main/res/layout/fragment_profile.xml @@ -43,11 +43,36 @@ android:text="@string/loading" /> - <Button - android:layout_marginTop="40dp" + <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/edit_profile_label" - /> + android:orientation="vertical"> -</LinearLayout> \ No newline at end of file + <Button + android:id="@+id/edit_profile" + android:layout_marginTop="40dp" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/edit_profile_label" + /> + + <Button + android:id="@+id/change_password" + android:layout_marginTop="10dp" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/change_password_label" + /> + + <Button + android:id="@+id/logout" + android:layout_marginTop="40dp" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:backgroundTint="@color/dark_gray" + android:text="@string/logout_label" + /> + + </LinearLayout> + +</LinearLayout> diff --git a/app/app/src/main/res/values/strings.xml b/app/app/src/main/res/values/strings.xml index 3868279..e134a57 100644 --- a/app/app/src/main/res/values/strings.xml +++ b/app/app/src/main/res/values/strings.xml @@ -14,8 +14,12 @@ <string name="email_hint">john@doe.com</string> <string name="password_label">Password</string> <string name="confirm_password_label">Confirm password</string> + <string name="current_password_label">Current password</string> + <string name="new_password_label">New password</string> + <string name="confirm_new_password_label">Confirm new password</string> <string name="password_hint">••••••••</string> <string name="back_label">Back</string> + <string name="save_label">Save</string> <string name="create_dish_heading">Create Dish</string> <string name="ingredient_label">Ingredient</string> <string name="measurement_label">Measurement</string> @@ -32,6 +36,8 @@ <string name="ingredient_amount_hint">500</string> <string name="ingredient_name_label">Name</string> <string name="ingredient_name_hint">Beef</string> + <string name="change_password_label">Change password</string> + <string name="logout_label">Log out</string> <string-array name="units"> <item>g</item> <item>kg</item>