diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4c7351c1c4b7fd6d64a76e9c6d5affb08e54ef31..414ed3f05b48552bae6367710b66841ededa4661 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -79,6 +79,8 @@ dependencies { implementation(appCompat("recyclerview-v7")) implementation("com.android.support.constraint:constraint-layout:1.0.2") + implementation("com.github.StephenVinouze.AdvancedRecyclerView:core:1.1.6") + implementation("io.reactivex.rxjava2:rxjava:2.1.3") implementation(appArch("lifecycle", "runtime", version = "1.0.0")) diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/Keys.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/Keys.kt new file mode 100644 index 0000000000000000000000000000000000000000..4fa19496ad07605d3d768b699de5522c2cb14457 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/Keys.kt @@ -0,0 +1,10 @@ +package de.kuschku.quasseldroid_ng + +object Keys { + object Status { + const val NAME = "status" + + const val selectedAccount = "selectedAccount" + const val reconnect = "reconnect" + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt index 684888bc8d6f5351e987360ae5c68cdc294e15ba..f6e4c396aae7154fe864dbe95cb20263df6c191b 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt @@ -6,9 +6,9 @@ import android.content.pm.ShortcutInfo import android.content.pm.ShortcutManager import android.graphics.drawable.Icon import android.os.Build -import de.kuschku.quasseldroid_ng.util.AndroidCompatibilityUtils -import de.kuschku.quasseldroid_ng.util.AndroidLoggingHandler -import de.kuschku.quasseldroid_ng.util.AndroidStreamChannelFactory +import de.kuschku.quasseldroid_ng.util.compatibility.AndroidCompatibilityUtils +import de.kuschku.quasseldroid_ng.util.compatibility.AndroidLoggingHandler +import de.kuschku.quasseldroid_ng.util.compatibility.AndroidStreamChannelFactory import de.kuschku.quasseldroid_ng.util.helper.systemService import org.acra.ACRA import org.acra.ReportingInteractionMode diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt index 250be610d8b6ee7c6703670712ff931fd4340d3c..ed212b2e0580fc1fa608954cdd0264e6b6619bad 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt @@ -13,7 +13,7 @@ import de.kuschku.libquassel.session.SocketAddress import de.kuschku.quasseldroid_ng.BuildConfig import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase -import de.kuschku.quasseldroid_ng.util.AndroidHandlerService +import de.kuschku.quasseldroid_ng.util.compatibility.AndroidHandlerService import org.threeten.bp.Instant import java.security.cert.X509Certificate import javax.net.ssl.X509TrustManager diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/ChatActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/ChatActivity.kt index 9e198fc6fd43886ba828c61ff8dd42be625e9fbb..8cf87b3f5b03ac3dbec169a4bd62ec38ace52d49 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/ChatActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/ChatActivity.kt @@ -20,6 +20,7 @@ import de.kuschku.libquassel.session.Backend import de.kuschku.libquassel.session.ConnectionState import de.kuschku.libquassel.session.SocketAddress import de.kuschku.libquassel.util.compatibility.LoggingHandler +import de.kuschku.quasseldroid_ng.Keys import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.persistence.AccountDatabase import de.kuschku.quasseldroid_ng.service.QuasselService @@ -97,15 +98,15 @@ class ChatActivity : ServiceBoundActivity() { val database = AccountDatabase.Creator.init(this) handler.post { - val accountId = getSharedPreferences("status", Context.MODE_PRIVATE) - ?.getLong(selectedAccountKey, -1) ?: -1 + val accountId = getSharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE) + ?.getLong(Keys.Status.selectedAccount, -1) ?: -1 if (accountId == -1L) { - setResult(Activity.RESULT_CANCELED) + setResult(Activity.RESULT_OK) finish() } val it = database.accounts().findById(accountId) if (it == null) { - setResult(Activity.RESULT_CANCELED) + setResult(Activity.RESULT_OK) finish() } account = it @@ -149,8 +150,8 @@ class ChatActivity : ServiceBoundActivity() { override fun onOptionsItemSelected(item: MenuItem?) = when (item?.itemId) { R.id.disconnect -> { - getSharedPreferences("status", Context.MODE_PRIVATE).editApply { - putBoolean("reconnect", false) + getSharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE).editApply { + putBoolean(Keys.Status.reconnect, false) } finish() true @@ -167,8 +168,4 @@ class ChatActivity : ServiceBoundActivity() { LoggingHandler.loggingHandlers.remove(logHandler) super.onStop() } - - companion object { - private const val selectedAccountKey = "selectedAccount" - } } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/SetupActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/SetupActivity.kt index 202ff734e7d7df23e4961e61bdb23cc627159e8a..8991a7e38d2e94b792077b2437482dadc1367b3e 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/SetupActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/SetupActivity.kt @@ -176,6 +176,13 @@ abstract class SetupActivity : AppCompatActivity() { } } + override fun onBackPressed() { + if (viewPager.currentItem == 0) + super.onBackPressed() + else + viewPager.currentItem -= 1 + } + companion object { private const val currentItemKey = ":setupActivity:currentItem" private const val lastValidItemKey = ":setupActivity:lastValidItem" diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountAdapter.kt index cd2214cc0f77cf23b2ef4db2be41769b0e3ee9bf..dc2a18205d32e2534ff720d46556766876e6c080 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountAdapter.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountAdapter.kt @@ -1,6 +1,5 @@ package de.kuschku.quasseldroid_ng.ui.setup.accounts -import android.arch.lifecycle.MutableLiveData import android.arch.paging.PagedListAdapter import android.support.v7.recyclerview.extensions.DiffCallback import android.support.v7.widget.AppCompatImageButton @@ -19,18 +18,12 @@ class AccountAdapter : PagedListAdapter<AccountDatabase.Account, AccountAdapter.AccountViewHolder>(DIFF_CALLBACK) { private val actionListeners = mutableSetOf<(Long) -> Unit>() private val addListeners = mutableSetOf<() -> Unit>() - val selectedItemId: Long - get() { - val position = selectedPos.value - return if (position != null && position > -1 && position < super.getItemCount()) - getItem(position)?.id ?: -1 - else - -1 - } + private val selectionListeners = mutableSetOf<(Long) -> Unit>() private val clickListener = object : ItemListener { override fun onAction(id: Long, pos: Int) { - changeSelection(id, pos) + notifySelectionChanged(selectedItemView, pos) + selectionListener.invoke(id) } } @@ -42,6 +35,17 @@ class AccountAdapter : } } + private val selectionListener = { id: Long -> + selectedItemId = id + for (selectionListener in selectionListeners) { + selectionListener.invoke(id) + } + } + + private var selectedItemView = -1 + var selectedItemId = -1L + private set + private val addListener = { for (addListener in addListeners) { addListener.invoke() @@ -64,6 +68,14 @@ class AccountAdapter : addListeners.remove(f) } + fun addSelectionListener(f: (Long) -> Unit) { + selectionListeners.add(f) + } + + fun removeSelectionListener(f: (Long) -> Unit) { + selectionListeners.remove(f) + } + override fun onBindViewHolder(holder: AccountViewHolder, position: Int) { when (holder) { is AccountViewHolder.Item -> { @@ -71,12 +83,11 @@ class AccountAdapter : if (account == null) { holder.clear() } else { - val selected = selectedId == account.id - holder.bind(account, position, selected) - if (selected && position != selectedPos.value) - holder.itemView.post { - changeSelection(account.id, position) - } + val selected = account.id == selectedItemId + if (selected) { + selectedItemView = position + } + holder.bind(account, selected) } } is AccountViewHolder.Add -> { @@ -123,37 +134,23 @@ class AccountAdapter : } } - private fun notifySelectionChanged(from: Int?, to: Int?) { - if (from != null && from != -1) - notifyItemChanged(from) - - val real_to = to ?: -1 - selectedPos.value = real_to - - if (to != null && to != -1) - notifyItemChanged(to) + fun selectAccount(id: Long) { + selectedItemView = -1 + selectionListener(id) } - fun changeSelection(id: Long, position: Int) { - notifySelectionChanged(selectedPos.value, position) - selectedId = id - } + fun notifySelectionChanged(from: Int?, to: Int?) { + val _from = from ?: -1 + val _to = to ?: -1 - private fun indexOf(id: Long) = (0 until itemCount).lastOrNull { - getItemViewType(it) == TYPE_ACCOUNT && getItem(it)?.id == id - } ?: -1 + if (_from != -1) + notifyItemChanged(_from) - fun selectAccount(id: Long) { - val index = indexOf(id) - if (index != -1) { - changeSelection(id, index) - } else { - selectedId = id - } - } + selectedItemView = _to - private var selectedId = -1L - var selectedPos = MutableLiveData<Int>() + if (_to != -1) + notifyItemChanged(_to) + } interface ItemListener { fun onAction(id: Long, pos: Int) @@ -175,20 +172,18 @@ class AccountAdapter : lateinit var accountEdit: AppCompatImageButton private var id = -1L - private var index = -1 init { ButterKnife.bind(this, itemView) accountEdit.setOnClickListener { - actionListener.onAction(id, index) + actionListener.onAction(id, adapterPosition) } itemView.setOnClickListener { - clickListener.onAction(id, index) + clickListener.onAction(id, adapterPosition) } } - fun bind(account: AccountDatabase.Account, position: Int, selected: Boolean) { - index = position + fun bind(account: AccountDatabase.Account, selected: Boolean) { id = account.id accountName.text = account.name accountDescription.text = itemView.context.resources.getString( @@ -198,7 +193,6 @@ class AccountAdapter : } fun clear() { - index = -1 id = -1L accountName.text = "" accountDescription.text = "" diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountEditActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountEditActivity.kt index 8992f3c05613def5b0593b9c596980331c7ed0e7..d6500de7580ee08c7e76fa3cf98e01c018bcf756 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountEditActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountEditActivity.kt @@ -1,38 +1,51 @@ package de.kuschku.quasseldroid_ng.ui.setup.accounts import android.app.Activity +import android.content.Context import android.os.Bundle import android.os.Handler import android.os.HandlerThread -import android.support.design.widget.FloatingActionButton +import android.support.design.widget.TextInputEditText +import android.support.design.widget.TextInputLayout import android.support.v7.app.AlertDialog import android.support.v7.app.AppCompatActivity +import android.text.Editable import android.view.Menu import android.view.MenuItem -import android.widget.TextView import butterknife.BindView import butterknife.ButterKnife +import de.kuschku.quasseldroid_ng.Keys import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.persistence.AccountDatabase +import de.kuschku.quasseldroid_ng.util.Patterns +import de.kuschku.quasseldroid_ng.util.TextValidator +import de.kuschku.quasseldroid_ng.util.helper.editCommit class AccountEditActivity : AppCompatActivity() { + @BindView(R.id.nameWrapper) + lateinit var nameWrapper: TextInputLayout @BindView(R.id.name) - lateinit var name: TextView + lateinit var name: TextInputEditText + @BindView(R.id.hostWrapper) + lateinit var hostWrapper: TextInputLayout @BindView(R.id.host) - lateinit var host: TextView + lateinit var host: TextInputEditText + @BindView(R.id.portWrapper) + lateinit var portWrapper: TextInputLayout @BindView(R.id.port) - lateinit var port: TextView + lateinit var port: TextInputEditText + @BindView(R.id.userWrapper) + lateinit var userWrapper: TextInputLayout @BindView(R.id.user) - lateinit var user: TextView + lateinit var user: TextInputEditText + @BindView(R.id.passWrapper) + lateinit var passWrapper: TextInputLayout @BindView(R.id.pass) - lateinit var pass: TextView - - @BindView(R.id.save_button) - lateinit var saveButton: FloatingActionButton + lateinit var pass: TextInputEditText private var accountId: Long = -1 private var account: AccountDatabase.Account? = null @@ -50,22 +63,6 @@ class AccountEditActivity : AppCompatActivity() { setContentView(R.layout.setup_account_edit) ButterKnife.bind(this) - saveButton.setOnClickListener { - val it = account - if (it != null) { - it.name = name.text.toString() - it.host = host.text.toString() - it.port = port.text.toString().toIntOrNull() ?: 4242 - it.user = user.text.toString() - it.pass = pass.text.toString() - handler.post { - database.accounts().save(it) - setResult(Activity.RESULT_OK) - finish() - } - } - } - database = AccountDatabase.Creator.init(this) handler.post { accountId = intent.getLongExtra("account", -1) @@ -79,14 +76,60 @@ class AccountEditActivity : AppCompatActivity() { finish() } - name.text = account?.name - host.text = account?.host - port.text = account?.port?.toString() - user.text = account?.user - pass.text = account?.pass + name.setText(account?.name) + host.setText(account?.host) + port.setText(account?.port?.toString()) + user.setText(account?.user) + pass.setText(account?.pass) + } + + nameValidator = object : TextValidator( + nameWrapper::setError, resources.getString(R.string.hintInvalidName) + ) { + override fun validate(text: Editable) + = text.isNotBlank() + } + + hostValidator = object : TextValidator( + hostWrapper::setError, resources.getString(R.string.hintInvalidHost) + ) { + override fun validate(text: Editable) + = text.toString().matches(Patterns.DOMAIN_NAME.toRegex()) } + + portValidator = object : TextValidator( + portWrapper::setError, resources.getString(R.string.hintInvalidPort) + ) { + override fun validate(text: Editable) + = text.toString().toIntOrNull() in (0 until 65536) + } + + userValidator = object : TextValidator( + userWrapper::setError, resources.getString(R.string.hintInvalidUser) + ) { + override fun validate(text: Editable) + = text.isNotBlank() + } + + name.addTextChangedListener(nameValidator) + host.addTextChangedListener(hostValidator) + port.addTextChangedListener(portValidator) + user.addTextChangedListener(userValidator) + nameValidator.afterTextChanged(name.text) + hostValidator.afterTextChanged(host.text) + portValidator.afterTextChanged(port.text) + userValidator.afterTextChanged(user.text) } + private lateinit var nameValidator: TextValidator + private lateinit var hostValidator: TextValidator + private lateinit var portValidator: TextValidator + private lateinit var userValidator: TextValidator + + private val isValid + get() = nameValidator.isValid && hostValidator.isValid && portValidator.isValid + && userValidator.isValid + override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.setup_edit_account, menu) return super.onCreateOptionsMenu(menu) @@ -106,6 +149,12 @@ class AccountEditActivity : AppCompatActivity() { val it = account if (it != null) handler.post { + val preferences = getSharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE) + if (preferences.getLong(Keys.Status.selectedAccount, -1) == it.id) { + preferences.editCommit { + remove(Keys.Status.selectedAccount) + } + } database.accounts().delete(it) } setResult(Activity.RESULT_OK) @@ -117,6 +166,24 @@ class AccountEditActivity : AppCompatActivity() { .show() true } + R.id.save -> { + if (isValid) { + val it = account + if (it != null) { + it.name = name.text.toString() + it.host = host.text.toString() + it.port = port.text.toString().toIntOrNull() ?: 4242 + it.user = user.text.toString() + it.pass = pass.text.toString() + handler.post { + database.accounts().save(it) + setResult(Activity.RESULT_OK) + finish() + } + } + } + true + } else -> super.onOptionsItemSelected(item) } } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionActivity.kt index 437f31369c789467e875bf6accf1143d183735df..e446387990f08a35c71e00a9ed2ce963abb0db6e 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionActivity.kt @@ -1,41 +1,52 @@ package de.kuschku.quasseldroid_ng.ui.setup.accounts +import android.app.Activity import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.os.Bundle +import de.kuschku.quasseldroid_ng.Keys import de.kuschku.quasseldroid_ng.ui.ChatActivity import de.kuschku.quasseldroid_ng.ui.setup.SetupActivity import de.kuschku.quasseldroid_ng.util.helper.editCommit class AccountSelectionActivity : SetupActivity() { + companion object { + const val REQUEST_CHAT = 0 + const val REQUEST_CREATE_FIRST = 1 + const val REQUEST_CREATE_NEW = 2 + } + override val fragments = listOf( AccountSelectionSlide() ) - lateinit var statusPreferences: SharedPreferences + private lateinit var statusPreferences: SharedPreferences override fun onDone(data: Bundle) { statusPreferences.editCommit { - putLong(selectedAccountKey, data.getLong(selectedAccountKey, -1)) - putBoolean("reconnect", true) + putLong(Keys.Status.selectedAccount, data.getLong(Keys.Status.selectedAccount, -1)) + putBoolean(Keys.Status.reconnect, true) } startActivity(Intent(this, ChatActivity::class.java)) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - statusPreferences = this.getSharedPreferences("status", Context.MODE_PRIVATE) + statusPreferences = this.getSharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE) val data = Bundle() - val selectedAccount = statusPreferences.getLong(selectedAccountKey, -1) - data.putLong(selectedAccountKey, selectedAccount) + val selectedAccount = statusPreferences.getLong(Keys.Status.selectedAccount, -1) + data.putLong(Keys.Status.selectedAccount, selectedAccount) setInitData(data) - if (statusPreferences.getBoolean("reconnect", false) && selectedAccount != -1L) { - startActivity(Intent(this, ChatActivity::class.java)) + if (statusPreferences.getBoolean(Keys.Status.reconnect, false) && selectedAccount != -1L) { + startActivityForResult(Intent(this, ChatActivity::class.java), REQUEST_CHAT) } } - companion object { - private const val selectedAccountKey = "selectedAccount" + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode == REQUEST_CHAT && resultCode == Activity.RESULT_CANCELED) { + finish() + } } } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionSlide.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionSlide.kt index 094c72ba7ab94326d75561ecf6291bf2ac152b17..7e5718e5915255847613721be12a729eb0a498f6 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionSlide.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionSlide.kt @@ -1,5 +1,6 @@ package de.kuschku.quasseldroid_ng.ui.setup.accounts +import android.app.Activity import android.arch.lifecycle.Observer import android.arch.lifecycle.ViewModelProviders import android.arch.paging.PagedList @@ -16,12 +17,14 @@ import butterknife.ButterKnife import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.persistence.AccountDatabase import de.kuschku.quasseldroid_ng.ui.setup.SlideFragment +import de.kuschku.quasseldroid_ng.ui.setup.accounts.AccountSelectionActivity.Companion.REQUEST_CREATE_FIRST +import de.kuschku.quasseldroid_ng.ui.setup.accounts.AccountSelectionActivity.Companion.REQUEST_CREATE_NEW class AccountSelectionSlide : SlideFragment() { @BindView(R.id.account_list) lateinit var accountList: RecyclerView - override fun isValid() = adapter.selectedPos.value ?: -1 != -1 + override fun isValid() = adapter.selectedItemId != -1L override val title = R.string.slideAccountSelectTitle override val description = R.string.slideAccountSelectDescription @@ -45,7 +48,8 @@ class AccountSelectionSlide : SlideFragment() { val firstObserver = object : Observer<PagedList<AccountDatabase.Account>?> { override fun onChanged(t: PagedList<AccountDatabase.Account>?) { if (t?.isEmpty() != false) - startActivityForResult(Intent(context, AccountSetupActivity::class.java), -1) + startActivityForResult(Intent(context, AccountSetupActivity::class.java), + REQUEST_CREATE_FIRST) accountViewmodel.accounts.removeObserver(this) } } @@ -54,19 +58,25 @@ class AccountSelectionSlide : SlideFragment() { accountList.layoutManager = LinearLayoutManager(context) accountList.itemAnimator = DefaultItemAnimator() accountList.adapter = adapter - adapter.selectedPos.observe(this, Observer { - updateValidity() - }) adapter.addAddListener { startActivityForResult(Intent(context, AccountSetupActivity::class.java), -1) } adapter.addEditListener { id -> val intent = Intent(context, AccountEditActivity::class.java) intent.putExtra("account", id) - startActivityForResult(intent, -1) + startActivityForResult(intent, REQUEST_CREATE_NEW) + } + adapter.addSelectionListener { + updateValidity() } return view } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode == REQUEST_CREATE_FIRST && resultCode == Activity.RESULT_CANCELED) { + activity.finish() + } + } } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupConnectionSlide.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupConnectionSlide.kt index dd16c6ddf9c8cef4a4f797695e3c776e69141bff..28d100c6ba0d9dd055b94277c348891d00491b63 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupConnectionSlide.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupConnectionSlide.kt @@ -2,8 +2,8 @@ package de.kuschku.quasseldroid_ng.ui.setup.accounts import android.os.Bundle import android.support.design.widget.TextInputEditText +import android.support.design.widget.TextInputLayout import android.text.Editable -import android.text.TextWatcher import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -11,22 +11,23 @@ import butterknife.BindView import butterknife.ButterKnife import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.ui.setup.SlideFragment +import de.kuschku.quasseldroid_ng.util.Patterns +import de.kuschku.quasseldroid_ng.util.TextValidator class AccountSetupConnectionSlide : SlideFragment() { + @BindView(R.id.hostWrapper) + lateinit var hostWrapper: TextInputLayout @BindView(R.id.host) lateinit var hostField: TextInputEditText + @BindView(R.id.portWrapper) + lateinit var portWrapper: TextInputLayout + @BindView(R.id.port) lateinit var portField: TextInputEditText - private val textWatcher = object : TextWatcher { - override fun afterTextChanged(p0: Editable?) = updateValidity() - override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) = Unit - override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) = Unit - } - override fun isValid(): Boolean { - return validHost() && validPort() + return hostValidator.isValid && portValidator.isValid } override val title = R.string.slideAccountConnectionTitle @@ -49,17 +50,30 @@ class AccountSetupConnectionSlide : SlideFragment() { savedInstanceState: Bundle?): View { val view = inflater.inflate(R.layout.setup_account_connection, container, false) ButterKnife.bind(this, view) - hostField.addTextChangedListener(textWatcher) - portField.addTextChangedListener(textWatcher) - return view - } + hostValidator = object : TextValidator( + hostWrapper::setError, resources.getString(R.string.hintInvalidHost) + ) { + override fun validate(text: Editable) + = text.toString().matches(Patterns.DOMAIN_NAME.toRegex()) + + override fun onChanged() = updateValidity() + } + portValidator = object : TextValidator( + portWrapper::setError, resources.getString(R.string.hintInvalidPort) + ) { + override fun validate(text: Editable) + = text.toString().toIntOrNull() in (0 until 65536) - override fun onDestroyView() { - hostField.removeTextChangedListener(textWatcher) - portField.removeTextChangedListener(textWatcher) - super.onDestroyView() + override fun onChanged() = updateValidity() + } + + hostField.addTextChangedListener(hostValidator) + portField.addTextChangedListener(portValidator) + hostValidator.afterTextChanged(hostField.text) + portValidator.afterTextChanged(portField.text) + return view } - private fun validHost() = hostField.text.isNotEmpty() - private fun validPort() = (0 until 65536).contains(portField.text.toString().toIntOrNull()) + private lateinit var hostValidator: TextValidator + private lateinit var portValidator: TextValidator } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupNameSlide.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupNameSlide.kt index 026752a2ed6cbc4b59f4f1c078a5f0cdb38d491a..33d68251b894a488c1187498adb7c24f236ce989 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupNameSlide.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupNameSlide.kt @@ -2,8 +2,8 @@ package de.kuschku.quasseldroid_ng.ui.setup.accounts import android.os.Bundle import android.support.design.widget.TextInputEditText +import android.support.design.widget.TextInputLayout import android.text.Editable -import android.text.TextWatcher import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -11,19 +11,16 @@ import butterknife.BindView import butterknife.ButterKnife import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.ui.setup.SlideFragment +import de.kuschku.quasseldroid_ng.util.TextValidator class AccountSetupNameSlide : SlideFragment() { + @BindView(R.id.nameWrapper) + lateinit var nameWrapper: TextInputLayout @BindView(R.id.name) lateinit var nameField: TextInputEditText - private val textWatcher = object : TextWatcher { - override fun afterTextChanged(p0: Editable?) = updateValidity() - override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) = Unit - override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) = Unit - } - override fun isValid(): Boolean { - return validName() + return nameValidator.isValid } override val title = R.string.slideAccountNameTitle @@ -43,14 +40,18 @@ class AccountSetupNameSlide : SlideFragment() { savedInstanceState: Bundle?): View { val view = inflater.inflate(R.layout.setup_account_name, container, false) ButterKnife.bind(this, view) - nameField.addTextChangedListener(textWatcher) + nameValidator = object : TextValidator( + nameWrapper::setError, resources.getString(R.string.hintInvalidName) + ) { + override fun validate(text: Editable) + = text.isNotBlank() + + override fun onChanged() = updateValidity() + } + nameField.addTextChangedListener(nameValidator) + nameValidator.afterTextChanged(nameField.text) return view } - override fun onDestroyView() { - nameField.removeTextChangedListener(textWatcher) - super.onDestroyView() - } - - private fun validName() = nameField.text.isNotEmpty() + private lateinit var nameValidator: TextValidator } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupUserSlide.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupUserSlide.kt index 34559a4aff9cb2044da6e86743942d7bd3764d98..8e10124b96db17c43e93133ed54c5bd5013a3fa2 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupUserSlide.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSetupUserSlide.kt @@ -2,8 +2,8 @@ package de.kuschku.quasseldroid_ng.ui.setup.accounts import android.os.Bundle import android.support.design.widget.TextInputEditText +import android.support.design.widget.TextInputLayout import android.text.Editable -import android.text.TextWatcher import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -11,22 +11,21 @@ import butterknife.BindView import butterknife.ButterKnife import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.ui.setup.SlideFragment +import de.kuschku.quasseldroid_ng.util.TextValidator class AccountSetupUserSlide : SlideFragment() { + @BindView(R.id.userWrapper) + lateinit var userWrapper: TextInputLayout @BindView(R.id.user) lateinit var userField: TextInputEditText + @BindView(R.id.passWrapper) + lateinit var passWrapper: TextInputLayout @BindView(R.id.pass) lateinit var passField: TextInputEditText - private val textWatcher = object : TextWatcher { - override fun afterTextChanged(p0: Editable?) = updateValidity() - override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) = Unit - override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) = Unit - } - override fun isValid(): Boolean { - return validUser() && validPass() + return true } override val title = R.string.slideAccountUserTitle @@ -48,17 +47,18 @@ class AccountSetupUserSlide : SlideFragment() { savedInstanceState: Bundle?): View { val view = inflater.inflate(R.layout.setup_account_user, container, false) ButterKnife.bind(this, view) - userField.addTextChangedListener(textWatcher) - passField.addTextChangedListener(textWatcher) - return view - } + userValidator = object : TextValidator( + userWrapper::setError, resources.getString(R.string.hintInvalidUser) + ) { + override fun validate(text: Editable) + = text.isNotBlank() - override fun onDestroyView() { - userField.removeTextChangedListener(textWatcher) - passField.removeTextChangedListener(textWatcher) - super.onDestroyView() + override fun onChanged() = updateValidity() + } + userField.addTextChangedListener(userValidator) + userValidator.afterTextChanged(userField.text) + return view } - private fun validUser() = userField.text.isNotEmpty() - private fun validPass() = passField.text.isNotEmpty() + private lateinit var userValidator: TextValidator } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/Patterns.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/Patterns.kt new file mode 100644 index 0000000000000000000000000000000000000000..c4e55304c2128dc3179a0b142d49356b7e1f2330 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/Patterns.kt @@ -0,0 +1,64 @@ +package de.kuschku.quasseldroid_ng.util + +import java.util.regex.Pattern + +@SuppressWarnings("Access") +object Patterns { + const val IPv4 + = "(?:(?:[0-1]?[0-9]?[0-9]|2[0-5][0-5])\\.){3}(?:[0-1]?[0-9]?[0-9]|2[0-5][0-5])" + + const val IPv6 + = "(?:(?:(?:[0-9a-fA-F]{1,4}:){7}(?:[0-9a-fA-F]{1,4}))|(?:(?:(?:[0-9a-fA-F]{1,4}:){1,7}|:):(?:[0-9a-fA-F]{1,4}))|(?:(?:(?:[0-9a-fA-F]{1,4}:){1,6}|:):(?:[0-9a-fA-F]{1,4}:){0,1}(?:[0-9a-fA-F]{1,4}))|(?:(?:(?:[0-9a-fA-F]{1,4}:){1,5}|:):(?:[0-9a-fA-F]{1,4}:){0,2}(?:[0-9a-fA-F]{1,4}))|(?:(?:(?:[0-9a-fA-F]{1,4}:){1,4}|:):(?:[0-9a-fA-F]{1,4}:){0,3}(?:[0-9a-fA-F]{1,4}))|(?:(?:(?:[0-9a-fA-F]{1,4}:){1,3}|:):(?:[0-9a-fA-F]{1,4}:){1,4}(?:[0-9a-fA-F]{1,4}))|(?:(?:(?:[0-9a-fA-F]{1,4}:){1,2}|:):(?:[0-9a-fA-F]{1,4}:){0,5}(?:[0-9a-fA-F]{1,4}))|(?:(?:(?:[0-9a-fA-F]{1,4}:)|:):(?:[0-9a-fA-F]{1,4}:){0,6}(?:[0-9a-fA-F]{1,4})))" + + const val IP_ADDRESS_STRING = "(?:" + IPv4 + "|" + IPv6 + ")" + + const val UCS_CHAR = "[" + + "\u00A0-\uD7FF" + + "\uF900-\uFDCF" + + "\uFDF0-\uFFEF" + + "\uD800\uDC00-\uD83F\uDFFD" + + "\uD840\uDC00-\uD87F\uDFFD" + + "\uD880\uDC00-\uD8BF\uDFFD" + + "\uD8C0\uDC00-\uD8FF\uDFFD" + + "\uD900\uDC00-\uD93F\uDFFD" + + "\uD940\uDC00-\uD97F\uDFFD" + + "\uD980\uDC00-\uD9BF\uDFFD" + + "\uD9C0\uDC00-\uD9FF\uDFFD" + + "\uDA00\uDC00-\uDA3F\uDFFD" + + "\uDA40\uDC00-\uDA7F\uDFFD" + + "\uDA80\uDC00-\uDABF\uDFFD" + + "\uDAC0\uDC00-\uDAFF\uDFFD" + + "\uDB00\uDC00-\uDB3F\uDFFD" + + "\uDB44\uDC00-\uDB7F\uDFFD" + + "&&[^\u00A0[\u2000-\u200A]\u2028\u2029\u202F\u3000]" + + "]" + /** + * Valid characters for IRI label defined in RFC 3987. + */ + const val LABEL_CHAR + = "a-zA-Z0-9" + UCS_CHAR + /** + * Valid characters for IRI TLD defined in RFC 3987. + */ + const val TLD_CHAR + = "a-zA-Z" + UCS_CHAR + /** + * RFC 1035 Section 2.3.4 limits the labels to a maximum 63 octets. + */ + const val IRI_LABEL + = "[" + LABEL_CHAR + "](?:[" + LABEL_CHAR + "_\\-]{0,61}[" + LABEL_CHAR + "]){0,1}" + /** + * RFC 3492 references RFC 1034 and limits Punycode algorithm output to 63 characters. + */ + const val PUNYCODE_TLD + = "xn\\-\\-[\\w\\-]{0,58}\\w" + const val TLD + = "(?:" + PUNYCODE_TLD + "|" + "[" + TLD_CHAR + "]{2,63}" + ")" + const val HOST_NAME + = "(?:" + IRI_LABEL + "\\.)+" + TLD + ".?" + const val LOCAL_HOST_NAME + = "(?:" + IRI_LABEL + "\\.)*" + IRI_LABEL + const val DOMAIN_NAME_STR + = "(?:" + LOCAL_HOST_NAME + "|" + HOST_NAME + "|" + IP_ADDRESS_STRING + ")" + val DOMAIN_NAME = Pattern.compile(DOMAIN_NAME_STR) +} diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/TextValidator.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/TextValidator.kt new file mode 100644 index 0000000000000000000000000000000000000000..777fe5c2c0bec9185c4c180427ec0b847aaafb72 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/TextValidator.kt @@ -0,0 +1,22 @@ +package de.kuschku.quasseldroid_ng.util + +import android.text.Editable +import android.text.TextWatcher + +abstract class TextValidator(private val errorListener: (String?) -> Unit, + private val error: String) : TextWatcher { + override fun afterTextChanged(p0: Editable) { + isValid = validate(p0) + errorListener(if (isValid) null else error) + onChanged() + } + + protected open fun onChanged() = Unit + + override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) = Unit + override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) = Unit + + abstract fun validate(text: Editable): Boolean + var isValid = false + private set +} diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/AndroidCompatibilityUtils.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/compatibility/AndroidCompatibilityUtils.kt similarity index 95% rename from app/src/main/java/de/kuschku/quasseldroid_ng/util/AndroidCompatibilityUtils.kt rename to app/src/main/java/de/kuschku/quasseldroid_ng/util/compatibility/AndroidCompatibilityUtils.kt index aee755bc8bfa2722a2988c681d883136b8a312d3..15ab06a453c4bfbef0f944314c8318ea6dc97f1d 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/AndroidCompatibilityUtils.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/compatibility/AndroidCompatibilityUtils.kt @@ -1,4 +1,4 @@ -package de.kuschku.quasseldroid_ng.util +package de.kuschku.quasseldroid_ng.util.compatibility import android.os.Build import de.kuschku.libquassel.util.compatibility.CompatibilityUtils diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/AndroidHandlerService.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/compatibility/AndroidHandlerService.kt similarity index 96% rename from app/src/main/java/de/kuschku/quasseldroid_ng/util/AndroidHandlerService.kt rename to app/src/main/java/de/kuschku/quasseldroid_ng/util/compatibility/AndroidHandlerService.kt index c7278f3aa0b7ec588a471c9714280f645b7f349a..d1d341a097f5969863668e37f53b72b81a56aed2 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/AndroidHandlerService.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/compatibility/AndroidHandlerService.kt @@ -1,4 +1,4 @@ -package de.kuschku.quasseldroid_ng.util +package de.kuschku.quasseldroid_ng.util.compatibility import android.os.Handler import android.os.HandlerThread diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/AndroidLoggingHandler.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/compatibility/AndroidLoggingHandler.kt similarity index 80% rename from app/src/main/java/de/kuschku/quasseldroid_ng/util/AndroidLoggingHandler.kt rename to app/src/main/java/de/kuschku/quasseldroid_ng/util/compatibility/AndroidLoggingHandler.kt index bd2ebaf1d968a758434f00a3cc151c54019faa6b..564df9023dfd882dff907a8b3d916d0a6561fd51 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/AndroidLoggingHandler.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/compatibility/AndroidLoggingHandler.kt @@ -1,15 +1,18 @@ -package de.kuschku.quasseldroid_ng.util +package de.kuschku.quasseldroid_ng.util.compatibility import android.util.Log import de.kuschku.libquassel.util.compatibility.LoggingHandler object AndroidLoggingHandler : LoggingHandler() { override fun isLoggable(logLevel: LogLevel, tag: String): Boolean { - return Log.isLoggable(tag, priority(logLevel)) + return Log.isLoggable(tag, + priority( + logLevel)) } override fun log(logLevel: LogLevel, tag: String, message: String?, throwable: Throwable?) { - val priority = priority(logLevel) + val priority = priority( + logLevel) if (message != null) Log.println(priority, tag, message) if (throwable != null) diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/AndroidStreamChannelFactory.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/compatibility/AndroidStreamChannelFactory.kt similarity index 92% rename from app/src/main/java/de/kuschku/quasseldroid_ng/util/AndroidStreamChannelFactory.kt rename to app/src/main/java/de/kuschku/quasseldroid_ng/util/compatibility/AndroidStreamChannelFactory.kt index 40ca744039284346a8bf9f291aa621cc16788ae7..7b4a2a987d2e18bbb4491709a6a4f5967537cb61 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/AndroidStreamChannelFactory.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/compatibility/AndroidStreamChannelFactory.kt @@ -1,4 +1,4 @@ -package de.kuschku.quasseldroid_ng.util +package de.kuschku.quasseldroid_ng.util.compatibility import de.kuschku.libquassel.util.compatibility.StreamChannelFactory import de.kuschku.quasseldroid_ng.util.backport.ReadableStreamChannel diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index ae8a6ba048986eed4cc4c8c042305ac3df70d9e7..2f767fd2961521820dace4d4923f0e5cb659a46a 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -22,7 +22,7 @@ android:layout_height="wrap_content" android:minWidth="88dp" android:text="Connect" - android:theme="@style/RaisedThemedButton" /> + android:theme="@style/AppTheme.Button.Colored" /> <Button android:id="@+id/disconnect" @@ -30,7 +30,7 @@ android:layout_height="wrap_content" android:minWidth="88dp" android:text="Disconnect" - android:theme="@style/RaisedButton" /> + android:theme="@style/AppTheme.Button" /> <Button android:id="@+id/clear" @@ -38,7 +38,7 @@ android:layout_height="wrap_content" android:minWidth="88dp" android:text="Clear" - android:theme="@style/RaisedButton" /> + android:theme="@style/AppTheme.Button" /> </LinearLayout> <TextView diff --git a/app/src/main/res/layout/activity_setup.xml b/app/src/main/res/layout/activity_setup.xml index 8a58d8776dcbae78505cb4839dd4f37e0333af86..4ded7a4411572ad7e256fe251394a05512fe7094 100644 --- a/app/src/main/res/layout/activity_setup.xml +++ b/app/src/main/res/layout/activity_setup.xml @@ -16,9 +16,6 @@ android:layout_marginBottom="16dp" android:layout_marginRight="16dp" android:tint="#ffffff" - app:backgroundTint="#8A808080" - app:elevation="0dip" - app:fabSize="normal" - app:pressedTranslationZ="0dip" /> + app:fabSize="normal" /> </merge> diff --git a/app/src/main/res/layout/setup_account_connection.xml b/app/src/main/res/layout/setup_account_connection.xml index 83ae786732a750565a92bc02352979cfda33525e..86e7e13b417ba37d69bbb50e27b441ab8c8dc0d4 100644 --- a/app/src/main/res/layout/setup_account_connection.xml +++ b/app/src/main/res/layout/setup_account_connection.xml @@ -27,9 +27,10 @@ android:padding="32dp"> <android.support.design.widget.TextInputLayout + android:id="@+id/hostWrapper" android:layout_width="match_parent" android:layout_height="wrap_content" - android:hint="@string/labelConnectionHostname" + android:hint="@string/labelConnectionHost" app:errorEnabled="true"> <android.support.design.widget.TextInputEditText @@ -40,6 +41,7 @@ </android.support.design.widget.TextInputLayout> <android.support.design.widget.TextInputLayout + android:id="@+id/portWrapper" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/labelConnectionPort" diff --git a/app/src/main/res/layout/setup_account_edit.xml b/app/src/main/res/layout/setup_account_edit.xml index 93e4873d7c22e80da55a827f432f0544a4e59196..c00a9e81d82326e88714b39e447cb309aa5dc886 100644 --- a/app/src/main/res/layout/setup_account_edit.xml +++ b/app/src/main/res/layout/setup_account_edit.xml @@ -24,7 +24,7 @@ <FrameLayout android:layout_width="56dp" - android:layout_height="match_parent"> + android:layout_height="68dp"> <ImageView android:layout_width="24dp" @@ -35,6 +35,7 @@ </FrameLayout> <android.support.design.widget.TextInputLayout + android:id="@+id/nameWrapper" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/labelAccountName" @@ -60,7 +61,7 @@ <FrameLayout android:layout_width="56dp" - android:layout_height="match_parent"> + android:layout_height="68dp"> <ImageView android:layout_width="24dp" @@ -70,45 +71,44 @@ app:srcCompat="@drawable/ic_server_network" /> </FrameLayout> - <android.support.design.widget.TextInputLayout + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:hint="@string/labelConnectionHostname" - app:errorEnabled="true"> + android:orientation="vertical"> - <android.support.design.widget.TextInputEditText - android:id="@+id/host" + <android.support.design.widget.TextInputLayout + android:id="@+id/hostWrapper" android:layout_width="match_parent" android:layout_height="wrap_content" - android:inputType="textUri" /> - </android.support.design.widget.TextInputLayout> - </LinearLayout> - - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingLeft="16dp" - android:paddingRight="16dp"> - - <android.support.v4.widget.Space - android:layout_width="56dp" - android:layout_height="match_parent" /> - - <android.support.design.widget.TextInputLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:hint="@string/labelConnectionPort" - app:errorEnabled="true"> - - <android.support.design.widget.TextInputEditText - android:id="@+id/port" + android:hint="@string/labelConnectionHost" + app:errorEnabled="true"> + + <android.support.design.widget.TextInputEditText + android:id="@+id/host" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textUri" /> + </android.support.design.widget.TextInputLayout> + + <android.support.v4.widget.Space + android:layout_width="56dp" + android:layout_height="match_parent" /> + + <android.support.design.widget.TextInputLayout + android:id="@+id/portWrapper" android:layout_width="match_parent" android:layout_height="wrap_content" - android:inputType="number" - android:numeric="integer" - android:text="@string/defaultConnectionPort" /> - </android.support.design.widget.TextInputLayout> + android:hint="@string/labelConnectionPort" + app:errorEnabled="true"> + + <android.support.design.widget.TextInputEditText + android:id="@+id/port" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="number" + android:text="@string/defaultConnectionPort" /> + </android.support.design.widget.TextInputLayout> + </LinearLayout> </LinearLayout> <android.support.v4.widget.Space @@ -124,7 +124,7 @@ <FrameLayout android:layout_width="56dp" - android:layout_height="match_parent"> + android:layout_height="68dp"> <ImageView android:layout_width="24dp" @@ -134,44 +134,40 @@ app:srcCompat="@drawable/ic_account" /> </FrameLayout> - <android.support.design.widget.TextInputLayout + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:hint="@string/labelAccountUsername"> + android:orientation="vertical"> - <android.support.design.widget.TextInputEditText - android:id="@+id/user" + <android.support.design.widget.TextInputLayout + android:id="@+id/userWrapper" android:layout_width="match_parent" android:layout_height="wrap_content" - android:inputType="textVisiblePassword|textNoSuggestions" - app:errorEnabled="true" /> - </android.support.design.widget.TextInputLayout> - </LinearLayout> - - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingLeft="16dp" - android:paddingRight="16dp"> - - <android.support.v4.widget.Space - android:layout_width="56dp" - android:layout_height="match_parent" /> - - <android.support.design.widget.TextInputLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:hint="@string/labelAccountPassword"> - - <android.support.design.widget.TextInputEditText - android:id="@+id/pass" + android:hint="@string/labelAccountUser"> + + <android.support.design.widget.TextInputEditText + android:id="@+id/user" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textVisiblePassword|textNoSuggestions" + app:errorEnabled="true" /> + </android.support.design.widget.TextInputLayout> + + <android.support.design.widget.TextInputLayout + android:id="@+id/passWrapper" android:layout_width="match_parent" android:layout_height="wrap_content" - android:inputType="textPassword" - android:password="true" - app:errorEnabled="true" /> - </android.support.design.widget.TextInputLayout> + android:hint="@string/labelAccountPass"> + + <android.support.design.widget.TextInputEditText + android:id="@+id/pass" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textPassword" + app:errorEnabled="true" /> + </android.support.design.widget.TextInputLayout> + + </LinearLayout> </LinearLayout> <android.support.v4.widget.Space @@ -179,16 +175,4 @@ android:layout_height="16dp" /> </LinearLayout> </android.support.v4.widget.NestedScrollView> - - <android.support.design.widget.FloatingActionButton - android:id="@+id/save_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="end|bottom" - android:layout_marginBottom="16dp" - android:layout_marginEnd="16dp" - android:layout_marginRight="16dp" - android:tint="#ffffff" - app:fabSize="normal" - app:srcCompat="@drawable/ic_save" /> </FrameLayout> diff --git a/app/src/main/res/layout/setup_account_name.xml b/app/src/main/res/layout/setup_account_name.xml index 46c48f9a2ba1e8def14717663244f46bb182d6fa..591099361b76cb3175f4103e944b9ce1295ed465 100644 --- a/app/src/main/res/layout/setup_account_name.xml +++ b/app/src/main/res/layout/setup_account_name.xml @@ -6,6 +6,7 @@ android:padding="32dp"> <android.support.design.widget.TextInputLayout + android:id="@+id/nameWrapper" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/labelAccountName" diff --git a/app/src/main/res/layout/setup_account_user.xml b/app/src/main/res/layout/setup_account_user.xml index 6bf604941d1892966315946e7f6bd25f6856c079..4468f2c97765ce2638a7e3fa30af1144cd47ea9a 100644 --- a/app/src/main/res/layout/setup_account_user.xml +++ b/app/src/main/res/layout/setup_account_user.xml @@ -6,9 +6,10 @@ android:padding="32dp"> <android.support.design.widget.TextInputLayout + android:id="@+id/userWrapper" android:layout_width="match_parent" android:layout_height="wrap_content" - android:hint="@string/labelAccountUsername"> + android:hint="@string/labelAccountUser"> <android.support.design.widget.TextInputEditText android:id="@+id/user" @@ -19,9 +20,10 @@ </android.support.design.widget.TextInputLayout> <android.support.design.widget.TextInputLayout + android:id="@+id/passWrapper" android:layout_width="match_parent" android:layout_height="wrap_content" - android:hint="@string/labelAccountPassword"> + android:hint="@string/labelAccountPass"> <android.support.design.widget.TextInputEditText android:id="@+id/pass" diff --git a/app/src/main/res/layout/setup_slide.xml b/app/src/main/res/layout/setup_slide.xml index 679e33f2c132825a3df6cc4234940736914dbfe2..c31e16eb498f638f03124036a0f42cf916462e1a 100644 --- a/app/src/main/res/layout/setup_slide.xml +++ b/app/src/main/res/layout/setup_slide.xml @@ -58,9 +58,14 @@ </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> - <FrameLayout - android:id="@+id/content_host" + <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="wrap_content" - app:layout_behavior="@string/appbar_scrolling_view_behavior" /> + app:layout_behavior="@string/appbar_scrolling_view_behavior"> + + <FrameLayout + android:id="@+id/content_host" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + </android.support.v4.widget.NestedScrollView> </android.support.design.widget.CoordinatorLayout> diff --git a/app/src/main/res/menu/setup_edit_account.xml b/app/src/main/res/menu/setup_edit_account.xml index fa05be637b1ba55ba1d0d373ba052751012c1517..6c17b904723bf3b1f437c102241f475f5c383e5c 100644 --- a/app/src/main/res/menu/setup_edit_account.xml +++ b/app/src/main/res/menu/setup_edit_account.xml @@ -1,10 +1,14 @@ <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> + <item + android:id="@+id/save" + android:title="Save" + app:showAsAction="ifRoom" /> <item android:id="@+id/delete" android:icon="@drawable/ic_delete" android:title="Delete" app:iconTint="#fff" - app:showAsAction="ifRoom" /> + app:showAsAction="never" /> </menu> diff --git a/app/src/main/res/values/strings_setup.xml b/app/src/main/res/values/strings_setup.xml index 87287e6691373862b5d454315fae6e34a74d940a..2a5608326795f5ee2d13d339f7050e804ee80fdf 100644 --- a/app/src/main/res/values/strings_setup.xml +++ b/app/src/main/res/values/strings_setup.xml @@ -8,15 +8,20 @@ <string name="slideAccountConnectionTitle">Connection</string> <string name="slideAccountConnectionDescription">First, please choose which server your core is hosted on.</string> - <string name="labelConnectionHostname">Host</string> + <string name="labelConnectionHost">Host</string> <string name="labelConnectionPort">Port</string> + <string name="hintInvalidHost">Not a valid hostname</string> + <string name="hintInvalidPort">Not a valid port</string> + <!-- Account User --> <string name="slideAccountUserTitle">Your Account</string> <string name="slideAccountUserDescription">Now, please enter the username and password for your account on the core. If you just created that core, we’ll set up this account for you</string> - <string name="labelAccountUsername">Username</string> - <string name="labelAccountPassword">Password</string> + <string name="labelAccountUser">Username</string> + <string name="labelAccountPass">Password</string> + + <string name="hintInvalidUser">Username can not be empty</string> <!-- Account Name --> <string name="slideAccountNameTitle">Customize Account</string> @@ -24,6 +29,8 @@ <string name="labelAccountName">Account name</string> + <string name="hintInvalidName">Name can not be empty</string> + <!-- Core Authenticator Select --> <string name="slideCoreAuthenticatorSelectTitle">Select Authentication Backend</string> <string name="slideCoreAuthenticatorSelectDescription">Please select an authentication backend for the Quassel Core to use for authenticating users.</string> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 965fd74373686ac931d9d47642cd35b411ad2224..97b0c5a58e61c285190fc0c545f0661127837ce3 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -14,11 +14,12 @@ <style name="SetupTheme" parent="AppTheme.NoActionBar" /> - <style name="RaisedButton" parent="AppTheme"> - <item name="colorButtonNormal">?attr/background</item> + <style name="AppTheme.Button" parent="AppTheme"> + <item name="colorButtonNormal">@android:color/background_light</item> </style> - <style name="RaisedThemedButton" parent="RaisedButton"> + <style name="AppTheme.Button.Colored" parent="AppTheme.Button"> + <item name="android:textColor">?android:attr/textColorPrimaryInverse</item> <item name="colorButtonNormal">@color/colorAccent</item> </style>