Signup: add a signup fragment so people can sign up from the app

pull/131/head
Tom Hacohen 4 years ago
parent 44503715a8
commit 2eeee1214f

@ -0,0 +1,226 @@
/*
* Copyright © 2013 2016 Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package com.etesync.syncadapter.ui.etebase
import android.app.Dialog
import android.app.ProgressDialog
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.CheckedTextView
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.commit
import androidx.fragment.app.viewModels
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.observe
import com.etebase.client.Account
import com.etebase.client.Client
import com.etebase.client.User
import com.etebase.client.exceptions.EtebaseException
import com.etesync.syncadapter.Constants
import com.etesync.syncadapter.HttpClient
import com.etesync.syncadapter.R
import com.etesync.syncadapter.ui.setup.BaseConfigurationFinder
import com.etesync.syncadapter.ui.setup.CreateAccountFragment
import com.etesync.syncadapter.ui.setup.DetectConfigurationFragment
import com.etesync.syncadapter.ui.setup.LoginCredentialsFragment
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import net.cachapa.expandablelayout.ExpandableLayout
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread
import java.net.URI
import java.util.concurrent.Future
class SignupFragment(private val initialUsername: String?, private val initialPassword: String?) : Fragment() {
internal lateinit var editUserName: TextInputLayout
internal lateinit var editEmail: TextInputLayout
internal lateinit var editPassword: TextInputLayout
internal lateinit var showAdvanced: CheckedTextView
internal lateinit var customServer: TextInputEditText
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val v = inflater.inflate(R.layout.signup_fragment, container, false)
editUserName = v.findViewById(R.id.user_name)
editEmail = v.findViewById(R.id.email)
editPassword = v.findViewById(R.id.url_password)
showAdvanced = v.findViewById(R.id.show_advanced)
customServer = v.findViewById(R.id.custom_server)
if (savedInstanceState == null) {
editUserName.editText?.setText(initialUsername ?: "")
editPassword.editText?.setText(initialPassword ?: "")
}
val login = v.findViewById<Button>(R.id.login)
login.setOnClickListener {
parentFragmentManager.commit {
replace(android.R.id.content, LoginCredentialsFragment(editUserName.editText?.text.toString(), editPassword.editText?.text.toString()))
}
}
val createAccount = v.findViewById<Button>(R.id.create_account)
createAccount.setOnClickListener {
val credentials = validateData()
if (credentials != null) {
SignupDoFragment(credentials).show(fragmentManager!!, null)
}
}
val advancedLayout = v.findViewById<View>(R.id.advanced_layout) as ExpandableLayout
showAdvanced.setOnClickListener {
if (showAdvanced.isChecked) {
showAdvanced.isChecked = false
advancedLayout.collapse()
} else {
showAdvanced.isChecked = true
advancedLayout.expand()
}
}
return v
}
protected fun validateData(): SignupCredentials? {
var valid = true
val userName = editUserName.editText?.text.toString()
if (userName.isEmpty()) {
editUserName.error = getString(R.string.login_username_error)
valid = false
} else {
editUserName.error = null
}
val email = editEmail.editText?.text.toString()
if (email.isEmpty()) {
editEmail.error = getString(R.string.login_email_address_error)
valid = false
} else {
editEmail.error = null
}
val password = editPassword.editText?.text.toString()
if (password.isEmpty()) {
editPassword.error = getString(R.string.signup_password_restrictions)
valid = false
} else {
editPassword.error = null
}
var uri: URI? = null
if (showAdvanced.isChecked) {
val server = customServer.text.toString()
// If this field is null, just use the default
if (!server.isEmpty()) {
val url = server.toHttpUrlOrNull()
if (url != null) {
uri = url.toUri()
customServer.error = null
} else {
customServer.error = getString(R.string.login_custom_server_error)
valid = false
}
}
}
return if (valid) SignupCredentials(uri, userName, email, password) else null
}
}
class SignupDoFragment(private val signupCredentials: SignupCredentials) : DialogFragment() {
private val model: ConfigurationViewModel by viewModels()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val progress = ProgressDialog(activity)
progress.setTitle(R.string.login_configuration_detection)
progress.setMessage(getString(R.string.login_querying_server))
progress.isIndeterminate = true
progress.setCanceledOnTouchOutside(false)
isCancelable = false
return progress
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
model.signup(requireContext(), signupCredentials)
model.observe(this) {
if (it.isFailed) {
// no service found: show error message
requireFragmentManager().beginTransaction()
.add(DetectConfigurationFragment.NothingDetectedFragment.newInstance(it.error!!.localizedMessage), null)
.commitAllowingStateLoss()
} else {
requireFragmentManager().beginTransaction()
.replace(android.R.id.content, CreateAccountFragment.newInstance(it))
.addToBackStack(null)
.commitAllowingStateLoss()
}
dismissAllowingStateLoss()
}
}
}
}
class ConfigurationViewModel : ViewModel() {
private val account = MutableLiveData<BaseConfigurationFinder.Configuration>()
private var asyncTask: Future<Unit>? = null
fun signup(context: Context, credentials: SignupCredentials) {
asyncTask = doAsync {
val httpClient = HttpClient.Builder(context).build().okHttpClient
val uri = credentials.uri ?: URI(Constants.etebaseServiceUrl)
var etebaseSession: String? = null
var exception: Throwable? = null
try {
val client = Client.create(httpClient, uri.toString())
val user = User(credentials.userName, credentials.email)
val etebase = Account.signup(client, user, credentials.password)
etebaseSession = etebase.save(null)
} catch (e: EtebaseException) {
exception = e
}
uiThread {
account.value = BaseConfigurationFinder.Configuration(
uri,
credentials.userName,
etebaseSession,
null,
null,
exception
)
}
}
}
fun cancelLoad() {
asyncTask?.cancel(true)
}
fun observe(owner: LifecycleOwner, observer: (BaseConfigurationFinder.Configuration) -> Unit) =
account.observe(owner, observer)
}
data class SignupCredentials(val uri: URI?, val userName: String, val email: String, val password: String)

@ -30,7 +30,7 @@ class LoginActivity : BaseActivity() {
if (savedInstanceState == null)
// first call, add fragment
supportFragmentManager.beginTransaction()
.replace(android.R.id.content, LoginCredentialsFragment())
.replace(android.R.id.content, LoginCredentialsFragment(null, null))
.commit()
}
@ -43,16 +43,4 @@ class LoginActivity : BaseActivity() {
fun showHelp(item: MenuItem) {
WebViewActivity.openUrl(this, Constants.helpUri)
}
companion object {
/**
* When set, and [.EXTRA_PASSWORD] is set too, the user name field will be set to this value.
*/
val EXTRA_USERNAME = "username"
/**
* When set, the password field will be set to this value.
*/
val EXTRA_PASSWORD = "password"
}
}

@ -17,16 +17,19 @@ import android.widget.CheckedTextView
import android.widget.EditText
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.fragment.app.commit
import androidx.fragment.app.replace
import com.etesync.syncadapter.Constants
import com.etesync.syncadapter.R
import com.etesync.syncadapter.ui.WebViewActivity
import com.etesync.syncadapter.ui.etebase.SignupFragment
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import net.cachapa.expandablelayout.ExpandableLayout
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import java.net.URI
class LoginCredentialsFragment : Fragment() {
class LoginCredentialsFragment(private val initialUsername: String?, private val initialPassword: String?) : Fragment() {
internal lateinit var editUserName: EditText
internal lateinit var editUrlPassword: TextInputLayout
@ -43,22 +46,15 @@ class LoginCredentialsFragment : Fragment() {
customServer = v.findViewById<TextInputEditText>(R.id.custom_server)
if (savedInstanceState == null) {
val activity = activity
val intent = activity?.intent
if (intent != null) {
// we've got initial login data
val username = intent.getStringExtra(LoginActivity.EXTRA_USERNAME)
val password = intent.getStringExtra(LoginActivity.EXTRA_PASSWORD)
editUserName.setText(username)
editUrlPassword.editText?.setText(password)
}
editUserName.setText(initialUsername ?: "")
editUrlPassword.editText?.setText(initialPassword ?: "")
}
val createAccount = v.findViewById<View>(R.id.create_account) as Button
createAccount.setOnClickListener {
val createUri = Constants.registrationUrl.buildUpon().appendQueryParameter("email", editUserName.text.toString()).build()
WebViewActivity.openUrl(context!!, createUri)
parentFragmentManager.commit {
replace(android.R.id.content, SignupFragment(editUserName.text.toString(), editUrlPassword.editText?.text.toString()))
}
}
val login = v.findViewById<View>(R.id.login) as Button

@ -0,0 +1,134 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright © 2013 2016 Ricki Hirner (bitfire web engineering).
~ All rights reserved. This program and the accompanying materials
~ are made available under the terms of the GNU Public License v3.0
~ which accompanies this distribution, and is available at
~ http://www.gnu.org/licenses/gpl.html
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_margin="@dimen/activity_margin">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/login_type_headline"
android:text="@string/signup_title"
android:layout_marginBottom="14dp"/>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/user_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="14dp">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/login_username"
android:autofillHints="emailAddress"
android:inputType="textEmailAddress"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="14dp">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/login_email_address"
android:autofillHints="emailAddress"
android:inputType="textEmailAddress"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/url_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:autofillHints="password"
android:inputType="textPassword"
android:hint="@string/login_password"/>
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/login_encryption_check_password"/>
<CheckedTextView
android:id="@+id/show_advanced"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:layout_marginLeft="4dp"
android:textSize="18sp"
android:gravity="center_vertical"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:text="@string/login_toggle_advanced" />
<net.cachapa.expandablelayout.ExpandableLayout
android:id="@+id/advanced_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/custom_server"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/login_custom_server"
android:inputType="textUri"/>
</com.google.android.material.textfield.TextInputLayout>
</net.cachapa.expandablelayout.ExpandableLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/stepper_nav_bar">
<Button
android:id="@+id/login"
android:layout_width="0dp"
android:layout_weight="1"
android:text="@string/login_login"
style="@style/stepper_nav_button"/>
<Space
android:layout_width="0dp"
android:layout_weight="1"
style="@style/stepper_nav_button"/>
<Button
android:id="@+id/create_account"
android:layout_width="0dp"
android:layout_weight="1"
android:text="@string/login_signup"
style="@style/stepper_nav_button"/>
</LinearLayout>
</LinearLayout>

@ -211,6 +211,7 @@
<!-- AddAccountActivity -->
<string name="login_title">Add account</string>
<string name="login_username">Username</string>
<string name="login_username_error">Valid username required</string>
<string name="login_email_address">Email</string>
<string name="login_email_address_error">Valid email required</string>
<string name="login_password">Password</string>
@ -220,7 +221,7 @@
<string name="login_encryption_password">Encryption Password</string>
<string name="login_encryption_set_new_password">Please set your encryption password below, and make sure you got it right, as it *can\'t* be recovered if lost!</string>
<string name="login_encryption_enter_password">You are logged in as \"%s\". Please enter your encryption password to continue, or log out from the side menu.</string>
<string name="login_encryption_check_password">* Please double-check the password, as it can\'t be recovered if wrong!</string>
<string name="login_encryption_check_password">* Please make sure you remember your password, as it can\'t be recovered if lost!</string>
<string name="login_encryption_extra_info">* This password is used to encrypt your data, unlike the previous one, which is used to log into the service.\nYou are asked to choose a separate encryption password for security reasons. For more information, please refer to the FAQ at: %s</string>
<string name="login_password_required">Password required</string>
<string name="login_login">Log In</string>
@ -245,6 +246,9 @@
<string name="wrong_encryption_password">Wrong encryption password</string>
<string name="wrong_encryption_password_content">Got an integrity error while accessing your account, which most likely means you put in the wrong encryption password.\nPlease note that the username is case sensitive, so please also try different capitalizations, for example make the first character uppercase.\n\nError: %s</string>
<string name="signup_title">Enter Signup Details</string>
<string name="signup_password_restrictions">Password should be at least 8 characters long</string>
<!-- ChangeEncryptionPasswordActivity -->
<string name="change_encryption_password_title">Change Encryption Password</string>
<string name="change_encryption_password_extra_info">Please don\'t use this tool if you believe your encryption password has been compromised. Contact support instead.</string>

Loading…
Cancel
Save