Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rc/update currency #345

Merged
merged 17 commits into from
Oct 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,6 @@ buildscript {
}
}

apollo {
customTypeMapping['Email'] = "java.lang.String"
}

afterEvaluate {
buildFabricPropertiesIfNeeded()
}
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/graphql/userprivacy.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ query UserPrivacy {
me {
name
email
chosenCurrency
}
}

Expand All @@ -14,6 +15,14 @@ mutation UpdateUserEmail($email: Email!, $current_password: String!) {
}
}

mutation UpdateUserCurrency($chosenCurrency: CurrencyCode!) {
updateUserProfile(input: {chosenCurrency: $chosenCurrency}) {
user {
chosenCurrency
}
}
}

mutation UpdateUserPassword($current_password: String!, $password: String!, $password_confirmation: String!) {
updateUserAccount(input: {current_password: $current_password, password: $password, password_confirmation: $password_confirmation}) {
user {
Expand Down
3 changes: 0 additions & 3 deletions app/src/main/java/com/kickstarter/ApplicationModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
import com.kickstarter.libs.KoalaTrackingClient;
import com.kickstarter.libs.Logout;
import com.kickstarter.libs.PushNotifications;
import com.kickstarter.libs.graphql.EmailAdapter;
import com.kickstarter.libs.preferences.BooleanPreference;
import com.kickstarter.libs.preferences.BooleanPreferenceType;
import com.kickstarter.libs.preferences.IntPreference;
Expand Down Expand Up @@ -89,7 +88,6 @@
import retrofit2.converter.gson.GsonConverterFactory;
import rx.Scheduler;
import rx.schedulers.Schedulers;
import type.CustomType;

@Module
public final class ApplicationModule {
Expand Down Expand Up @@ -175,7 +173,6 @@ static ApolloClient provideApolloClient(final @NonNull Build build, final @NonNu

return ApolloClient.builder()
.serverUrl(webEndpoint + "/graph")
.addCustomTypeAdapter(CustomType.EMAIL, new EmailAdapter())
.okHttpClient(okHttpClient)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package com.kickstarter.mock.services

import UpdateUserCurrencyMutation
import UpdateUserEmailMutation
import UpdateUserPasswordMutation
import UserPrivacyQuery
import com.kickstarter.services.ApolloClientType
import rx.Observable
import type.CurrencyCode

open class MockApolloClient : ApolloClientType {
override fun updateUserCurrencyPreference(currency: CurrencyCode): Observable<UpdateUserCurrencyMutation.Data> {
return Observable.just(UpdateUserCurrencyMutation.Data(UpdateUserCurrencyMutation.UpdateUserProfile("",
UpdateUserCurrencyMutation.User("", "USD"))))
}

override fun updateUserPassword(currentPassword: String, newPassword: String, confirmPassword: String): Observable<UpdateUserPasswordMutation.Data> {
return Observable.just(UpdateUserPasswordMutation.Data(UpdateUserPasswordMutation.UpdateUserAccount("",
UpdateUserPasswordMutation.User("", "some@email.com"))))
Expand All @@ -18,6 +25,8 @@ open class MockApolloClient : ApolloClientType {
}

override fun userPrivacy(): Observable<UserPrivacyQuery.Data> {
return Observable.just(UserPrivacyQuery.Data(UserPrivacyQuery.Me("", "Some Name", "some@email.com")))
return Observable.just(UserPrivacyQuery.Data(UserPrivacyQuery.Me("", "Some Name",
"some@email.com", "USD")))
}
}

Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.kickstarter.services

import UpdateUserCurrencyMutation
import UpdateUserEmailMutation
import UpdateUserPasswordMutation
import UserPrivacyQuery
import rx.Observable
import type.CurrencyCode

interface ApolloClientType {
fun updateUserCurrencyPreference(currency: CurrencyCode): Observable<UpdateUserCurrencyMutation.Data>

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

fun updateUserPassword(currentPassword: String, newPassword: String, confirmPassword: String): Observable<UpdateUserPasswordMutation.Data>
Expand Down
25 changes: 25 additions & 0 deletions app/src/main/java/com/kickstarter/services/KSApolloClient.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.kickstarter.services

import UpdateUserCurrencyMutation
import UpdateUserEmailMutation
import UpdateUserPasswordMutation
import UserPrivacyQuery
Expand All @@ -9,8 +10,32 @@ import com.apollographql.apollo.api.Response
import com.apollographql.apollo.exception.ApolloException
import rx.Observable
import rx.subjects.PublishSubject
import type.CurrencyCode

class KSApolloClient(val service: ApolloClient) : ApolloClientType {
override fun updateUserCurrencyPreference(currency: CurrencyCode): Observable<UpdateUserCurrencyMutation.Data> {
return Observable.defer {
val ps = PublishSubject.create<UpdateUserCurrencyMutation.Data>()
service.mutate(UpdateUserCurrencyMutation.builder()
.chosenCurrency(currency)
.build())
.enqueue(object : ApolloCall.Callback<UpdateUserCurrencyMutation.Data>() {
override fun onFailure(exception: ApolloException) {
ps.onError(exception)
}

override fun onResponse(response: Response<UpdateUserCurrencyMutation.Data>) {
if (response.hasErrors()) {
ps.onError(Exception(response.errors().first().message()))
}
ps.onNext(response.data())
ps.onCompleted()
}
})
return@defer ps
}
}

override fun updateUserEmail(email: String, currentPassword: String): Observable<UpdateUserEmailMutation.Data> {
return Observable.defer {
val ps = PublishSubject.create<UpdateUserEmailMutation.Data>()
Expand Down
113 changes: 103 additions & 10 deletions app/src/main/java/com/kickstarter/ui/activities/AccountActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,126 @@ package com.kickstarter.ui.activities

import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.support.v7.app.AlertDialog
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import com.kickstarter.R
import com.kickstarter.extensions.showErrorSnackbar
import com.kickstarter.extensions.showSuccessSnackbar
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.viewmodels.AccountViewModel
import kotlinx.android.synthetic.main.account_toolbar.*
import kotlinx.android.synthetic.main.activity_account.*
import rx.android.schedulers.AndroidSchedulers
import type.CurrencyCode

class AccountActivity : AppCompatActivity() {
@RequiresActivityViewModel(AccountViewModel.ViewModel::class)
class AccountActivity : BaseActivity<AccountViewModel.ViewModel>() {

private var currentCurrencySelection: CurrencyCode? = null
private var newCurrencySelection: CurrencyCode? = null
private var showCurrencyChangeDialog: AlertDialog? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_account)

setUpSpinner()

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

this.viewModel.outputs.error()
.compose(bindToLifecycle())
.compose(Transformers.observeForUI())
.subscribe { showErrorSnackbar(account_toolbar, it) }

this.viewModel.outputs.progressBarIsVisible()
.compose(bindToLifecycle())
.compose(Transformers.observeForUI())
.subscribe { ViewUtils.setGone(progress_bar, !it) }

this.viewModel.outputs.success()
.compose(bindToLifecycle())
.compose(Transformers.observeForUI())
.subscribe { showSuccessSnackbar(account_container, R.string.Got_it_your_changes_have_been_saved) }

change_email_row.setOnClickListener { startActivity(Intent(this, ChangeEmailActivity::class.java)) }
change_password_row.setOnClickListener { startActivity(Intent(this, ChangePasswordActivity::class.java)) }
privacy_row.setOnClickListener { startActivity(Intent(this, PrivacyActivity::class.java)) }
}

private fun setUpSpinner() {
val currencies = listOf(getString(R.string.Currency_AUD), getString(R.string.Currency_CAD),
getString(R.string.Currency_CHF), getString(R.string.Currency_DKK), getString(R.string.Currency_EUR),
getString(R.string.Currency_GBP), getString(R.string.Currency_HKD), getString(R.string.Currency_JPY),
getString(R.string.Currency_MXN), getString(R.string.Currency_NOK), getString(R.string.Currency_NZD),
getString(R.string.Currency_SEK), getString(R.string.Currency_SGD), getString(R.string.Currency_USD))
private fun getListOfCurrencies(): List<String> {
val strings = arrayListOf<String>()
for (currency in CurrencyCode.values()) {
strings.add(getStringForCurrencyCode(currency))
}
return strings.filter { it != CurrencyCode.`$UNKNOWN`.rawValue() }
}

val arrayAdapter = ArrayAdapter<String>(this, R.layout.item_spinner, currencies)
private fun getStringForCurrencyCode(currency: CurrencyCode): String {
return when (currency) {
CurrencyCode.AUD -> getString(R.string.Currency_AUD)
CurrencyCode.CAD -> getString(R.string.Currency_CAD)
CurrencyCode.CHF -> getString(R.string.Currency_CHF)
CurrencyCode.DKK -> getString(R.string.Currency_DKK)
CurrencyCode.EUR -> getString(R.string.Currency_EUR)
CurrencyCode.GBP -> getString(R.string.Currency_GBP)
CurrencyCode.HKD -> getString(R.string.Currency_HKD)
CurrencyCode.JPY -> getString(R.string.Currency_JPY)
CurrencyCode.MXN -> getString(R.string.Currency_MXN)
CurrencyCode.NOK -> getString(R.string.Currency_NOK)
CurrencyCode.NZD -> getString(R.string.Currency_NZD)
CurrencyCode.SEK -> getString(R.string.Currency_SEK)
CurrencyCode.SGD -> getString(R.string.Currency_SGD)
CurrencyCode.USD -> getString(R.string.Currency_USD)
else -> CurrencyCode.`$UNKNOWN`.rawValue()
}
}

private fun lazyFollowingOptOutConfirmationDialog(): AlertDialog {
if (this.showCurrencyChangeDialog == null) {
this.showCurrencyChangeDialog = AlertDialog.Builder(this)
.setCancelable(false)
.setTitle(getString(R.string.Change_currency))
.setNegativeButton(R.string.Cancel) { _, _ ->
setSpinnerSelection(currentCurrencySelection!!.rawValue())
}
.setPositiveButton(R.string.Yes_change_currency, { _, _ ->
this.viewModel.inputs.onSelectedCurrency(newCurrencySelection!!)
setSpinnerSelection(newCurrencySelection!!.rawValue())
})
.create()
}
return this.showCurrencyChangeDialog!!
}

private fun setSpinnerSelection(currencyCode: String) {
currentCurrencySelection = CurrencyCode.safeValueOf(currencyCode)
currency_spinner.setSelection(currentCurrencySelection!!.ordinal)
}

private fun setUpSpinner() {
val arrayAdapter = ArrayAdapter<String>(this, R.layout.item_spinner, getListOfCurrencies())
arrayAdapter.setDropDownViewResource(R.layout.item_spinner_dropdown)
currency_spinner.adapter = arrayAdapter

currency_spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {}

override fun onItemSelected(parent: AdapterView<*>?, view: View?, postion: Int, id: Long) {
if (currentCurrencySelection != null && currentCurrencySelection!!.ordinal != postion) {
newCurrencySelection = CurrencyCode.values()[postion]
lazyFollowingOptOutConfirmationDialog().show()
}
}
}
}
}
107 changes: 107 additions & 0 deletions app/src/main/java/com/kickstarter/viewmodels/AccountViewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.kickstarter.viewmodels

import UpdateUserCurrencyMutation
import android.support.annotation.NonNull
import com.kickstarter.libs.ActivityViewModel
import com.kickstarter.libs.Environment
import com.kickstarter.libs.rx.transformers.Transformers
import com.kickstarter.libs.rx.transformers.Transformers.combineLatestPair
import com.kickstarter.libs.rx.transformers.Transformers.values
import com.kickstarter.libs.utils.ObjectUtils
import com.kickstarter.services.ApolloClientType
import com.kickstarter.ui.activities.AccountActivity
import rx.Observable
import rx.subjects.BehaviorSubject
import rx.subjects.PublishSubject
import type.CurrencyCode

interface AccountViewModel {

interface Inputs {
/** Notifies when the spinner has been selected. */
fun onSelectedCurrency(currencyCode: CurrencyCode)
}

interface Outputs {
/** Emits the current user's chosen Currency. */
fun chosenCurrency(): Observable<String>

/** Emits whenever there is an error updating the user's currency. */
fun error(): Observable<String>

/** Emits when the progress bar should be visible. */
fun progressBarIsVisible(): Observable<Boolean>

/** Emits when the currency update was successful. */
fun success(): Observable<String>
}

class ViewModel(@NonNull val environment: Environment) : ActivityViewModel<AccountActivity>(environment), Inputs, Outputs {

val inputs: Inputs = this
val outputs: Outputs = this

private val onSelectedCurrency = PublishSubject.create<CurrencyCode>()

private val chosenCurrency = BehaviorSubject.create<String>()
private val progressBarIsVisible = BehaviorSubject.create<Boolean>()
private val success = BehaviorSubject.create<String>()

private val error = BehaviorSubject.create<String>()

private val apolloClient: ApolloClientType = environment.apolloClient()

init {

this.apolloClient.userPrivacy()
.map { it.me()?.chosenCurrency() }
.map { ObjectUtils.coalesce(it, CurrencyCode.USD.rawValue()) }
.compose(bindToLifecycle())
.subscribe { this.chosenCurrency.onNext(it) }

val updateCurrencyNotification = this.onSelectedCurrency
.compose(combineLatestPair<CurrencyCode, String>(this.chosenCurrency))
.filter { it.first.rawValue() != it.second }
.map<CurrencyCode> { it.first }
.switchMap { updateUserCurrency(it).materialize() }
.compose(bindToLifecycle())
.share()

updateCurrencyNotification
.compose(values())
.map { it.updateUserProfile()?.user()?.chosenCurrency() }
.subscribe {
this.chosenCurrency.onNext(it)
this.success.onNext(it)
}

updateCurrencyNotification
.compose(Transformers.errors())
.subscribe { this.error.onNext(it.localizedMessage) }

}

override fun onSelectedCurrency(currencyCode: CurrencyCode) {
this.onSelectedCurrency.onNext(currencyCode)
}

override fun chosenCurrency(): BehaviorSubject<String> = this.chosenCurrency

override fun error(): Observable<String> = this.error

override fun progressBarIsVisible(): Observable<Boolean> {
return this.progressBarIsVisible
}

override fun success(): BehaviorSubject<String> {
return this.success
}

private fun updateUserCurrency(currencyCode: CurrencyCode): Observable<UpdateUserCurrencyMutation.Data> {
return this.apolloClient.updateUserCurrencyPreference(currencyCode)
.doOnSubscribe { this.progressBarIsVisible.onNext(true) }
.doAfterTerminate { this.progressBarIsVisible.onNext(false) }

}
}
}
Loading