Created
December 29, 2018 04:18
-
-
Save IlyaGulya/ec294786c6286f2f153170626b35d651 to your computer and use it in GitHub Desktop.
Android code sample
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.app.feature.editprofile.di | |
import com.app.feature.editprofile.presenter.EditProfilePresenter | |
import com.app.feature.editprofile.di.EditProfileScope | |
@EditProfileScope | |
interface EditProfileComponent { | |
val editProfilePresenter: EditProfilePresenter | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.app.feature.editprofile.interactor | |
import android.net.Uri | |
import com.app.manager.usermanager.AuthStatus | |
import com.app.network.model.Country | |
import com.app.network.response.auth.AuthInfo | |
import io.reactivex.Single | |
interface EditProfileInteractor { | |
fun onCountriesAvailable(): Single<List<Country>> | |
fun getCurrentAuthStatus(): AuthStatus | |
fun modifyUserInfo(modifier: (AuthInfo) -> Unit) | |
fun saveUserData(): Single<UserEditResult> | |
fun setCurrentPassword(password: String) | |
fun setNewPassword(password: String) | |
fun setNewPasswordAgain(password: String) | |
fun onAvatarPicked(image: Uri) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.app.feature.editprofile.interactor | |
import android.net.Uri | |
import com.b.basetools.util.Utils | |
import com.app.exception.NothingChangedException | |
import com.app.exception.PasswordNotGivenException | |
import com.app.exception.PasswordsAreNotEqual | |
import com.app.manager.usermanager.AuthStatus | |
import com.app.manager.usermanager.LoggedInStatus | |
import com.app.manager.usermanager.UserManager | |
import com.app.network.AppApi | |
import com.app.network.model.Country | |
import com.app.network.request.edit.EditUserRequest | |
import com.app.network.request.edit.User | |
import com.app.network.response.auth.AuthInfo | |
import com.app.source.country.CountrySource | |
import com.app.utils.appContext | |
import com.app.feature.editprofile.interactor.UserEditResult | |
import com.app.network.request.edit.EditUserRequest | |
import com.book2read.network.request.edit.EditUserRequest | |
import io.reactivex.Single | |
import io.reactivex.schedulers.Schedulers | |
class EditProfileInteractorImpl constructor(private val api: AppApi, | |
private val countrySource: CountrySource, | |
private val userManager: UserManager) : EditProfileInteractor { | |
private var curUserInfo: AuthInfo = (getCurrentAuthStatus() as LoggedInStatus).auth | |
private var editableUserInfo: AuthInfo = curUserInfo.copy() | |
private var newAvatar: Uri? = null | |
private var curPassword: String? = null | |
private var newPassword: String? = null | |
private var newPasswordAgain: String = "" | |
override fun onCountriesAvailable(): Single<List<Country>> = countrySource.getDataCached() | |
override fun getCurrentAuthStatus(): AuthStatus = userManager.getCurrentAuthStatus() | |
override fun modifyUserInfo(modifier: (AuthInfo) -> Unit) { | |
modifier(editableUserInfo) | |
} | |
override fun saveUserData(): Single<UserEditResult> { | |
val userDataToSave = editableUserInfo | |
val isNeedToPresentPassword = curUserInfo.showPassField && (!newPassword.isNullOrBlank() || curUserInfo.mail != editableUserInfo.mail) | |
val passwordIsPresented = !curPassword.isNullOrBlank() | |
return if (isNeedToPresentPassword && !passwordIsPresented) { | |
Single.error(PasswordNotGivenException()) | |
} else { | |
val userWantToChangePassword = !newPassword.isNullOrBlank() | |
if (userWantToChangePassword) { | |
val passwordsAreEqual = newPassword == newPasswordAgain | |
if (passwordsAreEqual) { | |
editValidData(userDataToSave) | |
} else { | |
Single.error(PasswordsAreNotEqual()) | |
} | |
} else { | |
val isSomethingChanged = isSomethingChanged() | |
return when { | |
isSomethingChanged -> editValidData(userDataToSave) | |
else -> Single.error(NothingChangedException()) | |
} | |
} | |
} | |
} | |
private fun isSomethingChanged(): Boolean { | |
return curUserInfo != editableUserInfo || newAvatar != null | |
} | |
private fun editValidData(userInfo: AuthInfo): Single<UserEditResult> { | |
return prepareEditRequest(userInfo) | |
.flatMap { api.editUserData(it) } | |
.flatMap { api.userInfo() } | |
.map { UserEditResult(it.authInfo, !newPassword.isNullOrBlank()) } | |
.doOnSuccess { editResult -> | |
val updated = editResult.userInfo | |
curUserInfo = updated | |
editableUserInfo = curUserInfo.copy() | |
if (editResult.passwordWasChanged) { | |
dispose() | |
} | |
userManager.notifyUserChanged(updated) | |
} | |
} | |
private fun prepareEditRequest(userInfo: AuthInfo): Single<EditUserRequest> { | |
return if (newAvatar == null) { | |
Single.just(User(userInfo.mail, curPassword, newPassword, userInfo.country)) | |
} else { | |
Single.just(newAvatar) | |
.observeOn(Schedulers.computation()) | |
.map { | |
val avatar = Utils.createFileBody(appContext(), it) | |
User(userInfo.mail, curPassword, newPassword, userInfo.country, avatar) | |
} | |
}.map(::EditUserRequest) | |
} | |
override fun setCurrentPassword(password: String) { | |
curPassword = password | |
} | |
override fun setNewPassword(password: String) { | |
newPassword = password | |
} | |
override fun setNewPasswordAgain(password: String) { | |
newPasswordAgain = password | |
} | |
override fun onAvatarPicked(image: Uri) { | |
newAvatar = image | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.app.feature.editprofile.di | |
import com.app.feature.editprofile.interactor.EditProfileInteractor | |
import com.app.feature.editprofile.interactor.EditProfileInteractorImpl | |
import com.app.manager.usermanager.UserManager | |
import com.app.network.AppApi | |
import com.app.source.country.CountrySource | |
import dagger.Module | |
import dagger.Provides | |
import javax.inject.Singleton | |
@Module | |
class EditProfileModule { | |
@Provides | |
@Singleton | |
fun provideEditProfileInteractor(apiManager: AppApi, countrySource: CountrySource, userManager: UserManager): EditProfileInteractor { | |
return EditProfileInteractorImpl(apiManager, countrySource, userManager) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.app.feature.editprofile.presenter | |
import android.net.Uri | |
import com.arellomobile.mvp.InjectViewState | |
import com.b.basetools.component.BasePresenter | |
import com.app.R | |
import com.app.feature.editprofile.interactor.EditProfileInteractor | |
import com.app.feature.editprofile.interactor.UserEditResult | |
import com.app.feature.editprofile.view.EditProfileView | |
import com.app.manager.usermanager.LoggedInStatus | |
import com.app.network.model.Country | |
import com.app.network.response.auth.AuthInfo | |
import io.reactivex.android.schedulers.AndroidSchedulers | |
import io.reactivex.disposables.Disposable | |
import timber.log.Timber | |
import javax.inject.Inject | |
@InjectViewState | |
class EditProfilePresenter @Inject constructor(private val interactor: EditProfileInteractor) : BasePresenter<EditProfileView>() { | |
private var userData: AuthInfo = (interactor.getCurrentAuthStatus() as LoggedInStatus).auth | |
override fun onFirstViewAttach() { | |
super.onFirstViewAttach() | |
subscribeToScreenData().track() | |
viewState.setProgressVisible(true) | |
viewState.setInterfaceEnabled(false) | |
} | |
private fun subscribeToScreenData(): Disposable { | |
return interactor.onCountriesAvailable() | |
.observeOn(AndroidSchedulers.mainThread()) | |
.subscribe(::onScreenDataReady, Timber::e) | |
} | |
private fun onScreenDataReady(countries: List<Country>) { | |
val selectedCountry = countries.firstOrNull { it.isoCode.toLowerCase() == userData.country.toLowerCase() } ?: countries.first() | |
with(viewState) { | |
setCountries(countries, selectedCountry) | |
setUserInfo(userData) | |
setProgressVisible(false) | |
setInterfaceEnabled(true) | |
} | |
} | |
fun onEditPhotoClick() { | |
viewState.goToPickImage() | |
} | |
fun onImagePicked(image: Uri) { | |
interactor.onAvatarPicked(image) | |
viewState.setPortrait(image) | |
} | |
fun onImagePickFailed(err: Throwable) { | |
viewState.showError(err) | |
} | |
fun onSaveClick() { | |
save() | |
} | |
fun onPasswordForgot() { | |
viewState.goRestorePassword() | |
} | |
private fun save() { | |
viewState.setInterfaceEnabled(false) | |
viewState.setProgressVisible(true) | |
interactor.saveUserData() | |
.observeOn(AndroidSchedulers.mainThread()) | |
.subscribe(::onSaveDataSuccess, ::onSaveDataFailed).track() | |
} | |
private fun onSaveDataSuccess(userEditResult: UserEditResult) { | |
viewState.notify(R.string.saved) | |
if (userEditResult.passwordWasChanged) { | |
viewState.clearPasswordFields() | |
} | |
onSaveDataComplete() | |
} | |
private fun onSaveDataFailed(err: Throwable) { | |
viewState.notify(err.toString()) | |
onSaveDataComplete() | |
} | |
private fun onSaveDataComplete() { | |
viewState.setInterfaceEnabled(true) | |
viewState.setProgressVisible(false) | |
} | |
fun onCountrySelected(country: Country) { | |
interactor.modifyUserInfo { userInfo -> userInfo.country = country.isoCode } | |
} | |
fun onEmailChanged(email: CharSequence) { | |
interactor.modifyUserInfo { userInfo -> userInfo.mail = email.toString().trim() } | |
} | |
fun onCurrentPasswordChanged(password: CharSequence) { | |
interactor.setCurrentPassword(password.toString().trim()) | |
} | |
fun onNewPasswordChanged(password: CharSequence) { | |
interactor.setNewPassword(password.toString().trim()) | |
} | |
fun onNewPasswordRepeatChanged(password: CharSequence) { | |
interactor.setNewPasswordAgain(password.toString().trim()) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.app.feature.editprofile.view | |
import android.net.Uri | |
import android.support.annotation.StringRes | |
import com.arellomobile.mvp.MvpView | |
import com.arellomobile.mvp.viewstate.strategy.AddToEndSingleStrategy | |
import com.arellomobile.mvp.viewstate.strategy.SkipStrategy | |
import com.arellomobile.mvp.viewstate.strategy.StateStrategyType | |
import com.app.network.model.Country | |
import com.app.network.response.auth.AuthInfo | |
@StateStrategyType(AddToEndSingleStrategy::class) | |
interface EditProfileView : MvpView { | |
@StateStrategyType(SkipStrategy::class) | |
fun goToPickImage() | |
fun setPortrait(image: Uri) | |
@StateStrategyType(SkipStrategy::class) | |
fun showError(err: Throwable) | |
fun setCountries(countries: List<Country>, selectedCountry: Country) | |
fun setUserInfo(authInfo: AuthInfo) | |
fun notify(@StringRes messageResource: Int) | |
fun notify(message: String) | |
fun setProgressVisible(visible: Boolean) | |
fun setInterfaceEnabled(enabled: Boolean) | |
fun clearPasswordFields() | |
@StateStrategyType(SkipStrategy::class) | |
fun goRestorePassword(email: String = "") | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.app.feature.editprofile.view | |
import android.content.Context | |
import android.content.Intent | |
import android.databinding.ObservableBoolean | |
import android.net.Uri | |
import android.os.Bundle | |
import android.support.v4.app.ActivityCompat.invalidateOptionsMenu | |
import android.view.Menu | |
import android.view.MenuItem | |
import android.view.View | |
import android.widget.AdapterView | |
import android.widget.ArrayAdapter | |
import com.arellomobile.mvp.presenter.InjectPresenter | |
import com.arellomobile.mvp.presenter.ProvidePresenter | |
import com.b.basetools.binding.ObservableString | |
import com.b.basetools.util.Utils | |
import com.app.AppApplication | |
import com.app.R | |
import com.app.component.AppBaseActivity | |
import com.app.databinding.ViewEditProfileBinding | |
import com.app.feature.editprofile.presenter.EditProfilePresenter | |
import com.app.feature.registration.requestnewpassword.view.RequestNewPasswordVm | |
import com.app.network.model.Country | |
import com.app.network.response.auth.AuthInfo | |
import com.app.utils.AppUtils | |
import com.app.utils.dialog | |
import com.app.utils.portraitloader.PortraitLoader | |
import com.bumptech.glide.Glide | |
import com.bumptech.glide.load.resource.bitmap.CircleCrop | |
import com.bumptech.glide.request.RequestOptions | |
class EditProfileVm : AppBaseActivity<ViewEditProfileBinding>(), EditProfileView { | |
override val layoutId: Int = R.layout.view_edit_profile | |
val oProgressVisible = ObservableBoolean() | |
val oInterfaceEnabled = ObservableBoolean() | |
val oName = ObservableString() | |
val oEmail = ObservableString() | |
val oPassFieldVisible = ObservableBoolean() | |
@InjectPresenter | |
lateinit var presenter: EditProfilePresenter | |
@ProvidePresenter | |
fun providePresenter(): EditProfilePresenter { | |
return AppApplication.mainComponent.editProfileComponent.editProfilePresenter | |
} | |
override fun init(savedInstanceState: Bundle?) { | |
binding.vm = this | |
} | |
override fun onCreateOptionsMenu(menu: Menu?): Boolean { | |
menuInflater.inflate(R.menu.view_edit_profile, menu) | |
return super.onCreateOptionsMenu(menu) | |
} | |
override fun onOptionsItemSelected(item: MenuItem): Boolean { | |
when (item.itemId) { | |
R.id.save -> presenter.onSaveClick() | |
} | |
return true | |
} | |
override fun goToPickImage() { | |
PortraitLoader.goPickImage(this) | |
.subscribe(presenter::onImagePicked, presenter::onImagePickFailed).track() | |
} | |
override fun setPortrait(image: Uri) { | |
Glide.with(this) | |
.load(image) | |
.apply(RequestOptions().transforms(CircleCrop())) | |
.into(binding.portrait) | |
} | |
override fun setCountries(countries: List<Country>, selectedCountry: Country) { | |
val selectionIndex = countries.indexOf(selectedCountry) | |
val countryNames = countries.map { it.displayName } | |
binding.countrySelector.adapter = ArrayAdapter<String>(this, R.layout.spinner_list_item, countryNames) | |
binding.countrySelector.setSelection(selectionIndex) | |
binding.countrySelector.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { | |
override fun onItemSelected(adapter: AdapterView<*>, view: View, position: Int, id: Long) { | |
presenter.onCountrySelected(countries[position]) | |
} | |
override fun onNothingSelected(adapter: AdapterView<*>) {} | |
} | |
} | |
override fun setUserInfo(authInfo: AuthInfo) { | |
oName.set(authInfo.name) | |
oEmail.set(authInfo.validEmail) | |
oPassFieldVisible.set(authInfo.showPassField) | |
Glide.with(this) | |
.load(authInfo.image) | |
.apply(AppUtils.getAvatarRequestOptions()) | |
.into(binding.portrait) | |
} | |
override fun notify(messageResource: Int) { | |
notify(getString(messageResource)) | |
} | |
override fun notify(message: String) { | |
Utils.dialog(this, message) | |
} | |
override fun setProgressVisible(visible: Boolean) { | |
oProgressVisible.set(visible) | |
} | |
override fun setInterfaceEnabled(enabled: Boolean) { | |
oInterfaceEnabled.set(enabled) | |
invalidateOptionsMenu() | |
} | |
override fun onPrepareOptionsMenu(menu: Menu): Boolean { | |
menu.findItem(R.id.save).isEnabled = oInterfaceEnabled.get() | |
return true | |
} | |
override fun clearPasswordFields() { | |
binding.currentPassword.setText("") | |
binding.newPassword.setText("") | |
binding.confirmNewPassword.setText("") | |
} | |
override fun goRestorePassword(email: String) { | |
RequestNewPasswordVm.newInstance(email) | |
.show(this) | |
} | |
override fun showError(err: Throwable) { | |
dialog(this, err) | |
} | |
companion object { | |
fun with(ctx: Context) = Intent(ctx, EditProfileVm::class.java) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment