Commit f2064688 authored by Niklas Fix's avatar Niklas Fix 🎓

implement forgot password #25

parent c4a58f56
......@@ -14,6 +14,11 @@
android:supportsRtl="true"
android:theme="@style/WhiteTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity
android:name=".features.password.ForgotPasswordActivity"
android:label="@string/reset_your_password"
android:parentActivityName=".features.login.LoginActivity"
android:screenOrientation="portrait" />
<activity
android:name=".features.summary.SummaryActivity"
android:screenOrientation="portrait" />
......@@ -53,7 +58,7 @@
<activity
android:name=".features.title.TitleActivity"
android:label="@string/title_activity_choose_title"
android:parentActivityName=".features.title.TitleActivity"
android:parentActivityName=".features.profile.ProfileActivity"
android:screenOrientation="portrait" />
<activity
android:name=".features.questionpools.QuestionPoolsActivity"
......@@ -66,9 +71,9 @@
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize" />
<activity
android:name="de.akamu.tudarmstadt.features.settings.SettingsActivity"
android:label="@string/title_activity_settings"
android:name=".features.settings.SettingsActivity"
android:configChanges="locale"
android:label="@string/title_activity_settings"
android:parentActivityName=".features.dashboard.MainActivity"
android:screenOrientation="portrait" />
......
package de.akamu.tudarmstadt.api;
import android.util.ArrayMap;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.Map;
import de.akamu.tudarmstadt.exceptions.AkamuAPIException;
public final class V2APIForgotPassword {
public static void requestNewPassword(String usernameOrEmail) throws IOException, AkamuAPIException, JSONException {
Map<String, String> parameters = new ArrayMap<>(0);
JSONObject json = new JSONObject();
if (usernameOrEmail.contains("@")) {
json.put("email", usernameOrEmail);
} else {
json.put("username", usernameOrEmail);
}
V2API.post("forgotpassword", json.toString(), parameters);
}
}
......@@ -4,7 +4,7 @@ import de.akamu.tudarmstadt.model.User
interface PasswordDataSource {
interface VerifyPasswordDataSource {
interface VerifyPasswordCallback {
/**
* Called when the given password matches the user's password
......@@ -23,6 +23,21 @@ interface PasswordDataSource {
/**
* Checks if the given [password] matches the user's password
*/
fun verifyPassword(user: User, password: String, firebaseToken: String, callback: PasswordDataSource.VerifyPasswordDataSource)
fun verifyPassword(user: User, password: String, firebaseToken: String, callback: VerifyPasswordCallback)
interface RequestNewPasswordCallback {
fun onRequestNewPasswordSuccess()
fun onRequestNewPasswordFailed(reason: String)
fun onNoSuchUserError()
}
/**
* Sends a request for resetting the user's password
*
* @param usernameOrEmail the user or its email address
* @param callback RequestNewPasswordCallback
*/
fun requestNewPassword(usernameOrEmail: String, callback: RequestNewPasswordCallback)
}
\ No newline at end of file
package de.akamu.tudarmstadt.data.password
import android.os.AsyncTask
import de.akamu.tudarmstadt.api.V2APIForgotPassword
import de.akamu.tudarmstadt.api.V2APILogin
import de.akamu.tudarmstadt.exceptions.AkamuAPIException
import de.akamu.tudarmstadt.model.Login
import de.akamu.tudarmstadt.model.User
import java.lang.Exception
class PasswordDataSourceImpl : PasswordDataSource {
override fun verifyPassword(user: User, password: String, firebaseToken: String, callback: PasswordDataSource.VerifyPasswordDataSource) {
override fun verifyPassword(
user: User,
password: String,
firebaseToken: String,
callback: PasswordDataSource.VerifyPasswordCallback
) {
val task = VerifyPasswordTask(user, password, firebaseToken, callback)
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
}
class VerifyPasswordTask(private val user: User, private val password: String, private val firebaseToken: String, private val callback: PasswordDataSource.VerifyPasswordDataSource)
: AsyncTask<Void, Void, Boolean>() {
class VerifyPasswordTask(
private val user: User,
private val password: String,
private val firebaseToken: String,
private val callback: PasswordDataSource.VerifyPasswordCallback
) : AsyncTask<Void, Void, Boolean>() {
private var errorCode = 0
......@@ -27,6 +37,8 @@ class PasswordDataSourceImpl : PasswordDataSource {
} catch (e: AkamuAPIException) {
errorCode = e.code
return false
} catch (e: Exception) {
return false
}
return true
......@@ -47,4 +59,48 @@ class PasswordDataSourceImpl : PasswordDataSource {
}
override fun requestNewPassword(
usernameOrEmail: String,
callback: PasswordDataSource.RequestNewPasswordCallback
) {
val task = RequestNewPasswordTask(usernameOrEmail, callback)
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
}
class RequestNewPasswordTask(
private val usernameOrEmail: String,
private val callback: PasswordDataSource.RequestNewPasswordCallback
) : AsyncTask<Void, Void, Boolean>() {
private var errorCode = 0
private var errorMsg = "Oops. Something went wrong..."
override fun doInBackground(vararg params: Void?): Boolean {
try {
V2APIForgotPassword.requestNewPassword(usernameOrEmail)
} catch (e: AkamuAPIException) {
errorCode = e.code
errorMsg = e.message!!
return false
} catch (e: Exception) {
errorMsg = e.message!!
return false
}
return true
}
override fun onPostExecute(result: Boolean?) {
when {
result!! -> callback.onRequestNewPasswordSuccess()
errorCode == 404 -> callback.onNoSuchUserError()
else -> callback.onRequestNewPasswordFailed(errorMsg)
}
}
}
}
\ No newline at end of file
......@@ -10,6 +10,7 @@ import de.akamu.tudarmstadt.R
import de.akamu.tudarmstadt.data.login.LoginDataSourceImpl
import de.akamu.tudarmstadt.features.dashboard.MainActivity
import de.akamu.tudarmstadt.features.dse.DSEActivity
import de.akamu.tudarmstadt.features.password.ForgotPasswordActivity
import de.akamu.tudarmstadt.model.User
import de.akamu.tudarmstadt.util.AppUserUtil
import de.akamu.tudarmstadt.util.Constants
......@@ -42,6 +43,10 @@ class LoginActivity : BaseActivity(), LoginContract.View {
logo_login.start()
forgot_password_button.setOnClickListener {
startActivity(Intent(this, ForgotPasswordActivity::class.java))
}
login_button.setOnClickListener {
val username: String = login_username_input.text.toString()
val password: String = login_password_input.text.toString()
......
......@@ -29,7 +29,7 @@ class ChangePasswordActivity :
supportActionBar?.setDisplayHomeAsUpEnabled(true)
presenter = ChangePasswordPresenter(ChangePasswordInteractor(PasswordDataSourceImpl()),
presenter = ChangePasswordPresenter(PasswordInteractor(PasswordDataSourceImpl()),
UserInteractor(UserDataSourceImpl()), this)
user = AppUserUtil.getLocalAppUser(this)
......
package de.akamu.tudarmstadt.features.password
import de.akamu.tudarmstadt.data.password.PasswordDataSource
import de.akamu.tudarmstadt.model.User
class ChangePasswordInteractor(private val dataSource: PasswordDataSource) : PasswordDataSource {
override fun verifyPassword(user: User, password: String, firebaseToken: String, callback: PasswordDataSource.VerifyPasswordDataSource) {
dataSource.verifyPassword(user, password, firebaseToken, object : PasswordDataSource.VerifyPasswordDataSource {
override fun passwordCorrect() {
callback.passwordCorrect()
}
override fun passwordInCorrect() {
callback.passwordInCorrect()
}
override fun passwordError(msg: String) {
callback.passwordError(msg)
}
})
}
}
\ No newline at end of file
......@@ -6,7 +6,7 @@ import de.akamu.tudarmstadt.data.user.UserDataSource
import de.akamu.tudarmstadt.interactors.UserInteractor
import de.akamu.tudarmstadt.model.User
class ChangePasswordPresenter(private val interactor: ChangePasswordInteractor,
class ChangePasswordPresenter(private val interactor: PasswordInteractor,
private val userInteractor: UserInteractor,
private var view: ChangePasswordContract.View?)
: ChangePasswordContract.Presenter {
......@@ -27,7 +27,7 @@ class ChangePasswordPresenter(private val interactor: ChangePasswordInteractor,
AkamuFirebase.loadFirebaseToken(object : AkamuFirebase.GetFirebaseTokenCallback {
override fun onGetFirebaseTokenSuccess(firebaseToken: String) {
interactor.verifyPassword(user, currentPw, firebaseToken, object: PasswordDataSource.VerifyPasswordDataSource {
interactor.verifyPassword(user, currentPw, firebaseToken, object: PasswordDataSource.VerifyPasswordCallback {
override fun passwordCorrect() {
user.password = newPw
updateUserWithNewPW(newPw)
......
package de.akamu.tudarmstadt.features.password
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import com.google.android.material.snackbar.Snackbar
import de.akamu.tudarmstadt.R
import de.akamu.tudarmstadt.data.password.PasswordDataSourceImpl
import de.akamu.tudarmstadt.data.user.UserDataSourceImpl
import de.akamu.tudarmstadt.interactors.UserInteractor
import de.akamu.tudarmstadt.model.User
import de.akamu.tudarmstadt.util.AppUserUtil
import kotlinx.android.synthetic.main.activity_change_password.*
import kotlinx.android.synthetic.main.activity_forgot_password.*
class ForgotPasswordActivity : AppCompatActivity(), ForgotPasswordContract.View,
View.OnClickListener {
override lateinit var presenter: ForgotPasswordContract.Presenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setTheme(R.style.BlueTheme)
setContentView(R.layout.activity_forgot_password)
setSupportActionBar(toolbar_forgot_password)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
forgot_password_confirm_btn.setOnClickListener(this)
presenter = ForgotPasswordPresenter(PasswordInteractor(PasswordDataSourceImpl()), this)
}
override fun onClick(v: View?) {
if (v!!.id == forgot_password_confirm_btn.id) {
val usernameOrEmail = forgot_password_input_text.text.toString()
presenter.requestNewPassword(usernameOrEmail)
}
}
override fun showRequestNewPasswordSuccess() {
Snackbar.make(findViewById(R.id.coordinator_forgot_password),
R.string.email_sent, Snackbar.LENGTH_LONG).show()
}
override fun showRequestNewPasswordFailed(reason: String) {
Snackbar.make(findViewById(R.id.coordinator_forgot_password),
getString(R.string.failed_to_send_email, reason), Snackbar.LENGTH_LONG).show()
}
override fun showNoSuchUserError() {
Snackbar.make(findViewById(R.id.coordinator_forgot_password),
R.string.no_such_user, Snackbar.LENGTH_LONG).show()
}
override fun disableConfirmButton() {
forgot_password_confirm_btn.visibility = View.GONE
}
override fun enableConfirmButton() {
forgot_password_confirm_btn.visibility = View.VISIBLE
}
override fun showProgress() {
forgot_password_progress_container.visibility = View.VISIBLE
}
override fun hideProgress() {
forgot_password_progress_container.visibility = View.GONE
}
}
\ No newline at end of file
package de.akamu.tudarmstadt.features.password
import de.akamu.tudarmstadt.BasePresenter
import de.akamu.tudarmstadt.BaseView
class ForgotPasswordContract {
interface View : BaseView<Presenter> {
fun showRequestNewPasswordSuccess()
fun showRequestNewPasswordFailed(reason: String)
fun showNoSuchUserError()
fun disableConfirmButton()
fun enableConfirmButton()
fun showProgress()
fun hideProgress()
}
interface Presenter : BasePresenter {
fun requestNewPassword(usernameOrEmail: String)
}
}
\ No newline at end of file
package de.akamu.tudarmstadt.features.password
import de.akamu.tudarmstadt.data.password.PasswordDataSource
class ForgotPasswordPresenter(private val interactor: PasswordInteractor,
private var view: ForgotPasswordContract.View?)
: ForgotPasswordContract.Presenter {
init {
view?.presenter = this
}
override fun requestNewPassword(usernameOrEmail: String) {
view?.disableConfirmButton()
view?.showProgress()
interactor.requestNewPassword(usernameOrEmail, object : PasswordDataSource.RequestNewPasswordCallback {
override fun onRequestNewPasswordSuccess() {
view?.hideProgress()
view?.enableConfirmButton()
view?.showRequestNewPasswordSuccess()
}
override fun onRequestNewPasswordFailed(reason: String) {
view?.hideProgress()
view?.enableConfirmButton()
view?.showRequestNewPasswordFailed(reason)
}
override fun onNoSuchUserError() {
view?.hideProgress()
view?.enableConfirmButton()
view?.showNoSuchUserError()
}
})
}
override fun stop() {
view = null
}
}
\ No newline at end of file
package de.akamu.tudarmstadt.features.password
import de.akamu.tudarmstadt.data.password.PasswordDataSource
import de.akamu.tudarmstadt.model.User
class PasswordInteractor(private val dataSource: PasswordDataSource) : PasswordDataSource {
override fun verifyPassword(
user: User,
password: String,
firebaseToken: String,
callback: PasswordDataSource.VerifyPasswordCallback
) {
dataSource.verifyPassword(
user,
password,
firebaseToken,
object : PasswordDataSource.VerifyPasswordCallback {
override fun passwordCorrect() {
callback.passwordCorrect()
}
override fun passwordInCorrect() {
callback.passwordInCorrect()
}
override fun passwordError(msg: String) {
callback.passwordError(msg)
}
})
}
override fun requestNewPassword(
usernameOrEmail: String,
callback: PasswordDataSource.RequestNewPasswordCallback
) {
dataSource.requestNewPassword(usernameOrEmail, object : PasswordDataSource.RequestNewPasswordCallback {
override fun onRequestNewPasswordSuccess() {
callback.onRequestNewPasswordSuccess()
}
override fun onRequestNewPasswordFailed(reason: String) {
callback.onRequestNewPasswordFailed(reason)
}
override fun onNoSuchUserError() {
callback.onNoSuchUserError()
}
})
}
}
\ No newline at end of file
<?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:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/coordinator_forgot_password"
android:background="@color/white"
android:animateLayoutChanges="true"
tools:context=".features.password.ForgotPasswordActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar_forgot_password"
android:layout_width="match_parent"
android:layout_height="?android:actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar_forgot_password">
<TextView
android:id="@+id/textView_forgot_your_password"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_margin_side"
android:layout_marginTop="@dimen/activity_margin_top"
android:layout_marginEnd="@dimen/activity_margin_side"
android:text="@string/forgot_your_password"
android:textSize="23sp"
android:textStyle="bold"
android:textColor="@color/darkText"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView_forgotPassword_descr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_margin_top"
android:paddingStart="@dimen/activity_margin_side"
android:paddingEnd="@dimen/activity_margin_side"
android:text="@string/no_problem_just_enter_your_username_or_your_user_email_address_below_and_we_will_send_you_an_email_it_will_contain_all_the_instructions_for_resetting_your_password"
android:textColor="@color/textLight"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_forgot_your_password" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textinputlayout_forgotPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_margin_side"
android:layout_marginTop="50dp"
android:layout_marginEnd="@dimen/activity_margin_side"
android:hint="@string/your_username_or_email"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView_forgotPassword_descr">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/forgot_password_input_text"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/forgot_password_confirm_btn"
style="@style/AppButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:paddingStart="40dp"
android:paddingEnd="40dp"
android:text="@string/confirm"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textinputlayout_forgotPassword" />
<LinearLayout
android:id="@+id/forgot_password_progress_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_margin_top"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/forgot_password_confirm_btn">
<ProgressBar
android:id="@+id/forgot_password_progress"
android:layout_width="30dp"
android:layout_height="30dp" />
<TextView
android:id="@+id/forgot_password_progress_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="10dp"
android:text="@string/sending_email"
android:textColor="@color/textLight"
tools:ignore="HardcodedText" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
......@@ -209,4 +209,12 @@
<string name="answers">Antworten</string>
<string name="your_swipes">Deine Swipes</string>
<string name="pref_language_label_sys">Systemsprache</string>
<string name="your_username_or_email">Benutzername oder Email-Adresse</string>
<string name="no_problem_just_enter_your_username_or_your_user_email_address_below_and_we_will_send_you_an_email_it_will_contain_all_the_instructions_for_resetting_your_password">Kein Problem. Gib einfach deinen Benutzernamen oder deine Email-Adresse in das Textfeld und wir werden dir eine Email senden. Diese wird alle weiteren Instruktionen beinhalten um dein Passwort zurückzusetzen.</string>
<string name="sending_email">Email wird versendet...</string>
<string name="reset_your_password">Passwort zurücksetzen</string>
<string name="email_sent">Wir haben dir eine Email mit weiteren Instruktionen gesendet.</string>
<string name="failed_to_send_email">Email konnte nicht gesendet werden aufgrund von %1$s</string>
<string name="no_such_user">Leider konnten wir diesen Nutzer nicht finden. Stelle bitte sicher, dass du dich nicht vertippt hast.</string>
<string name="forgot_your_password">Passwort vergessen?</string>
</resources>
\ No newline at end of file