diff --git a/app/src/main/java/com/kickstarter/services/ApiClient.java b/app/src/main/java/com/kickstarter/services/ApiClient.java index 40d490b5bb..96e42fc898 100644 --- a/app/src/main/java/com/kickstarter/services/ApiClient.java +++ b/app/src/main/java/com/kickstarter/services/ApiClient.java @@ -488,6 +488,7 @@ public ApiClient(final @NonNull ApiService service, final @NonNull Gson gson) { .gamesNewsletter(isTrue(user.gamesNewsletter()) ? 1 : 0) .happeningNewsletter(isTrue(user.happeningNewsletter()) ? 1 : 0) .promoNewsletter(isTrue(user.promoNewsletter()) ? 1 : 0) + .social(isTrue(user.social()) ? 1 : 0) .weeklyNewsletter(isTrue(user.weeklyNewsletter()) ? 1 : 0) .build()) .lift(apiErrorOperator()) diff --git a/app/src/main/java/com/kickstarter/services/apirequests/SettingsBody.java b/app/src/main/java/com/kickstarter/services/apirequests/SettingsBody.java index a6a76df6a8..610dfa073e 100644 --- a/app/src/main/java/com/kickstarter/services/apirequests/SettingsBody.java +++ b/app/src/main/java/com/kickstarter/services/apirequests/SettingsBody.java @@ -16,6 +16,7 @@ public abstract class SettingsBody { public abstract boolean notifyOfFriendActivity(); public abstract boolean notifyOfMessages(); public abstract boolean notifyOfUpdates(); + public abstract int social(); public abstract int gamesNewsletter(); public abstract int happeningNewsletter(); public abstract int promoNewsletter(); @@ -32,6 +33,7 @@ public abstract static class Builder { public abstract Builder notifyOfFriendActivity(boolean __); public abstract Builder notifyOfMessages(boolean __); public abstract Builder notifyOfUpdates(boolean __); + public abstract Builder social(int __); public abstract Builder gamesNewsletter(int __); public abstract Builder happeningNewsletter(int __); public abstract Builder promoNewsletter(int __); diff --git a/app/src/main/java/com/kickstarter/ui/activities/SettingsActivity.java b/app/src/main/java/com/kickstarter/ui/activities/SettingsActivity.java index 9ac96087ce..4434363631 100644 --- a/app/src/main/java/com/kickstarter/ui/activities/SettingsActivity.java +++ b/app/src/main/java/com/kickstarter/ui/activities/SettingsActivity.java @@ -46,6 +46,7 @@ @RequiresActivityViewModel(SettingsViewModel.ViewModel.class) public final class SettingsActivity extends BaseActivity { + protected @Bind(R.id.following_switch) SwitchCompat followingSwitch; protected @Bind(R.id.games_switch) SwitchCompat gamesNewsletterSwitch; protected @Bind(R.id.happening_now_switch) SwitchCompat happeningNewsletterSwitch; protected @Bind(R.id.friend_activity_mail_icon) ImageButton friendActivityMailImageButton; @@ -65,6 +66,9 @@ public final class SettingsActivity extends BaseActivity ViewUtils.showToast(this, this.unableToSaveString)); + RxView.clicks(this.followingSwitch) + .compose(bindToLifecycle()) + .subscribe(__ -> this.viewModel.inputs.optIntoFollowing(this.followingSwitch.isChecked())); + + this.viewModel.outputs.hideConfirmFollowingOptOutPrompt() + .compose(bindToLifecycle()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(__ -> SwitchCompatUtils.setCheckedWithoutAnimation(this.followingSwitch, true)); + + this.viewModel.outputs.showConfirmFollowingOptOutPrompt() + .compose(bindToLifecycle()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(__ -> lazyFollowingOptOutConfirmationDialog().show()); + + this.viewModel.outputs.showFollowingInfo() + .compose(bindToLifecycle()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(__ -> lazyFollowingInfoDialog().show()); + RxView.clicks(this.gamesNewsletterSwitch) .compose(bindToLifecycle()) .subscribe(__ -> this.viewModel.inputs.sendGamesNewsletter(this.gamesNewsletterSwitch.isChecked())); @@ -186,6 +212,11 @@ public void cookiePolicyClick() { startHelpActivity(HelpActivity.CookiePolicy.class); } + @OnClick(R.id.following_info) + public void followingInfoClick() { + this.viewModel.inputs.followingInfoClicked(); + } + @OnClick(R.id.help_center) public void helpCenterClick() { final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(Secrets.HelpCenter.ENDPOINT)); @@ -213,6 +244,11 @@ public void privacyPolicyClick() { startHelpActivity(HelpActivity.Privacy.class); } + @OnClick(R.id.recommendations_info) + public void recommendationsInfoClick() { + this.viewModel.inputs.recommendationsInfoClicked(); + } + public void startHelpActivity(final @NonNull Class helpClass) { final Intent intent = new Intent(this, helpClass); startActivityWithTransition(intent, R.anim.slide_in_right, R.anim.fade_out_slide_out_left); @@ -258,11 +294,6 @@ public void toggleNotifyMobileOfUpdates() { this.viewModel.inputs.notifyMobileOfUpdates(!this.notifyMobileOfUpdates); } - @OnClick(R.id.recommendations_info) - public void recommendationsInfoClick() { - this.viewModel.inputs.recommendationsInfoClicked(); - } - @OnClick(R.id.terms_of_use) public void termsOfUseClick() { startHelpActivity(HelpActivity.Terms.class); @@ -330,6 +361,7 @@ private void displayPreferences(final @NonNull User user) { toggleImageButtonIconColor(this.projectUpdatesMailImageButton, false, this.notifyOfUpdates); toggleTextViewIconColor(this.projectUpdatesPhoneIconTextView, true, this.notifyMobileOfUpdates); + SwitchCompatUtils.setCheckedWithoutAnimation(this.followingSwitch, isTrue(user.social())); SwitchCompatUtils.setCheckedWithoutAnimation(this.gamesNewsletterSwitch, isTrue(user.gamesNewsletter())); SwitchCompatUtils.setCheckedWithoutAnimation(this.recommendationsSwitch, isFalse(user.optedOutOfRecommendations())); SwitchCompatUtils.setCheckedWithoutAnimation(this.happeningNewsletterSwitch, isTrue(user.happeningNewsletter())); @@ -337,6 +369,32 @@ private void displayPreferences(final @NonNull User user) { SwitchCompatUtils.setCheckedWithoutAnimation(this.weeklyNewsletterSwitch, isTrue(user.weeklyNewsletter())); } + private @NonNull AlertDialog lazyFollowingInfoDialog() { + if (this.followingInfoDialog == null) { + final String capitalizedGotIt = this.gotItString.toUpperCase(Locale.getDefault()); + this.followingInfoDialog = new AlertDialog.Builder(this) + .setTitle(this.followingString) + .setMessage(this.followingInfoString) + .setPositiveButton(capitalizedGotIt, (__, ___) -> this.followingInfoDialog.dismiss()) + .setCancelable(true) + .create(); + } + return this.followingInfoDialog; + } + + private @NonNull AlertDialog lazyFollowingOptOutConfirmationDialog() { + if (this.followingConfirmationDialog == null) { + this.followingConfirmationDialog = new AlertDialog.Builder(this) + .setCancelable(false) + .setTitle(getString(R.string.Are_you_sure)) + .setMessage(getString(R.string.If_you_turn_following_off)) + .setNegativeButton(this.cancelString, (__, ___) -> this.viewModel.inputs.optOutOfFollowing(false)) + .setPositiveButton(this.yesTurnOffString, (__, ___) -> this.viewModel.inputs.optOutOfFollowing(true)) + .create(); + } + return this.followingConfirmationDialog; + } + /** * Lazily creates a logout confirmation dialog and stores it in an instance variable. */ @@ -357,7 +415,7 @@ private void displayPreferences(final @NonNull User user) { if (this.recommendationsInfoDialog == null) { final String capitalizedGotIt = this.gotItString.toUpperCase(Locale.getDefault()); this.recommendationsInfoDialog = new AlertDialog.Builder(this) - .setTitle(this.recommendations) + .setTitle(this.recommendationsString) .setMessage(this.recommendationsInfo) .setPositiveButton(capitalizedGotIt, (__, ___) -> this.recommendationsInfoDialog.dismiss()) .setCancelable(true) diff --git a/app/src/main/java/com/kickstarter/viewmodels/SettingsViewModel.java b/app/src/main/java/com/kickstarter/viewmodels/SettingsViewModel.java index 5b09314f71..0227fb7b1d 100644 --- a/app/src/main/java/com/kickstarter/viewmodels/SettingsViewModel.java +++ b/app/src/main/java/com/kickstarter/viewmodels/SettingsViewModel.java @@ -33,6 +33,9 @@ interface Inputs { /** Call when the user clicks on contact email. */ void contactEmailClicked(); + /** Call when the user clicks the Follwing info icon. */ + void followingInfoClicked(); + /** Call when the user taps the logout button. */ void logoutClicked(); @@ -60,6 +63,12 @@ interface Inputs { /** Call when the notify of project updates toggle changes. */ void notifyOfUpdates(boolean checked); + /** Call when the user toggles the Following switch. */ + void optIntoFollowing(boolean checked); + + /** Call when the user confirms or cancels opting out of Following. */ + void optOutOfFollowing(boolean optOut); + /** Call when the user toggles the Recommendations switch. */ void optedOutOfRecommendations(boolean checked); @@ -83,9 +92,18 @@ interface Outputs { /** Emits when its time to log the user out. */ Observable logout(); + /** Emits when Following switch should be turned back on after user cancels opting out. */ + Observable hideConfirmFollowingOptOutPrompt(); + + /** Emits when user should be shown the Following confirmation dialog. */ + Observable showConfirmFollowingOptOutPrompt(); + /** Emits a boolean that determines if the logout confirmation should be displayed. */ Observable showConfirmLogoutPrompt(); + /** Emits when user should be shown the Following info dialog. */ + Observable showFollowingInfo(); + /** Show a dialog to inform the user that their newsletter subscription must be confirmed via email. */ Observable showOptInPrompt(); @@ -161,6 +179,26 @@ public ViewModel(final @NonNull Environment environment) { this.logout.onNext(null); }); + this.optIntoFollowing + .compose(bindToLifecycle()) + .filter(checked -> checked) + .subscribe(__ -> this.userInput.onNext(this.userOutput.getValue().toBuilder().social(true).build())); + + this.optIntoFollowing + .compose(bindToLifecycle()) + .filter(checked -> !checked) + .subscribe(__ -> this.showConfirmFollowingOptOutPrompt.onNext(null)); + + this.optOutOfFollowing + .compose(bindToLifecycle()) + .filter(optOut -> optOut) + .subscribe(__ -> this.userInput.onNext(this.userOutput.getValue().toBuilder().social(false).build())); + + this.optOutOfFollowing + .compose(bindToLifecycle()) + .filter(optOut -> !optOut) + .subscribe(__ -> this.hideConfirmFollowingOptOutPrompt.onNext(null)); + this.koala.trackSettingsView(); } @@ -182,10 +220,15 @@ private void success(final @NonNull User user) { private final PublishSubject contactEmailClicked = PublishSubject.create(); private final PublishSubject optedOutOfRecommendations = PublishSubject.create(); private final PublishSubject> newsletterInput = PublishSubject.create(); + private final PublishSubject optIntoFollowing = PublishSubject.create(); + private final PublishSubject optOutOfFollowing = PublishSubject.create(); private final PublishSubject userInput = PublishSubject.create(); + private final BehaviorSubject hideConfirmFollowingOptOutPrompt = BehaviorSubject.create(); private final BehaviorSubject logout = BehaviorSubject.create(); + private final BehaviorSubject showConfirmFollowingOptOutPrompt = BehaviorSubject.create(); private final BehaviorSubject showConfirmLogoutPrompt = BehaviorSubject.create(); + private final BehaviorSubject showFollowingInfo = BehaviorSubject.create(); private final PublishSubject showOptInPrompt = PublishSubject.create(); private final PublishSubject showRecommendationsInfo = PublishSubject.create(); private final PublishSubject updateSuccess = PublishSubject.create(); @@ -206,6 +249,9 @@ private void success(final @NonNull User user) { @Override public void contactEmailClicked() { this.contactEmailClicked.onNext(null); } + @Override public void followingInfoClicked() { + this.showFollowingInfo.onNext(null); + } @Override public void optedOutOfRecommendations(final boolean checked) { this.userInput.onNext(this.userOutput.getValue().toBuilder().optedOutOfRecommendations(!checked).build()); @@ -241,6 +287,12 @@ public void optedOutOfRecommendations(final boolean checked) { @Override public void notifyOfUpdates(final boolean b) { this.userInput.onNext(this.userOutput.getValue().toBuilder().notifyOfUpdates(b).build()); } + @Override public void optIntoFollowing(final boolean checked) { + this.optIntoFollowing.onNext(checked); + } + @Override public void optOutOfFollowing(final boolean optOut) { + this.optOutOfFollowing.onNext(optOut); + } @Override public void sendGamesNewsletter(final boolean checked) { this.userInput.onNext(this.userOutput.getValue().toBuilder().gamesNewsletter(checked).build()); this.newsletterInput.onNext(new Pair<>(checked, Newsletter.GAMES)); @@ -261,9 +313,18 @@ public void optedOutOfRecommendations(final boolean checked) { @Override public @NonNull Observable logout() { return this.logout; } + @Override public @NonNull Observable hideConfirmFollowingOptOutPrompt() { + return this.hideConfirmFollowingOptOutPrompt; + } @Override public @NonNull Observable showConfirmLogoutPrompt() { return this.showConfirmLogoutPrompt; } + @Override public @NonNull Observable showConfirmFollowingOptOutPrompt() { + return this.showConfirmFollowingOptOutPrompt; + } + @Override public @NonNull Observable showFollowingInfo() { + return this.showFollowingInfo; + } @Override public @NonNull Observable showOptInPrompt() { return this.showOptInPrompt; } diff --git a/app/src/main/res/layout/settings_layout.xml b/app/src/main/res/layout/settings_layout.xml index 999e0aa78e..99bb25a729 100644 --- a/app/src/main/res/layout/settings_layout.xml +++ b/app/src/main/res/layout/settings_layout.xml @@ -415,6 +415,26 @@ style="@style/EndSettingsWidget" /> + + + + + + + + + diff --git a/app/src/test/java/com/kickstarter/viewmodels/SettingsViewModelTest.java b/app/src/test/java/com/kickstarter/viewmodels/SettingsViewModelTest.java index 2d9d4a4988..d35a826408 100644 --- a/app/src/test/java/com/kickstarter/viewmodels/SettingsViewModelTest.java +++ b/app/src/test/java/com/kickstarter/viewmodels/SettingsViewModelTest.java @@ -17,6 +17,8 @@ public final class SettingsViewModelTest extends KSRobolectricTestCase { private SettingsViewModel.ViewModel vm; private final TestSubscriber currentUserTest = new TestSubscriber<>(); + private final TestSubscriber hideConfirmFollowingOptOutPrompt = new TestSubscriber<>(); + private final TestSubscriber showConfirmFollowingOptOutPrompt = new TestSubscriber<>(); private final TestSubscriber showOptInPromptTest = new TestSubscriber<>(); private final TestSubscriber showRecommendationsInfo = new TestSubscriber<>(); @@ -30,10 +32,69 @@ private void setUpEnvironment(final @NonNull User user) { currentUser.observable().subscribe(this.currentUserTest); this.vm = new SettingsViewModel.ViewModel(environment); + this.vm.outputs.hideConfirmFollowingOptOutPrompt().subscribe(this.hideConfirmFollowingOptOutPrompt); + this.vm.outputs.showConfirmFollowingOptOutPrompt().subscribe(this.showConfirmFollowingOptOutPrompt); this.vm.outputs.showRecommendationsInfo().subscribe(this.showRecommendationsInfo); this.vm.outputs.showOptInPrompt().subscribe(this.showOptInPromptTest); } + @Test + public void testSettingsViewModel_optIntoFollowing() { + final User user = UserFactory.user(); + + setUpEnvironment(user); + + this.currentUserTest.assertValues(user); + + this.vm.inputs.optIntoFollowing(true); + this.currentUserTest.assertValues(user, user.toBuilder().social(true).build()); + + this.showConfirmFollowingOptOutPrompt.assertNoValues(); + this.hideConfirmFollowingOptOutPrompt.assertNoValues(); + this.showOptInPromptTest.assertNoValues(); + this.koalaTest.assertValues("Settings View"); + } + + @Test + public void testSettingsViewModel_optIntoFollowing_userCancelOptOut() { + final User user = UserFactory.socialUser(); + + setUpEnvironment(user); + + this.currentUserTest.assertValues(user); + + this.vm.inputs.optIntoFollowing(false); + this.currentUserTest.assertValues(user); + this.showConfirmFollowingOptOutPrompt.assertValueCount(1); + + this.vm.inputs.optOutOfFollowing(false); + this.hideConfirmFollowingOptOutPrompt.assertValueCount(1); + this.currentUserTest.assertValues(user); + + this.showOptInPromptTest.assertNoValues(); + this.koalaTest.assertValues("Settings View"); + } + + @Test + public void testSettingsViewModel_optIntoFollowing_userConfirmOptOut() { + final User user = UserFactory.socialUser(); + + setUpEnvironment(user); + + this.currentUserTest.assertValues(user); + + this.vm.inputs.optIntoFollowing(false); + this.currentUserTest.assertValues(user); + this.showConfirmFollowingOptOutPrompt.assertValueCount(1); + + this.vm.inputs.optOutOfFollowing(true); + this.currentUserTest.assertValues(user, user.toBuilder().social(false).build()); + + this.hideConfirmFollowingOptOutPrompt.assertNoValues(); + this.showOptInPromptTest.assertNoValues(); + this.koalaTest.assertValues("Settings View"); + } + @Test public void testSettingsViewModel_optedOutOfRecommendations() { final User user = UserFactory.noRecommendations();