123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- package io.github.zadam.triliumsender
- import android.animation.Animator
- import android.animation.AnimatorListenerAdapter
- import android.annotation.TargetApi
- import android.app.LoaderManager.LoaderCallbacks
- import android.content.Context
- 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.support.v7.app.AppCompatActivity
- import android.text.TextUtils
- import android.util.Log
- import android.view.View
- import android.view.inputmethod.EditorInfo
- import android.widget.ArrayAdapter
- import android.widget.TextView
- import kotlinx.android.synthetic.main.activity_login.*
- import okhttp3.*
- import org.json.JSONObject
- import java.util.*
- /**
- * A login screen that offers login via email/password.
- */
- class LoginActivity : AppCompatActivity(), LoaderCallbacks<Cursor> {
- val JSON = MediaType.parse("application/json; charset=utf-8")
- /**
- * 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)
- 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() }
- }
- /**
- * 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.
- username.error = null
- password.error = null
- // Store values at the time of the login attempt.
- val triliumAddress = trilium_address.text.toString();
- val usernameStr = username.text.toString()
- val passwordStr = password.text.toString()
- var cancel = false
- var focusView: View? = null
- // Check for a valid username
- if (TextUtils.isEmpty(usernameStr)) {
- username.error = getString(R.string.error_field_required)
- focusView = username
- 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(triliumAddress, usernameStr, passwordStr)
- mAuthTask!!.execute(null as Void?)
- }
- }
- /**
- * 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<Cursor> {
- 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: Cursor) {
- val emails = ArrayList<String>()
- cursor.moveToFirst()
- while (!cursor.isAfterLast) {
- emails.add(cursor.getString(ProfileQuery.ADDRESS))
- cursor.moveToNext()
- }
- addEmailsToAutoComplete(emails)
- }
- override fun onLoaderReset(cursorLoader: Loader<Cursor>) {
- }
- private fun addEmailsToAutoComplete(emailAddressCollection: List<String>) {
- //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)
- username.setAdapter(adapter)
- }
- object ProfileQuery {
- val PROJECTION = arrayOf(
- ContactsContract.CommonDataKinds.Email.ADDRESS,
- ContactsContract.CommonDataKinds.Email.IS_PRIMARY)
- val ADDRESS = 0
- val IS_PRIMARY = 1
- }
- inner class LoginResult (val success: Boolean, val errorCode : Int?,
- val token : String? = null);
- /**
- * Represents an asynchronous login/registration task used to authenticate
- * the user.
- */
- inner class UserLoginTask internal constructor(private val mTriliumAddress: String, private val mUsername: String, private val mPassword: String) : AsyncTask<Void, Void, LoginResult>() {
- val TAG : String = "UserLoginTask"
- override fun doInBackground(vararg params: Void): LoginResult {
- val client = OkHttpClient()
- val json = JSONObject()
- json.put("username", mUsername)
- json.put("password", mPassword)
- val body = RequestBody.create(JSON, json.toString())
- val request = Request.Builder()
- .url(mTriliumAddress + "/api/login/token")
- .post(body)
- .build()
- val response: Response;
- try {
- response = client.newCall(request).execute()
- }
- catch (e: Exception) {
- Log.e(TAG, "Can't connect to Trilium server", e);
- return LoginResult(false, R.string.error_network_error)
- }
- Log.i(TAG,"Response code: " + response.code())
- if (response.code() == 401) {
- return LoginResult(false, R.string.error_incorrect_credentials)
- }
- else if (response.code() != 200) {
- return LoginResult(false, R.string.error_unexpected_response)
- }
- val responseText = response.body()?.string()
- Log.i(TAG,"Response text: " + responseText)
- val resp = JSONObject(responseText)
- val token : String = resp.get("token") as String
- Log.i(TAG,"Token: " + token)
- return LoginResult(true, null, token);
- }
- override fun onPostExecute(loginResult: LoginResult) {
- mAuthTask = null
- showProgress(false)
- if (loginResult.success) {
- val prefs = this@LoginActivity.getSharedPreferences(MainActivity.PREFRENCES_NAME, Context.MODE_PRIVATE);
- val editor = prefs.edit()
- editor.putString(MainActivity.PREF_TRILIUM_ADDRESS, mTriliumAddress)
- editor.putString(MainActivity.PREF_TOKEN, loginResult.token);
- editor.apply()
- finish()
- } else {
- if (loginResult.errorCode == R.string.error_network_error
- || loginResult.errorCode == R.string.error_unexpected_response) {
- trilium_address.error = getString(loginResult.errorCode)
- trilium_address.requestFocus()
- }
- else if (loginResult.errorCode == R.string.error_incorrect_credentials) {
- password.error = getString(loginResult.errorCode)
- password.requestFocus()
- }
- else {
- throw RuntimeException("Unknown code: " + loginResult.errorCode);
- }
- }
- }
- override fun onCancelled() {
- mAuthTask = null
- showProgress(false)
- }
- }
- }
|