From d4478b7a4598e183227372b884209cea3cca1c0d Mon Sep 17 00:00:00 2001 From: Alexandra Daraban Date: Sat, 20 Apr 2019 12:05:17 +0300 Subject: [PATCH 1/4] added kotlin plugin --- app/build.gradle | 11 ++++++++--- build.gradle | 3 ++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6612e2d..636487e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,10 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-kapt' apply plugin: 'realm-android' + android { compileSdkVersion 28 buildToolsVersion '28.0.3' @@ -65,7 +69,7 @@ dependencies { implementation 'android.arch.lifecycle:extensions:1.1.1' implementation 'com.jakewharton:butterknife:8.8.1' - annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' + kapt 'com.jakewharton:butterknife-compiler:8.8.1' implementation 'com.squareup.retrofit2:retrofit:2.4.0' implementation 'com.squareup.retrofit2:converter-gson:2.4.0' @@ -76,8 +80,8 @@ dependencies { implementation 'com.google.dagger:dagger:2.17' implementation 'com.google.dagger:dagger-android-support:2.17' - annotationProcessor 'com.google.dagger:dagger-compiler:2.17' - annotationProcessor 'com.google.dagger:dagger-android-processor:2.17' + kapt 'com.google.dagger:dagger-compiler:2.17' + kapt 'com.google.dagger:dagger-android-processor:2.17' implementation 'io.reactivex:rxandroid:1.2.1' implementation 'io.reactivex:rxjava:1.2.4' @@ -87,6 +91,7 @@ dependencies { testImplementation 'junit:junit:' + rootProject.junitVersion testImplementation 'org.mockito:mockito-core:' + rootProject.mockitoVersion + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" } android.variantFilter { variant -> diff --git a/build.gradle b/build.gradle index 2dfe30b..f87004a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,13 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.kotlin_version = '1.3.21' repositories { jcenter() google() } dependencies { - + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.android.tools.build:gradle:3.3.2' classpath "io.realm:realm-gradle-plugin:5.7.0" // NOTE: Do not place your application dependencies here; they belong From 4f577729d287ea93a5443ae6b5b12fefdb1b2661 Mon Sep 17 00:00:00 2001 From: Alexandra Daraban Date: Sat, 20 Apr 2019 15:32:16 +0300 Subject: [PATCH 2/4] firebase authentication with phone number --- app/build.gradle | 6 + app/src/dev/res/values/authorities.xml | 6 +- app/src/main/AndroidManifest.xml | 6 + .../monitorizarevot/PhoneAuthActivity.kt | 173 ++++++++++++++++++ .../code4/monitorizarevot/StartActivity.java | 2 +- .../dagger/ActivityBindingModule.java | 4 + .../dagger/ViewModelModule.java | 19 +- .../viewmodel/PhoneAuthViewModel.kt | 72 ++++++++ .../main/res/layout/activity_phone_auth.xml | 125 +++++++++++++ app/src/main/res/values-ro/strings.xml | 3 + app/src/main/res/values/strings.xml | 1 + build.gradle | 1 + 12 files changed, 413 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/ro/code4/monitorizarevot/PhoneAuthActivity.kt create mode 100644 app/src/main/java/ro/code4/monitorizarevot/viewmodel/PhoneAuthViewModel.kt create mode 100644 app/src/main/res/layout/activity_phone_auth.xml diff --git a/app/build.gradle b/app/build.gradle index 636487e..aab2c9c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -92,6 +92,10 @@ dependencies { testImplementation 'junit:junit:' + rootProject.junitVersion testImplementation 'org.mockito:mockito-core:' + rootProject.mockitoVersion implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + + implementation 'com.google.firebase:firebase-core:16.0.8' + implementation 'com.google.firebase:firebase-auth:16.2.1' + } android.variantFilter { variant -> @@ -116,3 +120,5 @@ tasks.withType(Test) { events "started", "passed", "skipped", "failed" } } + +apply plugin: 'com.google.gms.google-services' \ No newline at end of file diff --git a/app/src/dev/res/values/authorities.xml b/app/src/dev/res/values/authorities.xml index d20ac64..9174ecd 100644 --- a/app/src/dev/res/values/authorities.xml +++ b/app/src/dev/res/values/authorities.xml @@ -1,6 +1,6 @@ - ro.code4.monitorizarevot.dev - ro.code4.monitorizarevot.dev - dev.code4.ro + ro.code4.monitorizarevot.dev + ro.code4.monitorizarevot.dev + dev.code4.ro \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b81339e..157e26a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -45,6 +45,12 @@ android:screenOrientation="portrait" android:theme="@style/AppTheme" android:windowSoftInputMode="stateAlwaysHidden|adjustResize" /> + diff --git a/app/src/main/java/ro/code4/monitorizarevot/PhoneAuthActivity.kt b/app/src/main/java/ro/code4/monitorizarevot/PhoneAuthActivity.kt new file mode 100644 index 0000000..882d497 --- /dev/null +++ b/app/src/main/java/ro/code4/monitorizarevot/PhoneAuthActivity.kt @@ -0,0 +1,173 @@ +package ro.code4.monitorizarevot + +import android.annotation.SuppressLint +import android.app.ActivityOptions +import android.arch.lifecycle.Observer +import android.arch.lifecycle.ViewModelProviders +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.provider.Settings +import android.text.Editable +import android.text.TextUtils +import android.text.TextWatcher +import android.util.Log +import android.util.Pair +import android.view.View +import android.view.inputmethod.EditorInfo +import android.widget.Toast +import butterknife.ButterKnife +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException +import com.google.firebase.auth.PhoneAuthCredential +import com.google.firebase.auth.PhoneAuthProvider +import kotlinx.android.synthetic.main.activity_phone_auth.* +import ro.code4.monitorizarevot.constants.Constants.ORGANISATION_WEB_URL +import ro.code4.monitorizarevot.viewmodel.PhoneAuthViewModel +import java.util.concurrent.TimeUnit + + +class PhoneAuthActivity : BaseActivity() { + + + companion object { + @JvmStatic + val TAG: String = PhoneAuthActivity::class.java.simpleName + } + + private fun signInWithPhoneAuthCredential(credential: PhoneAuthCredential) { + auth.signInWithCredential(credential) + .addOnCompleteListener(this) { task -> + if (task.isSuccessful) { + // Sign in success, update UI with the signed-in user's information + Log.d(TAG, "signInWithCredential:success") + + val user = task.result?.user + + // ... + } else { + // Sign in failed, display a message and update the UI + Log.w(TAG, "signInWithCredential:failure", task.exception) + if (task.exception is FirebaseAuthInvalidCredentialsException) { + // The verification code entered was invalid + showErrorDialog(task.exception?.localizedMessage) + } + } + } + } + + lateinit var auth: FirebaseAuth + override fun setupViewModel() { + viewModel = ViewModelProviders.of(this, factory).get(PhoneAuthViewModel::class.java) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_phone_auth) + ButterKnife.bind(this) + + + auth = FirebaseAuth.getInstance() + auth.useAppLanguage() + + app_version.text = getString(R.string.app_version, BuildConfig.VERSION_NAME) + + viewModel.message().observe(this, Observer { message -> showErrorDialog(message) }) + + viewModel.loginStatus().observe(this, Observer { status -> + if (status!!) { + performNavigation() + } + }) + + viewModel.codeSent().observe(this, Observer { + loginBtn.text = getString(R.string.login) + codeLayout.visibility = View.VISIBLE + }) + + viewModel.credential().observe(this, Observer { credential -> + credential?.let { + signInWithPhoneAuthCredential(it) + } + }) + + phone.addTextChangedListener(object : TextWatcher { + override fun afterTextChanged(s: Editable?) = Unit + + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + loginBtn.isEnabled = s.isNotEmpty() + } + + }) + + loginBtn.setOnClickListener { + + handleLoginClick() + } + login_organisation_link.setOnClickListener { + openOrganisationWebpage() + } + + code.setOnEditorActionListener { _, actionId, _ -> + if (actionId == EditorInfo.IME_ACTION_DONE) { + handleLoginClick() + } + return@setOnEditorActionListener false + } + + } + + private fun handleLoginClick() { + val phoneText = phone.text.toString() + val codeText = code.text.toString() + if (codeText.isNotEmpty()) { + login(phoneText, codeText) + } else { + if (phoneText.isNotEmpty()) { + validatePhoneNumber(phoneText) + } + } + } + + private fun showErrorDialog(message: String?) { + if (!TextUtils.isEmpty(message)) { + Toast.makeText(App.getContext(), message, Toast.LENGTH_LONG).show() + } + } + + + private fun validatePhoneNumber(phoneText: String) { + //TODO remove hardcoded country code + PhoneAuthProvider.getInstance() + .verifyPhoneNumber("+4$phoneText", 60, TimeUnit.SECONDS, this, viewModel.callbacks) + } + + @SuppressLint("HardwareIds") + private fun login(phoneNumber: String, code: String) { + val uuid = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID) + viewModel.login(phoneNumber, code, uuid) + } + + + private fun performNavigation() { + val intent = Intent(this, ToolbarActivity::class.java) + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + val options: ActivityOptions + val sharedBackground = Pair(findViewById(R.id.purple_background), + getString(R.string.shared_element_login_background)) + val sharedLogo = Pair(findViewById(R.id.logo), getString(R.string.shared_element_logo)) + options = ActivityOptions + .makeSceneTransitionAnimation(this, sharedBackground, sharedLogo) + startActivity(intent, options.toBundle()) + } else { + startActivity(intent) + } + finish() //TODO finish after transition is complete + } + + private fun openOrganisationWebpage() { + val openBrowser = Intent(Intent.ACTION_VIEW, Uri.parse(ORGANISATION_WEB_URL)) + startActivity(openBrowser) + } +} \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/StartActivity.java b/app/src/main/java/ro/code4/monitorizarevot/StartActivity.java index a58068f..7ad4cc5 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/StartActivity.java +++ b/app/src/main/java/ro/code4/monitorizarevot/StartActivity.java @@ -29,7 +29,7 @@ private class PreferencesSubscriber extends ObservableListener { public void onNext(Boolean hasCredentials) { super.onNext(hasCredentials); startActivity(new Intent(StartActivity.this, - hasCredentials ? ToolbarActivity.class : LoginActivity.class)); + hasCredentials ? ToolbarActivity.class : PhoneAuthActivity.class)); } @Override diff --git a/app/src/main/java/ro/code4/monitorizarevot/dagger/ActivityBindingModule.java b/app/src/main/java/ro/code4/monitorizarevot/dagger/ActivityBindingModule.java index 84c915d..094e1f2 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/dagger/ActivityBindingModule.java +++ b/app/src/main/java/ro/code4/monitorizarevot/dagger/ActivityBindingModule.java @@ -3,6 +3,7 @@ import dagger.Module; import dagger.android.ContributesAndroidInjector; import ro.code4.monitorizarevot.LoginActivity; +import ro.code4.monitorizarevot.PhoneAuthActivity; import ro.code4.monitorizarevot.StartActivity; import ro.code4.monitorizarevot.ToolbarActivity; @@ -15,6 +16,9 @@ public abstract class ActivityBindingModule { @ContributesAndroidInjector abstract LoginActivity loginActivity(); + @ContributesAndroidInjector + abstract PhoneAuthActivity phoneAuthActivity(); + @ContributesAndroidInjector(modules = {FragmentBindingModule.class}) abstract ToolbarActivity toolbarActivity(); diff --git a/app/src/main/java/ro/code4/monitorizarevot/dagger/ViewModelModule.java b/app/src/main/java/ro/code4/monitorizarevot/dagger/ViewModelModule.java index 5b29450..30f8094 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/dagger/ViewModelModule.java +++ b/app/src/main/java/ro/code4/monitorizarevot/dagger/ViewModelModule.java @@ -6,7 +6,19 @@ import dagger.Binds; import dagger.Module; import dagger.multibindings.IntoMap; -import ro.code4.monitorizarevot.viewmodel.*; +import ro.code4.monitorizarevot.viewmodel.AddNoteViewModel; +import ro.code4.monitorizarevot.viewmodel.BranchDetailsViewModel; +import ro.code4.monitorizarevot.viewmodel.BranchSelectionViewModel; +import ro.code4.monitorizarevot.viewmodel.FormsListViewModel; +import ro.code4.monitorizarevot.viewmodel.GuideViewModel; +import ro.code4.monitorizarevot.viewmodel.LoginViewModel; +import ro.code4.monitorizarevot.viewmodel.PhoneAuthViewModel; +import ro.code4.monitorizarevot.viewmodel.QuestionDetailsViewModel; +import ro.code4.monitorizarevot.viewmodel.QuestionOverviewViewModel; +import ro.code4.monitorizarevot.viewmodel.QuestionViewModel; +import ro.code4.monitorizarevot.viewmodel.StartViewModel; +import ro.code4.monitorizarevot.viewmodel.ToolbarViewModel; +import ro.code4.monitorizarevot.viewmodel.ViewModelFactory; @Module public abstract class ViewModelModule { @@ -24,6 +36,11 @@ public abstract class ViewModelModule { @ViewModelKey(LoginViewModel.class) abstract ViewModel bindLoginViewModel(LoginViewModel viewModel); + @Binds + @IntoMap + @ViewModelKey(PhoneAuthViewModel.class) + abstract ViewModel bindPhoneAuthViewModel(PhoneAuthViewModel viewModel); + @Binds @IntoMap @ViewModelKey(ToolbarViewModel.class) diff --git a/app/src/main/java/ro/code4/monitorizarevot/viewmodel/PhoneAuthViewModel.kt b/app/src/main/java/ro/code4/monitorizarevot/viewmodel/PhoneAuthViewModel.kt new file mode 100644 index 0000000..c2b1fbe --- /dev/null +++ b/app/src/main/java/ro/code4/monitorizarevot/viewmodel/PhoneAuthViewModel.kt @@ -0,0 +1,72 @@ +package ro.code4.monitorizarevot.viewmodel + +import android.arch.lifecycle.LiveData +import android.util.Log +import com.google.firebase.FirebaseException +import com.google.firebase.auth.PhoneAuthCredential +import com.google.firebase.auth.PhoneAuthProvider +import ro.code4.monitorizarevot.domain.usecase.UseCaseFactory +import ro.code4.monitorizarevot.presentation.LoadingMessage +import ro.code4.monitorizarevot.presentation.LoadingMessageFactory +import ro.code4.monitorizarevot.presentation.Message +import ro.code4.monitorizarevot.presentation.MessageFactory +import ro.code4.monitorizarevot.presentation.livedata.SingleLiveEvent +import ro.code4.monitorizarevot.presentation.rx.DefaultObserver +import javax.inject.Inject + +class PhoneAuthViewModel @Inject +constructor(useCaseFactory: UseCaseFactory, loadingMessageFactory: LoadingMessageFactory, messageFactory: MessageFactory) : BaseViewModel(useCaseFactory, loadingMessageFactory, messageFactory) { + + private val mLoginStatus = SingleLiveEvent() + private val codeSentLiveData = SingleLiveEvent() + private val credentialLiveData = SingleLiveEvent() + var verificationId: String? = null + + val callbacks: PhoneAuthProvider.OnVerificationStateChangedCallbacks by lazy { + object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() { + override fun onVerificationCompleted(credential: PhoneAuthCredential?) { +// login(credential) + } + + override fun onVerificationFailed(exception: FirebaseException?) { + mMessage.postValue(exception?.localizedMessage) + } + + override fun onCodeSent(vId: String?, token: PhoneAuthProvider.ForceResendingToken?) { + Log.i("phoneauth", vId) + verificationId = vId + codeSentLiveData.postValue(true) + } + + } + } + + fun codeSent(): LiveData = codeSentLiveData + fun credential(): LiveData = credentialLiveData + fun loginStatus(): LiveData = mLoginStatus + + fun login(phoneNumber: String, code: String, uuid: String) { + val credential = PhoneAuthProvider.getCredential(verificationId!!, code) + credentialLiveData.postValue(credential) +// val loginUC = mUseCaseFactory.login() +// +// mContentLoading.postValue(getLoadingMessage(loginUC.type)) +// +// val params = LoginDataParams(phoneNumber, code, uuid) +// loginUC.execute(LoginObserver(mMessageFactory), params) + } + + + private inner class LoginObserver(messageFactory: MessageFactory) : DefaultObserver(messageFactory) { + + override fun onNext(success: Boolean?) { + mLoginStatus.postValue(success) + mContentLoading.postValue(LoadingMessage(false)) + } + + override fun onErrorMessage(message: Message) { + mMessage.postValue(message.message) + mContentLoading.postValue(LoadingMessage(false)) + } + } +} diff --git a/app/src/main/res/layout/activity_phone_auth.xml b/app/src/main/res/layout/activity_phone_auth.xml new file mode 100644 index 0000000..c616be8 --- /dev/null +++ b/app/src/main/res/layout/activity_phone_auth.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +