From abe2bc76f7853a5a4b9a0b8df6efb9ebb6b279c6 Mon Sep 17 00:00:00 2001 From: Marvin Borner Date: Tue, 24 Jul 2018 12:37:27 +0200 Subject: Using kotlin now --- .../beam_messenger/beam_messenger/LoginActivity.kt | 295 +++++++++++++++++++++ 1 file changed, 295 insertions(+) create mode 100644 app/src/main/java/com/beam_messenger/beam_messenger/LoginActivity.kt (limited to 'app/src/main/java/com/beam_messenger') diff --git a/app/src/main/java/com/beam_messenger/beam_messenger/LoginActivity.kt b/app/src/main/java/com/beam_messenger/beam_messenger/LoginActivity.kt new file mode 100644 index 0000000..9662778 --- /dev/null +++ b/app/src/main/java/com/beam_messenger/beam_messenger/LoginActivity.kt @@ -0,0 +1,295 @@ +package com.beam_messenger.beam_messenger + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.annotation.TargetApi +import android.content.pm.PackageManager +import android.support.design.widget.Snackbar +import android.support.v7.app.AppCompatActivity +import android.app.LoaderManager.LoaderCallbacks +import android.content.CursorLoader +import android.content.Loader +import android.database.Cursor +import android.net.Uri +import android.os.AsyncTask +import android.os.Build +import android.os.Bundle +import android.provider.ContactsContract +import android.text.TextUtils +import android.view.View +import android.view.inputmethod.EditorInfo +import android.widget.ArrayAdapter +import android.widget.TextView + +import java.util.ArrayList +import android.Manifest.permission.READ_CONTACTS + +import kotlinx.android.synthetic.main.activity_login.* + +/** + * A login screen that offers login via email/password. + */ +class LoginActivity : AppCompatActivity(), LoaderCallbacks { + /** + * Keep track of the login task to ensure we can cancel it if requested. + */ + private var mAuthTask: UserLoginTask? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_login) + // Set up the login form. + populateAutoComplete() + password.setOnEditorActionListener(TextView.OnEditorActionListener { _, id, _ -> + if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) { + attemptLogin() + return@OnEditorActionListener true + } + false + }) + + email_sign_in_button.setOnClickListener { attemptLogin() } + } + + private fun populateAutoComplete() { + if (!mayRequestContacts()) { + return + } + + loaderManager.initLoader(0, null, this) + } + + private fun mayRequestContacts(): Boolean { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return true + } + if (checkSelfPermission(READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { + return true + } + if (shouldShowRequestPermissionRationale(READ_CONTACTS)) { + Snackbar.make(email, R.string.permission_rationale, Snackbar.LENGTH_INDEFINITE) + .setAction(android.R.string.ok, + { requestPermissions(arrayOf(READ_CONTACTS), REQUEST_READ_CONTACTS) }) + } else { + requestPermissions(arrayOf(READ_CONTACTS), REQUEST_READ_CONTACTS) + } + return false + } + + /** + * Callback received when a permissions request has been completed. + */ + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, + grantResults: IntArray) { + if (requestCode == REQUEST_READ_CONTACTS) { + if (grantResults.size == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + populateAutoComplete() + } + } + } + + + /** + * Attempts to sign in or register the account specified by the login form. + * If there are form errors (invalid email, missing fields, etc.), the + * errors are presented and no actual login attempt is made. + */ + private fun attemptLogin() { + if (mAuthTask != null) { + return + } + + // Reset errors. + email.error = null + password.error = null + + // Store values at the time of the login attempt. + val emailStr = email.text.toString() + val passwordStr = password.text.toString() + + var cancel = false + var focusView: View? = null + + // Check for a valid password, if the user entered one. + if (!TextUtils.isEmpty(passwordStr) && !isPasswordValid(passwordStr)) { + password.error = getString(R.string.error_invalid_password) + focusView = password + cancel = true + } + + // Check for a valid email address. + if (TextUtils.isEmpty(emailStr)) { + email.error = getString(R.string.error_field_required) + focusView = email + cancel = true + } else if (!isEmailValid(emailStr)) { + email.error = getString(R.string.error_invalid_email) + focusView = email + cancel = true + } + + if (cancel) { + // There was an error; don't attempt login and focus the first + // form field with an error. + focusView?.requestFocus() + } else { + // Show a progress spinner, and kick off a background task to + // perform the user login attempt. + showProgress(true) + mAuthTask = UserLoginTask(emailStr, passwordStr) + mAuthTask!!.execute(null as Void?) + } + } + + private fun isEmailValid(email: String): Boolean { + //TODO: Replace this with your own logic + return email.contains("@") + } + + private fun isPasswordValid(password: String): Boolean { + //TODO: Replace this with your own logic + return password.length > 4 + } + + /** + * Shows the progress UI and hides the login form. + */ + @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2) + private fun showProgress(show: Boolean) { + // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow + // for very easy animations. If available, use these APIs to fade-in + // the progress spinner. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { + val shortAnimTime = resources.getInteger(android.R.integer.config_shortAnimTime).toLong() + + login_form.visibility = if (show) View.GONE else View.VISIBLE + login_form.animate() + .setDuration(shortAnimTime) + .alpha((if (show) 0 else 1).toFloat()) + .setListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + login_form.visibility = if (show) View.GONE else View.VISIBLE + } + }) + + login_progress.visibility = if (show) View.VISIBLE else View.GONE + login_progress.animate() + .setDuration(shortAnimTime) + .alpha((if (show) 1 else 0).toFloat()) + .setListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + login_progress.visibility = if (show) View.VISIBLE else View.GONE + } + }) + } else { + // The ViewPropertyAnimator APIs are not available, so simply show + // and hide the relevant UI components. + login_progress.visibility = if (show) View.VISIBLE else View.GONE + login_form.visibility = if (show) View.GONE else View.VISIBLE + } + } + + override fun onCreateLoader(i: Int, bundle: Bundle?): Loader { + return CursorLoader(this, + // Retrieve data rows for the device user's 'profile' contact. + Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI, + ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION, + + // Select only email addresses. + ContactsContract.Contacts.Data.MIMETYPE + " = ?", arrayOf(ContactsContract.CommonDataKinds.Email + .CONTENT_ITEM_TYPE), + + // Show primary email addresses first. Note that there won't be + // a primary email address if the user hasn't specified one. + ContactsContract.Contacts.Data.IS_PRIMARY + " DESC") + } + + override fun onLoadFinished(cursorLoader: Loader, cursor: Cursor) { + val emails = ArrayList() + cursor.moveToFirst() + while (!cursor.isAfterLast) { + emails.add(cursor.getString(ProfileQuery.ADDRESS)) + cursor.moveToNext() + } + + addEmailsToAutoComplete(emails) + } + + override fun onLoaderReset(cursorLoader: Loader) { + + } + + private fun addEmailsToAutoComplete(emailAddressCollection: List) { + //Create adapter to tell the AutoCompleteTextView what to show in its dropdown list. + val adapter = ArrayAdapter(this@LoginActivity, + android.R.layout.simple_dropdown_item_1line, emailAddressCollection) + + email.setAdapter(adapter) + } + + object ProfileQuery { + val PROJECTION = arrayOf( + ContactsContract.CommonDataKinds.Email.ADDRESS, + ContactsContract.CommonDataKinds.Email.IS_PRIMARY) + val ADDRESS = 0 + val IS_PRIMARY = 1 + } + + /** + * Represents an asynchronous login/registration task used to authenticate + * the user. + */ + inner class UserLoginTask internal constructor(private val mEmail: String, private val mPassword: String) : AsyncTask() { + + override fun doInBackground(vararg params: Void): Boolean? { + // TODO: attempt authentication against a network service. + + try { + // Simulate network access. + Thread.sleep(2000) + } catch (e: InterruptedException) { + return false + } + + return DUMMY_CREDENTIALS + .map { it.split(":") } + .firstOrNull { it[0] == mEmail } + ?.let { + // Account exists, return true if the password matches. + it[1] == mPassword + } + ?: true + } + + override fun onPostExecute(success: Boolean?) { + mAuthTask = null + showProgress(false) + + if (success!!) { + finish() + } else { + password.error = getString(R.string.error_incorrect_password) + password.requestFocus() + } + } + + override fun onCancelled() { + mAuthTask = null + showProgress(false) + } + } + + companion object { + + /** + * Id to identity READ_CONTACTS permission request. + */ + private val REQUEST_READ_CONTACTS = 0 + + /** + * A dummy authentication store containing known user names and passwords. + * TODO: remove after connecting to a real authentication system. + */ + private val DUMMY_CREDENTIALS = arrayOf("foo@example.com:hello", "bar@example.com:world") + } +} -- cgit v1.2.3