Skip to content
Snippets Groups Projects
Commit ea8f96b9 authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

Fixed problems with setup UI scrolling and validation

parent 6c9f7889
Branches
Tags
No related merge requests found
Showing
with 371 additions and 169 deletions
...@@ -79,6 +79,8 @@ dependencies { ...@@ -79,6 +79,8 @@ dependencies {
implementation(appCompat("recyclerview-v7")) implementation(appCompat("recyclerview-v7"))
implementation("com.android.support.constraint:constraint-layout:1.0.2") 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("io.reactivex.rxjava2:rxjava:2.1.3")
implementation(appArch("lifecycle", "runtime", version = "1.0.0")) implementation(appArch("lifecycle", "runtime", version = "1.0.0"))
......
package de.kuschku.quasseldroid_ng
object Keys {
object Status {
const val NAME = "status"
const val selectedAccount = "selectedAccount"
const val reconnect = "reconnect"
}
}
...@@ -6,9 +6,9 @@ import android.content.pm.ShortcutInfo ...@@ -6,9 +6,9 @@ import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon import android.graphics.drawable.Icon
import android.os.Build import android.os.Build
import de.kuschku.quasseldroid_ng.util.AndroidCompatibilityUtils import de.kuschku.quasseldroid_ng.util.compatibility.AndroidCompatibilityUtils
import de.kuschku.quasseldroid_ng.util.AndroidLoggingHandler import de.kuschku.quasseldroid_ng.util.compatibility.AndroidLoggingHandler
import de.kuschku.quasseldroid_ng.util.AndroidStreamChannelFactory import de.kuschku.quasseldroid_ng.util.compatibility.AndroidStreamChannelFactory
import de.kuschku.quasseldroid_ng.util.helper.systemService import de.kuschku.quasseldroid_ng.util.helper.systemService
import org.acra.ACRA import org.acra.ACRA
import org.acra.ReportingInteractionMode import org.acra.ReportingInteractionMode
......
...@@ -13,7 +13,7 @@ import de.kuschku.libquassel.session.SocketAddress ...@@ -13,7 +13,7 @@ import de.kuschku.libquassel.session.SocketAddress
import de.kuschku.quasseldroid_ng.BuildConfig import de.kuschku.quasseldroid_ng.BuildConfig
import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.R
import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase 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 org.threeten.bp.Instant
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import javax.net.ssl.X509TrustManager import javax.net.ssl.X509TrustManager
......
...@@ -20,6 +20,7 @@ import de.kuschku.libquassel.session.Backend ...@@ -20,6 +20,7 @@ import de.kuschku.libquassel.session.Backend
import de.kuschku.libquassel.session.ConnectionState import de.kuschku.libquassel.session.ConnectionState
import de.kuschku.libquassel.session.SocketAddress import de.kuschku.libquassel.session.SocketAddress
import de.kuschku.libquassel.util.compatibility.LoggingHandler import de.kuschku.libquassel.util.compatibility.LoggingHandler
import de.kuschku.quasseldroid_ng.Keys
import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.R
import de.kuschku.quasseldroid_ng.persistence.AccountDatabase import de.kuschku.quasseldroid_ng.persistence.AccountDatabase
import de.kuschku.quasseldroid_ng.service.QuasselService import de.kuschku.quasseldroid_ng.service.QuasselService
...@@ -97,15 +98,15 @@ class ChatActivity : ServiceBoundActivity() { ...@@ -97,15 +98,15 @@ class ChatActivity : ServiceBoundActivity() {
val database = AccountDatabase.Creator.init(this) val database = AccountDatabase.Creator.init(this)
handler.post { handler.post {
val accountId = getSharedPreferences("status", Context.MODE_PRIVATE) val accountId = getSharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE)
?.getLong(selectedAccountKey, -1) ?: -1 ?.getLong(Keys.Status.selectedAccount, -1) ?: -1
if (accountId == -1L) { if (accountId == -1L) {
setResult(Activity.RESULT_CANCELED) setResult(Activity.RESULT_OK)
finish() finish()
} }
val it = database.accounts().findById(accountId) val it = database.accounts().findById(accountId)
if (it == null) { if (it == null) {
setResult(Activity.RESULT_CANCELED) setResult(Activity.RESULT_OK)
finish() finish()
} }
account = it account = it
...@@ -149,8 +150,8 @@ class ChatActivity : ServiceBoundActivity() { ...@@ -149,8 +150,8 @@ class ChatActivity : ServiceBoundActivity() {
override fun onOptionsItemSelected(item: MenuItem?) = when (item?.itemId) { override fun onOptionsItemSelected(item: MenuItem?) = when (item?.itemId) {
R.id.disconnect -> { R.id.disconnect -> {
getSharedPreferences("status", Context.MODE_PRIVATE).editApply { getSharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE).editApply {
putBoolean("reconnect", false) putBoolean(Keys.Status.reconnect, false)
} }
finish() finish()
true true
...@@ -167,8 +168,4 @@ class ChatActivity : ServiceBoundActivity() { ...@@ -167,8 +168,4 @@ class ChatActivity : ServiceBoundActivity() {
LoggingHandler.loggingHandlers.remove(logHandler) LoggingHandler.loggingHandlers.remove(logHandler)
super.onStop() super.onStop()
} }
companion object {
private const val selectedAccountKey = "selectedAccount"
}
} }
...@@ -176,6 +176,13 @@ abstract class SetupActivity : AppCompatActivity() { ...@@ -176,6 +176,13 @@ abstract class SetupActivity : AppCompatActivity() {
} }
} }
override fun onBackPressed() {
if (viewPager.currentItem == 0)
super.onBackPressed()
else
viewPager.currentItem -= 1
}
companion object { companion object {
private const val currentItemKey = ":setupActivity:currentItem" private const val currentItemKey = ":setupActivity:currentItem"
private const val lastValidItemKey = ":setupActivity:lastValidItem" private const val lastValidItemKey = ":setupActivity:lastValidItem"
......
package de.kuschku.quasseldroid_ng.ui.setup.accounts package de.kuschku.quasseldroid_ng.ui.setup.accounts
import android.arch.lifecycle.MutableLiveData
import android.arch.paging.PagedListAdapter import android.arch.paging.PagedListAdapter
import android.support.v7.recyclerview.extensions.DiffCallback import android.support.v7.recyclerview.extensions.DiffCallback
import android.support.v7.widget.AppCompatImageButton import android.support.v7.widget.AppCompatImageButton
...@@ -19,18 +18,12 @@ class AccountAdapter : ...@@ -19,18 +18,12 @@ class AccountAdapter :
PagedListAdapter<AccountDatabase.Account, AccountAdapter.AccountViewHolder>(DIFF_CALLBACK) { PagedListAdapter<AccountDatabase.Account, AccountAdapter.AccountViewHolder>(DIFF_CALLBACK) {
private val actionListeners = mutableSetOf<(Long) -> Unit>() private val actionListeners = mutableSetOf<(Long) -> Unit>()
private val addListeners = mutableSetOf<() -> Unit>() private val addListeners = mutableSetOf<() -> Unit>()
val selectedItemId: Long private val selectionListeners = mutableSetOf<(Long) -> Unit>()
get() {
val position = selectedPos.value
return if (position != null && position > -1 && position < super.getItemCount())
getItem(position)?.id ?: -1
else
-1
}
private val clickListener = object : ItemListener { private val clickListener = object : ItemListener {
override fun onAction(id: Long, pos: Int) { override fun onAction(id: Long, pos: Int) {
changeSelection(id, pos) notifySelectionChanged(selectedItemView, pos)
selectionListener.invoke(id)
} }
} }
...@@ -42,6 +35,17 @@ class AccountAdapter : ...@@ -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 = { private val addListener = {
for (addListener in addListeners) { for (addListener in addListeners) {
addListener.invoke() addListener.invoke()
...@@ -64,6 +68,14 @@ class AccountAdapter : ...@@ -64,6 +68,14 @@ class AccountAdapter :
addListeners.remove(f) 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) { override fun onBindViewHolder(holder: AccountViewHolder, position: Int) {
when (holder) { when (holder) {
is AccountViewHolder.Item -> { is AccountViewHolder.Item -> {
...@@ -71,12 +83,11 @@ class AccountAdapter : ...@@ -71,12 +83,11 @@ class AccountAdapter :
if (account == null) { if (account == null) {
holder.clear() holder.clear()
} else { } else {
val selected = selectedId == account.id val selected = account.id == selectedItemId
holder.bind(account, position, selected) if (selected) {
if (selected && position != selectedPos.value) selectedItemView = position
holder.itemView.post {
changeSelection(account.id, position)
} }
holder.bind(account, selected)
} }
} }
is AccountViewHolder.Add -> { is AccountViewHolder.Add -> {
...@@ -123,37 +134,23 @@ class AccountAdapter : ...@@ -123,37 +134,23 @@ class AccountAdapter :
} }
} }
private fun notifySelectionChanged(from: Int?, to: Int?) { fun selectAccount(id: Long) {
if (from != null && from != -1) selectedItemView = -1
notifyItemChanged(from) selectionListener(id)
val real_to = to ?: -1
selectedPos.value = real_to
if (to != null && to != -1)
notifyItemChanged(to)
} }
fun changeSelection(id: Long, position: Int) { fun notifySelectionChanged(from: Int?, to: Int?) {
notifySelectionChanged(selectedPos.value, position) val _from = from ?: -1
selectedId = id val _to = to ?: -1
}
private fun indexOf(id: Long) = (0 until itemCount).lastOrNull { if (_from != -1)
getItemViewType(it) == TYPE_ACCOUNT && getItem(it)?.id == id notifyItemChanged(_from)
} ?: -1
fun selectAccount(id: Long) { selectedItemView = _to
val index = indexOf(id)
if (index != -1) {
changeSelection(id, index)
} else {
selectedId = id
}
}
private var selectedId = -1L if (_to != -1)
var selectedPos = MutableLiveData<Int>() notifyItemChanged(_to)
}
interface ItemListener { interface ItemListener {
fun onAction(id: Long, pos: Int) fun onAction(id: Long, pos: Int)
...@@ -175,20 +172,18 @@ class AccountAdapter : ...@@ -175,20 +172,18 @@ class AccountAdapter :
lateinit var accountEdit: AppCompatImageButton lateinit var accountEdit: AppCompatImageButton
private var id = -1L private var id = -1L
private var index = -1
init { init {
ButterKnife.bind(this, itemView) ButterKnife.bind(this, itemView)
accountEdit.setOnClickListener { accountEdit.setOnClickListener {
actionListener.onAction(id, index) actionListener.onAction(id, adapterPosition)
} }
itemView.setOnClickListener { itemView.setOnClickListener {
clickListener.onAction(id, index) clickListener.onAction(id, adapterPosition)
} }
} }
fun bind(account: AccountDatabase.Account, position: Int, selected: Boolean) { fun bind(account: AccountDatabase.Account, selected: Boolean) {
index = position
id = account.id id = account.id
accountName.text = account.name accountName.text = account.name
accountDescription.text = itemView.context.resources.getString( accountDescription.text = itemView.context.resources.getString(
...@@ -198,7 +193,6 @@ class AccountAdapter : ...@@ -198,7 +193,6 @@ class AccountAdapter :
} }
fun clear() { fun clear() {
index = -1
id = -1L id = -1L
accountName.text = "" accountName.text = ""
accountDescription.text = "" accountDescription.text = ""
......
package de.kuschku.quasseldroid_ng.ui.setup.accounts package de.kuschku.quasseldroid_ng.ui.setup.accounts
import android.app.Activity import android.app.Activity
import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.HandlerThread 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.AlertDialog
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.text.Editable
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.widget.TextView
import butterknife.BindView import butterknife.BindView
import butterknife.ButterKnife import butterknife.ButterKnife
import de.kuschku.quasseldroid_ng.Keys
import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.R
import de.kuschku.quasseldroid_ng.persistence.AccountDatabase 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() { class AccountEditActivity : AppCompatActivity() {
@BindView(R.id.nameWrapper)
lateinit var nameWrapper: TextInputLayout
@BindView(R.id.name) @BindView(R.id.name)
lateinit var name: TextView lateinit var name: TextInputEditText
@BindView(R.id.hostWrapper)
lateinit var hostWrapper: TextInputLayout
@BindView(R.id.host) @BindView(R.id.host)
lateinit var host: TextView lateinit var host: TextInputEditText
@BindView(R.id.portWrapper)
lateinit var portWrapper: TextInputLayout
@BindView(R.id.port) @BindView(R.id.port)
lateinit var port: TextView lateinit var port: TextInputEditText
@BindView(R.id.userWrapper)
lateinit var userWrapper: TextInputLayout
@BindView(R.id.user) @BindView(R.id.user)
lateinit var user: TextView lateinit var user: TextInputEditText
@BindView(R.id.passWrapper)
lateinit var passWrapper: TextInputLayout
@BindView(R.id.pass) @BindView(R.id.pass)
lateinit var pass: TextView lateinit var pass: TextInputEditText
@BindView(R.id.save_button)
lateinit var saveButton: FloatingActionButton
private var accountId: Long = -1 private var accountId: Long = -1
private var account: AccountDatabase.Account? = null private var account: AccountDatabase.Account? = null
...@@ -50,22 +63,6 @@ class AccountEditActivity : AppCompatActivity() { ...@@ -50,22 +63,6 @@ class AccountEditActivity : AppCompatActivity() {
setContentView(R.layout.setup_account_edit) setContentView(R.layout.setup_account_edit)
ButterKnife.bind(this) 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) database = AccountDatabase.Creator.init(this)
handler.post { handler.post {
accountId = intent.getLongExtra("account", -1) accountId = intent.getLongExtra("account", -1)
...@@ -79,14 +76,60 @@ class AccountEditActivity : AppCompatActivity() { ...@@ -79,14 +76,60 @@ class AccountEditActivity : AppCompatActivity() {
finish() finish()
} }
name.text = account?.name name.setText(account?.name)
host.text = account?.host host.setText(account?.host)
port.text = account?.port?.toString() port.setText(account?.port?.toString())
user.text = account?.user user.setText(account?.user)
pass.text = account?.pass 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 { override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.setup_edit_account, menu) menuInflater.inflate(R.menu.setup_edit_account, menu)
return super.onCreateOptionsMenu(menu) return super.onCreateOptionsMenu(menu)
...@@ -106,6 +149,12 @@ class AccountEditActivity : AppCompatActivity() { ...@@ -106,6 +149,12 @@ class AccountEditActivity : AppCompatActivity() {
val it = account val it = account
if (it != null) if (it != null)
handler.post { 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) database.accounts().delete(it)
} }
setResult(Activity.RESULT_OK) setResult(Activity.RESULT_OK)
...@@ -117,6 +166,24 @@ class AccountEditActivity : AppCompatActivity() { ...@@ -117,6 +166,24 @@ class AccountEditActivity : AppCompatActivity() {
.show() .show()
true 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) else -> super.onOptionsItemSelected(item)
} }
} }
package de.kuschku.quasseldroid_ng.ui.setup.accounts package de.kuschku.quasseldroid_ng.ui.setup.accounts
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import de.kuschku.quasseldroid_ng.Keys
import de.kuschku.quasseldroid_ng.ui.ChatActivity import de.kuschku.quasseldroid_ng.ui.ChatActivity
import de.kuschku.quasseldroid_ng.ui.setup.SetupActivity import de.kuschku.quasseldroid_ng.ui.setup.SetupActivity
import de.kuschku.quasseldroid_ng.util.helper.editCommit import de.kuschku.quasseldroid_ng.util.helper.editCommit
class AccountSelectionActivity : SetupActivity() { 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( override val fragments = listOf(
AccountSelectionSlide() AccountSelectionSlide()
) )
lateinit var statusPreferences: SharedPreferences private lateinit var statusPreferences: SharedPreferences
override fun onDone(data: Bundle) { override fun onDone(data: Bundle) {
statusPreferences.editCommit { statusPreferences.editCommit {
putLong(selectedAccountKey, data.getLong(selectedAccountKey, -1)) putLong(Keys.Status.selectedAccount, data.getLong(Keys.Status.selectedAccount, -1))
putBoolean("reconnect", true) putBoolean(Keys.Status.reconnect, true)
} }
startActivity(Intent(this, ChatActivity::class.java)) startActivity(Intent(this, ChatActivity::class.java))
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
statusPreferences = this.getSharedPreferences("status", Context.MODE_PRIVATE) statusPreferences = this.getSharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE)
val data = Bundle() val data = Bundle()
val selectedAccount = statusPreferences.getLong(selectedAccountKey, -1) val selectedAccount = statusPreferences.getLong(Keys.Status.selectedAccount, -1)
data.putLong(selectedAccountKey, selectedAccount) data.putLong(Keys.Status.selectedAccount, selectedAccount)
setInitData(data) setInitData(data)
if (statusPreferences.getBoolean("reconnect", false) && selectedAccount != -1L) { if (statusPreferences.getBoolean(Keys.Status.reconnect, false) && selectedAccount != -1L) {
startActivity(Intent(this, ChatActivity::class.java)) startActivityForResult(Intent(this, ChatActivity::class.java), REQUEST_CHAT)
} }
} }
companion object { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
private const val selectedAccountKey = "selectedAccount" super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CHAT && resultCode == Activity.RESULT_CANCELED) {
finish()
}
} }
} }
package de.kuschku.quasseldroid_ng.ui.setup.accounts package de.kuschku.quasseldroid_ng.ui.setup.accounts
import android.app.Activity
import android.arch.lifecycle.Observer import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders import android.arch.lifecycle.ViewModelProviders
import android.arch.paging.PagedList import android.arch.paging.PagedList
...@@ -16,12 +17,14 @@ import butterknife.ButterKnife ...@@ -16,12 +17,14 @@ import butterknife.ButterKnife
import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.R
import de.kuschku.quasseldroid_ng.persistence.AccountDatabase import de.kuschku.quasseldroid_ng.persistence.AccountDatabase
import de.kuschku.quasseldroid_ng.ui.setup.SlideFragment 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() { class AccountSelectionSlide : SlideFragment() {
@BindView(R.id.account_list) @BindView(R.id.account_list)
lateinit var accountList: RecyclerView 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 title = R.string.slideAccountSelectTitle
override val description = R.string.slideAccountSelectDescription override val description = R.string.slideAccountSelectDescription
...@@ -45,7 +48,8 @@ class AccountSelectionSlide : SlideFragment() { ...@@ -45,7 +48,8 @@ class AccountSelectionSlide : SlideFragment() {
val firstObserver = object : Observer<PagedList<AccountDatabase.Account>?> { val firstObserver = object : Observer<PagedList<AccountDatabase.Account>?> {
override fun onChanged(t: PagedList<AccountDatabase.Account>?) { override fun onChanged(t: PagedList<AccountDatabase.Account>?) {
if (t?.isEmpty() != false) if (t?.isEmpty() != false)
startActivityForResult(Intent(context, AccountSetupActivity::class.java), -1) startActivityForResult(Intent(context, AccountSetupActivity::class.java),
REQUEST_CREATE_FIRST)
accountViewmodel.accounts.removeObserver(this) accountViewmodel.accounts.removeObserver(this)
} }
} }
...@@ -54,19 +58,25 @@ class AccountSelectionSlide : SlideFragment() { ...@@ -54,19 +58,25 @@ class AccountSelectionSlide : SlideFragment() {
accountList.layoutManager = LinearLayoutManager(context) accountList.layoutManager = LinearLayoutManager(context)
accountList.itemAnimator = DefaultItemAnimator() accountList.itemAnimator = DefaultItemAnimator()
accountList.adapter = adapter accountList.adapter = adapter
adapter.selectedPos.observe(this, Observer {
updateValidity()
})
adapter.addAddListener { adapter.addAddListener {
startActivityForResult(Intent(context, AccountSetupActivity::class.java), -1) startActivityForResult(Intent(context, AccountSetupActivity::class.java), -1)
} }
adapter.addEditListener { id -> adapter.addEditListener { id ->
val intent = Intent(context, AccountEditActivity::class.java) val intent = Intent(context, AccountEditActivity::class.java)
intent.putExtra("account", id) intent.putExtra("account", id)
startActivityForResult(intent, -1) startActivityForResult(intent, REQUEST_CREATE_NEW)
}
adapter.addSelectionListener {
updateValidity()
} }
return view 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()
}
}
} }
...@@ -2,8 +2,8 @@ package de.kuschku.quasseldroid_ng.ui.setup.accounts ...@@ -2,8 +2,8 @@ package de.kuschku.quasseldroid_ng.ui.setup.accounts
import android.os.Bundle import android.os.Bundle
import android.support.design.widget.TextInputEditText import android.support.design.widget.TextInputEditText
import android.support.design.widget.TextInputLayout
import android.text.Editable import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
...@@ -11,22 +11,23 @@ import butterknife.BindView ...@@ -11,22 +11,23 @@ import butterknife.BindView
import butterknife.ButterKnife import butterknife.ButterKnife
import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.R
import de.kuschku.quasseldroid_ng.ui.setup.SlideFragment 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() { class AccountSetupConnectionSlide : SlideFragment() {
@BindView(R.id.hostWrapper)
lateinit var hostWrapper: TextInputLayout
@BindView(R.id.host) @BindView(R.id.host)
lateinit var hostField: TextInputEditText lateinit var hostField: TextInputEditText
@BindView(R.id.portWrapper)
lateinit var portWrapper: TextInputLayout
@BindView(R.id.port) @BindView(R.id.port)
lateinit var portField: TextInputEditText 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 { override fun isValid(): Boolean {
return validHost() && validPort() return hostValidator.isValid && portValidator.isValid
} }
override val title = R.string.slideAccountConnectionTitle override val title = R.string.slideAccountConnectionTitle
...@@ -49,17 +50,30 @@ class AccountSetupConnectionSlide : SlideFragment() { ...@@ -49,17 +50,30 @@ class AccountSetupConnectionSlide : SlideFragment() {
savedInstanceState: Bundle?): View { savedInstanceState: Bundle?): View {
val view = inflater.inflate(R.layout.setup_account_connection, container, false) val view = inflater.inflate(R.layout.setup_account_connection, container, false)
ButterKnife.bind(this, view) ButterKnife.bind(this, view)
hostField.addTextChangedListener(textWatcher) hostValidator = object : TextValidator(
portField.addTextChangedListener(textWatcher) hostWrapper::setError, resources.getString(R.string.hintInvalidHost)
return view ) {
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 onChanged() = updateValidity()
} }
override fun onDestroyView() { hostField.addTextChangedListener(hostValidator)
hostField.removeTextChangedListener(textWatcher) portField.addTextChangedListener(portValidator)
portField.removeTextChangedListener(textWatcher) hostValidator.afterTextChanged(hostField.text)
super.onDestroyView() portValidator.afterTextChanged(portField.text)
return view
} }
private fun validHost() = hostField.text.isNotEmpty() private lateinit var hostValidator: TextValidator
private fun validPort() = (0 until 65536).contains(portField.text.toString().toIntOrNull()) private lateinit var portValidator: TextValidator
} }
...@@ -2,8 +2,8 @@ package de.kuschku.quasseldroid_ng.ui.setup.accounts ...@@ -2,8 +2,8 @@ package de.kuschku.quasseldroid_ng.ui.setup.accounts
import android.os.Bundle import android.os.Bundle
import android.support.design.widget.TextInputEditText import android.support.design.widget.TextInputEditText
import android.support.design.widget.TextInputLayout
import android.text.Editable import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
...@@ -11,19 +11,16 @@ import butterknife.BindView ...@@ -11,19 +11,16 @@ import butterknife.BindView
import butterknife.ButterKnife import butterknife.ButterKnife
import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.R
import de.kuschku.quasseldroid_ng.ui.setup.SlideFragment import de.kuschku.quasseldroid_ng.ui.setup.SlideFragment
import de.kuschku.quasseldroid_ng.util.TextValidator
class AccountSetupNameSlide : SlideFragment() { class AccountSetupNameSlide : SlideFragment() {
@BindView(R.id.nameWrapper)
lateinit var nameWrapper: TextInputLayout
@BindView(R.id.name) @BindView(R.id.name)
lateinit var nameField: TextInputEditText 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 { override fun isValid(): Boolean {
return validName() return nameValidator.isValid
} }
override val title = R.string.slideAccountNameTitle override val title = R.string.slideAccountNameTitle
...@@ -43,14 +40,18 @@ class AccountSetupNameSlide : SlideFragment() { ...@@ -43,14 +40,18 @@ class AccountSetupNameSlide : SlideFragment() {
savedInstanceState: Bundle?): View { savedInstanceState: Bundle?): View {
val view = inflater.inflate(R.layout.setup_account_name, container, false) val view = inflater.inflate(R.layout.setup_account_name, container, false)
ButterKnife.bind(this, view) ButterKnife.bind(this, view)
nameField.addTextChangedListener(textWatcher) nameValidator = object : TextValidator(
return view nameWrapper::setError, resources.getString(R.string.hintInvalidName)
} ) {
override fun validate(text: Editable)
= text.isNotBlank()
override fun onDestroyView() { override fun onChanged() = updateValidity()
nameField.removeTextChangedListener(textWatcher) }
super.onDestroyView() nameField.addTextChangedListener(nameValidator)
nameValidator.afterTextChanged(nameField.text)
return view
} }
private fun validName() = nameField.text.isNotEmpty() private lateinit var nameValidator: TextValidator
} }
...@@ -2,8 +2,8 @@ package de.kuschku.quasseldroid_ng.ui.setup.accounts ...@@ -2,8 +2,8 @@ package de.kuschku.quasseldroid_ng.ui.setup.accounts
import android.os.Bundle import android.os.Bundle
import android.support.design.widget.TextInputEditText import android.support.design.widget.TextInputEditText
import android.support.design.widget.TextInputLayout
import android.text.Editable import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
...@@ -11,22 +11,21 @@ import butterknife.BindView ...@@ -11,22 +11,21 @@ import butterknife.BindView
import butterknife.ButterKnife import butterknife.ButterKnife
import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.R
import de.kuschku.quasseldroid_ng.ui.setup.SlideFragment import de.kuschku.quasseldroid_ng.ui.setup.SlideFragment
import de.kuschku.quasseldroid_ng.util.TextValidator
class AccountSetupUserSlide : SlideFragment() { class AccountSetupUserSlide : SlideFragment() {
@BindView(R.id.userWrapper)
lateinit var userWrapper: TextInputLayout
@BindView(R.id.user) @BindView(R.id.user)
lateinit var userField: TextInputEditText lateinit var userField: TextInputEditText
@BindView(R.id.passWrapper)
lateinit var passWrapper: TextInputLayout
@BindView(R.id.pass) @BindView(R.id.pass)
lateinit var passField: TextInputEditText 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 { override fun isValid(): Boolean {
return validUser() && validPass() return true
} }
override val title = R.string.slideAccountUserTitle override val title = R.string.slideAccountUserTitle
...@@ -48,17 +47,18 @@ class AccountSetupUserSlide : SlideFragment() { ...@@ -48,17 +47,18 @@ class AccountSetupUserSlide : SlideFragment() {
savedInstanceState: Bundle?): View { savedInstanceState: Bundle?): View {
val view = inflater.inflate(R.layout.setup_account_user, container, false) val view = inflater.inflate(R.layout.setup_account_user, container, false)
ButterKnife.bind(this, view) ButterKnife.bind(this, view)
userField.addTextChangedListener(textWatcher) userValidator = object : TextValidator(
passField.addTextChangedListener(textWatcher) userWrapper::setError, resources.getString(R.string.hintInvalidUser)
return view ) {
} override fun validate(text: Editable)
= text.isNotBlank()
override fun onDestroyView() { override fun onChanged() = updateValidity()
userField.removeTextChangedListener(textWatcher) }
passField.removeTextChangedListener(textWatcher) userField.addTextChangedListener(userValidator)
super.onDestroyView() userValidator.afterTextChanged(userField.text)
return view
} }
private fun validUser() = userField.text.isNotEmpty() private lateinit var userValidator: TextValidator
private fun validPass() = passField.text.isNotEmpty()
} }
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)
}
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
}
package de.kuschku.quasseldroid_ng.util package de.kuschku.quasseldroid_ng.util.compatibility
import android.os.Build import android.os.Build
import de.kuschku.libquassel.util.compatibility.CompatibilityUtils import de.kuschku.libquassel.util.compatibility.CompatibilityUtils
......
package de.kuschku.quasseldroid_ng.util package de.kuschku.quasseldroid_ng.util.compatibility
import android.os.Handler import android.os.Handler
import android.os.HandlerThread import android.os.HandlerThread
......
package de.kuschku.quasseldroid_ng.util package de.kuschku.quasseldroid_ng.util.compatibility
import android.util.Log import android.util.Log
import de.kuschku.libquassel.util.compatibility.LoggingHandler import de.kuschku.libquassel.util.compatibility.LoggingHandler
object AndroidLoggingHandler : LoggingHandler() { object AndroidLoggingHandler : LoggingHandler() {
override fun isLoggable(logLevel: LogLevel, tag: String): Boolean { 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?) { override fun log(logLevel: LogLevel, tag: String, message: String?, throwable: Throwable?) {
val priority = priority(logLevel) val priority = priority(
logLevel)
if (message != null) if (message != null)
Log.println(priority, tag, message) Log.println(priority, tag, message)
if (throwable != null) if (throwable != null)
......
package de.kuschku.quasseldroid_ng.util package de.kuschku.quasseldroid_ng.util.compatibility
import de.kuschku.libquassel.util.compatibility.StreamChannelFactory import de.kuschku.libquassel.util.compatibility.StreamChannelFactory
import de.kuschku.quasseldroid_ng.util.backport.ReadableStreamChannel import de.kuschku.quasseldroid_ng.util.backport.ReadableStreamChannel
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minWidth="88dp" android:minWidth="88dp"
android:text="Connect" android:text="Connect"
android:theme="@style/RaisedThemedButton" /> android:theme="@style/AppTheme.Button.Colored" />
<Button <Button
android:id="@+id/disconnect" android:id="@+id/disconnect"
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minWidth="88dp" android:minWidth="88dp"
android:text="Disconnect" android:text="Disconnect"
android:theme="@style/RaisedButton" /> android:theme="@style/AppTheme.Button" />
<Button <Button
android:id="@+id/clear" android:id="@+id/clear"
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minWidth="88dp" android:minWidth="88dp"
android:text="Clear" android:text="Clear"
android:theme="@style/RaisedButton" /> android:theme="@style/AppTheme.Button" />
</LinearLayout> </LinearLayout>
<TextView <TextView
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment