Skip to content

Commit

Permalink
WEB-665 [Android] When Facebook-only users log in to Kickstarter, req…
Browse files Browse the repository at this point in the history
…uire them to set a password (#1680)

* Create set password

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Refactor two factor auth for facebook

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Update strings

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Create set password  intent

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Converting files to Kotlin with safe renaming.

* Show set password for DiscoveryFragment

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Update intent to set email

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Update updateUserAccount with hasPassword

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Add test case to DiscoveryFragmentViewModel

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Update startSetPasswordActivity with email

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Update dialog strings

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Only Facebook errors

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Show dialog with facebook error only

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* setUserPassword For facebook user

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Add error mask

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Set password refactor

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Fix import

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Update test

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Change mask for email

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Removed unused code

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Add unit test

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Add test

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Update graphql call

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Fix missing &&

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Fix missing &&

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Update UI

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Update strings with latest

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

* Update strings with latest

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>

Signed-off-by: hadia <hadiamohamed.iti@gmail.com>
Co-authored-by: Isabel Martin <arkariang@gmail.com>
  • Loading branch information
hadia and Arkariang committed Sep 6, 2022
1 parent f4beabd commit c76dc6e
Show file tree
Hide file tree
Showing 28 changed files with 1,498 additions and 723 deletions.
4 changes: 4 additions & 0 deletions app/src/main/AndroidManifest.xml
Expand Up @@ -110,6 +110,10 @@
android:name=".ui.activities.ResetPasswordActivity"
android:parentActivityName=".ui.activities.LoginActivity"
android:theme="@style/Login" />
<activity
android:name=".ui.activities.SetPasswordActivity"
android:parentActivityName=".ui.activities.LoginActivity"
android:theme="@style/Login" />
<activity
android:name=".ui.activities.HelpActivity"
android:theme="@style/HelpActivity" />
Expand Down
1 change: 1 addition & 0 deletions app/src/main/graphql/userprivacy.graphql
Expand Up @@ -41,6 +41,7 @@ mutation UpdateUserPassword($currentPassword: String!, $password: String!, $pass
user {
email
isEmailVerified
hasPassword
}
}
}
Expand Down
Expand Up @@ -13,6 +13,7 @@ import com.kickstarter.ui.activities.LoginActivity
import com.kickstarter.ui.activities.ProjectPageActivity
import com.kickstarter.ui.activities.ProjectUpdatesActivity
import com.kickstarter.ui.activities.ResetPasswordActivity
import com.kickstarter.ui.activities.SetPasswordActivity
import com.kickstarter.ui.activities.UpdateActivity
import com.kickstarter.ui.activities.VideoActivity
import com.kickstarter.ui.data.LoginReason
Expand Down Expand Up @@ -168,3 +169,18 @@ fun Intent.getLoginActivityIntent(
}
}
}

/**
* Return a Intent ready to launch the SetPasswordActivity with extras:
* @param context
*/
fun Intent.getSetPasswordActivity(
context: Context,
email: String?
): Intent {
return this.setClass(context, SetPasswordActivity::class.java).apply {
email?.let {
this.putExtra(IntentKey.EMAIL, it)
}
}
}
Expand Up @@ -2,6 +2,7 @@
package com.kickstarter.libs.utils.extensions

import android.util.Patterns
import com.kickstarter.R
import org.jsoup.Jsoup
import java.util.Locale
import java.util.regex.Matcher
Expand Down Expand Up @@ -105,3 +106,27 @@ fun String.isGif(): Boolean {
val gifPattern = "(?:\\/\\/.*\\.(?:gif))"
return gifPattern.toRegex().find(this) != null
}

/**
* Mask Email
*/
fun String.maskEmail(): String {
val regex = """(.{1,4}@)""".toRegex()
return this.replace(regex, "****@")
}

/**
* validate password isNotEmptyAndAtLeast6Chars
*/
fun String.isNotEmptyAndAtLeast6Chars() = this.isNotEmpty() && this.length >= MINIMUM_PASSWORD_LENGTH

/**
* new Password Validation Warnings message
*/
fun String.newPasswordValidationWarnings(confirmPassword: String): Int? {
return if (this.isNotEmpty() && this.length in 1 until MINIMUM_PASSWORD_LENGTH)
R.string.Password_min_length_message
else if (confirmPassword.isNotEmpty() && confirmPassword != this)
R.string.Passwords_matching_message
else null
}
Expand Up @@ -35,6 +35,11 @@ object UserFactory {
return user().toBuilder().social(true).build()
}

@JvmStatic
fun userNeedPassword(): User {
return user().toBuilder().needsPassword(true).build()
}

@JvmStatic
fun collaborator(): User {
return user()
Expand Down
Expand Up @@ -251,7 +251,7 @@ open class MockApolloClient : ApolloClientType {
UpdateUserPasswordMutation.Data(
UpdateUserPasswordMutation.UpdateUserAccount(
"",
UpdateUserPasswordMutation.User("", "some@email.com", true)
UpdateUserPasswordMutation.User("", "some@email.com", true, true)
)
)
)
Expand Down
15 changes: 12 additions & 3 deletions app/src/main/java/com/kickstarter/models/User.kt
Expand Up @@ -49,6 +49,7 @@ class User private constructor(
private val promoNewsletter: Boolean,
private val publishingNewsletter: Boolean,
private val showPublicProfile: Boolean,
private val needsPassword: Boolean?,
private val social: Boolean,
private val starredProjectsCount: Int,
private val unreadMessagesCount: Int,
Expand Down Expand Up @@ -98,6 +99,7 @@ class User private constructor(
fun promoNewsletter() = this.promoNewsletter
fun publishingNewsletter() = this.publishingNewsletter
fun showPublicProfile() = this.showPublicProfile
fun needsPassword() = this.needsPassword
fun social() = this.social
fun starredProjectsCount() = this.starredProjectsCount
fun unreadMessagesCount() = this.unreadMessagesCount
Expand Down Expand Up @@ -148,6 +150,7 @@ class User private constructor(
private var promoNewsletter: Boolean = false,
private var publishingNewsletter: Boolean = false,
private var showPublicProfile: Boolean = false,
private var needsPassword: Boolean? = false,
private var social: Boolean = false,
private var starredProjectsCount: Int = 0,
private var unreadMessagesCount: Int = 0,
Expand Down Expand Up @@ -202,6 +205,8 @@ class User private constructor(
fun unreadMessagesCount(unreadMessagesCount: Int?) = apply { this.unreadMessagesCount = unreadMessagesCount ?: 0 }
fun unseenActivityCount(unseenActivityCount: Int?) = apply { this.unseenActivityCount = unseenActivityCount ?: 0 }
fun weeklyNewsletter(weeklyNewsletter: Boolean?) = apply { this.weeklyNewsletter = weeklyNewsletter ?: false }
fun needsPassword(needsPassword: Boolean?) = apply { this.needsPassword = needsPassword ?: false }

fun build() = User(
alumniNewsletter = alumniNewsletter,
artsCultureNewsletter = artsCultureNewsletter,
Expand Down Expand Up @@ -249,7 +254,9 @@ class User private constructor(
starredProjectsCount = starredProjectsCount,
unreadMessagesCount = unreadMessagesCount,
unseenActivityCount = unseenActivityCount,
weeklyNewsletter = weeklyNewsletter
weeklyNewsletter = weeklyNewsletter,
needsPassword = needsPassword

)
}

Expand Down Expand Up @@ -309,7 +316,8 @@ class User private constructor(
starredProjectsCount = starredProjectsCount,
unreadMessagesCount = unreadMessagesCount,
unseenActivityCount = unseenActivityCount,
weeklyNewsletter = weeklyNewsletter
weeklyNewsletter = weeklyNewsletter,
needsPassword = needsPassword
)

enum class EmailFrequency(private val stringResId: Int) {
Expand Down Expand Up @@ -378,7 +386,8 @@ class User private constructor(
starredProjectsCount() == obj.starredProjectsCount() &&
unreadMessagesCount() == obj.unreadMessagesCount() &&
unseenActivityCount() == obj.unseenActivityCount() &&
weeklyNewsletter() == obj.weeklyNewsletter()
weeklyNewsletter() == obj.weeklyNewsletter() &&
needsPassword() == obj.needsPassword()
}
return equals
}
Expand Down
Expand Up @@ -94,7 +94,7 @@ interface ApolloClientType {

fun updateUserEmail(email: String, currentPassword: String): Observable<UpdateUserEmailMutation.Data>

fun updateUserPassword(currentPassword: String, newPassword: String, confirmPassword: String): Observable<UpdateUserPasswordMutation.Data>
fun updateUserPassword(currentPassword: String = "", newPassword: String, confirmPassword: String): Observable<UpdateUserPasswordMutation.Data>

fun userPrivacy(): Observable<UserPrivacyQuery.Data>
}
Expand Down
Expand Up @@ -90,9 +90,9 @@ class LoginToutActivity : BaseActivity<LoginToutViewModel.ViewModel>() {
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
this.showAlertDialog(
message = getString(R.string.FPO_reset_your_password_dialog_msg),
message = getString(R.string.FPO_we_can_no_longer_log_you_in_through_Facebook),
positiveActionTitle = getString(R.string.FPO_Set_new_password),
negativeActionTitle = getString(R.string.FPO_login_with_kickstarter),
negativeActionTitle = getString(R.string.accessibility_discovery_buttons_log_in),
isCancelable = false,
positiveAction = {
viewModel.inputs.onResetPasswordFacebookErrorDialogClicked()
Expand Down
@@ -0,0 +1,95 @@
package com.kickstarter.ui.activities

import android.os.Bundle
import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.kickstarter.R
import com.kickstarter.databinding.ActivitySetPasswordBinding
import com.kickstarter.libs.BaseActivity
import com.kickstarter.libs.qualifiers.RequiresActivityViewModel
import com.kickstarter.libs.rx.transformers.Transformers
import com.kickstarter.libs.utils.ViewUtils
import com.kickstarter.ui.extensions.onChange
import com.kickstarter.viewmodels.SetPasswordViewModel
import rx.android.schedulers.AndroidSchedulers

@RequiresActivityViewModel(SetPasswordViewModel.ViewModel::class)
class SetPasswordActivity : BaseActivity<SetPasswordViewModel.ViewModel>() {
private lateinit var binding: ActivitySetPasswordBinding
private var errorTitleString = R.string.general_error_oops

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySetPasswordBinding.inflate(layoutInflater)

setContentView(binding.root)
setSupportActionBar(binding.resetPasswordToolbar.loginToolbar)
binding.resetPasswordToolbar.loginToolbar.setTitle(getString(R.string.FPO_Set_your_password))
binding.resetPasswordToolbar.backButton.isGone = true
binding.newPassword.onChange { this.viewModel.inputs.newPassword(it) }
binding.confirmPassword.onChange { this.viewModel.inputs.confirmPassword(it) }

binding.savePasswordButton.setOnClickListener {
viewModel.inputs.changePasswordClicked()
}

this.viewModel.outputs.progressBarIsVisible()
.compose(bindToLifecycle())
.compose(Transformers.observeForUI())
.subscribe {
binding.progressBar.isGone = !it
}

this.viewModel.outputs.setUserEmail()
.compose(bindToLifecycle())
.compose(Transformers.observeForUI())
.subscribe {
binding.setPasswordHint.text = getString(R.string.FPO_We_will_be_discontinuing_the_ability_to_log_in_via_Facebook, it)
}

this.viewModel.outputs.passwordWarning()
.compose(bindToLifecycle())
.compose(Transformers.observeForUI())
.subscribe {
binding.warning.text = when {
it != null -> {
getString(it)
}
else -> null
}
binding.warning.isVisible = (it != null)
}

this.viewModel.outputs.error()
.compose(bindToLifecycle())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { ViewUtils.showDialog(this, getString(this.errorTitleString), it) }

this.viewModel.outputs.isFormSubmitting()
.compose(bindToLifecycle())
.compose(Transformers.observeForUI())
.subscribe { this.setFormDisabled(it) }

this.viewModel.outputs.saveButtonIsEnabled()
.compose(bindToLifecycle())
.compose(Transformers.observeForUI())
.subscribe { this.setFormEnabled(it) }

this.viewModel.outputs.success()
.compose(bindToLifecycle())
.compose(Transformers.observeForUI())
.subscribe { finish() }
}

private fun setFormEnabled(isEnabled: Boolean) {
binding.savePasswordButton.isEnabled = isEnabled
}

private fun setFormDisabled(isDisabled: Boolean) {
setFormEnabled(!isDisabled)
}

override fun back() {
// Disable back action Gesture
}
}
Expand Up @@ -9,7 +9,7 @@ enum class ResetPasswordScreenState(
) {
ResetPassword(
title = R.string.FPO_reset_your_password,
hint = R.string.FPO_reset_your_password_hint
hint = R.string.FPO_we_re_simplifying_our_login_process_To_log_in
),
ForgetPassword(
title = R.string.forgot_password_title,
Expand Down
Expand Up @@ -25,6 +25,7 @@ import com.kickstarter.libs.utils.AnimationUtils.crossFadeAndReverse
import com.kickstarter.libs.utils.TransitionUtils
import com.kickstarter.libs.utils.ViewUtils
import com.kickstarter.libs.utils.extensions.getProjectIntent
import com.kickstarter.libs.utils.extensions.getSetPasswordActivity
import com.kickstarter.models.Activity
import com.kickstarter.models.Category
import com.kickstarter.models.Project
Expand Down Expand Up @@ -128,6 +129,11 @@ class DiscoveryFragment : BaseFragment<DiscoveryFragmentViewModel.ViewModel>() {
.observeOn(AndroidSchedulers.mainThread())
.subscribe { startActivityFeedActivity() }

this.viewModel.outputs.startSetPasswordActivity()
.compose(bindToLifecycle())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { startSetPasswordActivity(it) }

this.viewModel.outputs.startEditorialActivity()
.compose(bindToLifecycle())
.observeOn(AndroidSchedulers.mainThread())
Expand Down Expand Up @@ -223,6 +229,12 @@ class DiscoveryFragment : BaseFragment<DiscoveryFragmentViewModel.ViewModel>() {
startActivity(Intent(activity, ActivityFeedActivity::class.java))
}

private fun startSetPasswordActivity(email: String) {
val intent = Intent().getSetPasswordActivity(requireContext(), email)
startActivityForResult(intent, ActivityRequestCodes.LOGIN_FLOW)
TransitionUtils.transition(requireContext(), TransitionUtils.fadeIn())
}

private fun showStarToast() {
ViewUtils.showToastFromTop(requireContext(), getString(this.projectStarConfirmationString), 0, resources.getDimensionPixelSize(R.dimen.grid_8))
}
Expand Down
Expand Up @@ -2,13 +2,13 @@ package com.kickstarter.viewmodels

import UpdateUserPasswordMutation
import androidx.annotation.NonNull
import com.kickstarter.R
import com.kickstarter.libs.ActivityViewModel
import com.kickstarter.libs.Environment
import com.kickstarter.libs.rx.transformers.Transformers.errors
import com.kickstarter.libs.rx.transformers.Transformers.takeWhen
import com.kickstarter.libs.rx.transformers.Transformers.values
import com.kickstarter.libs.utils.extensions.MINIMUM_PASSWORD_LENGTH
import com.kickstarter.libs.utils.extensions.isNotEmptyAndAtLeast6Chars
import com.kickstarter.libs.utils.extensions.newPasswordValidationWarnings
import com.kickstarter.ui.activities.ChangePasswordActivity
import rx.Observable
import rx.subjects.BehaviorSubject
Expand Down Expand Up @@ -150,21 +150,14 @@ interface ChangePasswordViewModel {

data class ChangePassword(val currentPassword: String, val newPassword: String, val confirmPassword: String) {
fun isValid(): Boolean {
return isNotEmptyAndAtLeast6Chars(this.currentPassword) &&
isNotEmptyAndAtLeast6Chars(this.newPassword) &&
isNotEmptyAndAtLeast6Chars(this.confirmPassword) &&
return this.currentPassword.isNotEmptyAndAtLeast6Chars() &&
this.newPassword.isNotEmptyAndAtLeast6Chars() &&
this.confirmPassword.isNotEmptyAndAtLeast6Chars() &&
this.confirmPassword == this.newPassword
}

fun warning(): Int? {
return if (newPassword.isNotEmpty() && newPassword.length in 1 until MINIMUM_PASSWORD_LENGTH)
R.string.Password_min_length_message
else if (confirmPassword.isNotEmpty() && confirmPassword != newPassword)
R.string.Passwords_matching_message
else null
}

private fun isNotEmptyAndAtLeast6Chars(password: String) = !password.isEmpty() && password.length >= MINIMUM_PASSWORD_LENGTH
fun warning(): Int? =
newPassword.newPasswordValidationWarnings(confirmPassword)
}
}
}

0 comments on commit c76dc6e

Please sign in to comment.