Commit 2505c42c authored by Javier Romero's avatar Javier Romero

WIP: New form UI

parent 5aefbe68
......@@ -13,7 +13,6 @@ import java.sql.Connection
import java.sql.DriverManager
import java.sql.SQLException
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
class ConnectionAgent(private val sshTunnelAgent: SshTunnelAgent) {
......@@ -45,8 +44,10 @@ class ConnectionAgent(private val sshTunnelAgent: SshTunnelAgent) {
}
}
private fun doConnect(connectionInfo: ConnectionInfo,
tunnelAddress: Address?): Observable<Connection> {
private fun doConnect(
connectionInfo: ConnectionInfo,
tunnelAddress: Address?
): Observable<Connection> {
return Observable.create { subscriber ->
try {
if (!activeConnections.containsKey(connectionInfo) || activeConnections[connectionInfo]?.isClosed == true) {
......
......@@ -3,8 +3,6 @@ package app.devlife.connect2sql.db.model
import android.content.ContentValues
import android.database.Cursor
import net.sqlcipher.database.SQLiteDatabase
interface SqlModel<T> {
val modelClass: Class<T>
......
......@@ -30,14 +30,12 @@ class ConnectionInfoSqlModel : SqlModel<ConnectionInfo> {
const val SSH_PRIVATE_KEY = "ssh_private_key"
}
override val modelClass: Class<ConnectionInfo>
get() = ConnectionInfo::class.java
override val tableName: String
get() = TABLE_NAME
override val createSql: String
get() =
"""
......@@ -96,7 +94,7 @@ class ConnectionInfoSqlModel : SqlModel<ConnectionInfo> {
when {
privateKeyContents != null ->
SshConfig(Address(host, port), PrivateKey(username, privateKeyContents))
SshConfig(Address(host, port), PrivateKey(username, password, privateKeyContents))
password != null ->
SshConfig(Address(host, port), BasicAuth(username, password))
else ->
......
......@@ -7,4 +7,4 @@ data class SshTunnelConfig(val proxy: SshConfig, val serviceAddress: Address)
sealed class Authentication(val username: String)
data class None(private val username_: String) : Authentication(username_)
data class BasicAuth(private val username_: String, val password: String?) : Authentication(username_)
data class PrivateKey(private val username_: String, val privateKeyContents: String): Authentication(username_)
\ No newline at end of file
data class PrivateKey(private val username_: String, val passphrase: String?, val privateKeyContents: String) : Authentication(username_)
\ No newline at end of file
......@@ -31,8 +31,10 @@ class DefaultDriverAgent(private val driverHelper: DriverHelper) : DriverAgent {
}
}
override fun tables(connection: Connection,
databaseName: DriverAgent.Database): Observable<DriverAgent.Table> {
override fun tables(
connection: Connection,
databaseName: DriverAgent.Database
): Observable<DriverAgent.Table> {
EzLogger.v("[tables] databaseName=$databaseName")
return Observable.create { subscriber ->
try {
......@@ -72,9 +74,11 @@ class DefaultDriverAgent(private val driverHelper: DriverHelper) : DriverAgent {
}
}
override fun columns(connection: Connection,
databaseName: DriverAgent.Database,
tableName: DriverAgent.Table): Observable<DriverAgent.Column> {
override fun columns(
connection: Connection,
databaseName: DriverAgent.Database,
tableName: DriverAgent.Table
): Observable<DriverAgent.Column> {
EzLogger.v("[columns] databaseName=$databaseName, tableName=$tableName")
return Observable.create { subscriber ->
try {
......@@ -99,9 +103,11 @@ class DefaultDriverAgent(private val driverHelper: DriverHelper) : DriverAgent {
}
}
override fun execute(connection: Connection,
databaseName: DriverAgent.Database?,
sql: String): Observable<Statement> {
override fun execute(
connection: Connection,
databaseName: DriverAgent.Database?,
sql: String
): Observable<Statement> {
return Observable.create { subscriber ->
try {
if (databaseName != null) {
......@@ -121,9 +127,11 @@ class DefaultDriverAgent(private val driverHelper: DriverHelper) : DriverAgent {
}
}
override fun extract(resultSet: ResultSet,
startIndex: Int,
displayLimit: Int): Observable<DriverAgent.DisplayResults> {
override fun extract(
resultSet: ResultSet,
startIndex: Int,
displayLimit: Int
): Observable<DriverAgent.DisplayResults> {
return Observable.create { subscriber ->
try {
val metaData = resultSet.metaData
......@@ -132,7 +140,6 @@ class DefaultDriverAgent(private val driverHelper: DriverHelper) : DriverAgent {
resultSet.last()
val totalRows = resultSet.row
val columnNameAndTypes = (0 until columnCount).map { i ->
Pair(
metaData.getColumnLabel(i + 1),
......
......@@ -4,7 +4,6 @@ import rx.Observable
import java.sql.Connection
import java.sql.ResultSet
import java.sql.Statement
import java.util.LinkedList
/**
......
......@@ -23,10 +23,8 @@ import app.devlife.connect2sql.db.repo.ConnectionInfoRepository
import app.devlife.connect2sql.log.EzLogger
import app.devlife.connect2sql.sql.driver.DriverDefaults
import app.devlife.connect2sql.ui.connection.form.ActionBarContainer
import app.devlife.connect2sql.ui.connection.form.BaseForm
import app.devlife.connect2sql.ui.connection.form.Field
import app.devlife.connect2sql.ui.connection.form.Form
import app.devlife.connect2sql.ui.connection.form.FormFactory
import app.devlife.connect2sql.ui.connection.form.FormUtils
import app.devlife.connect2sql.ui.widget.NotifyingScrollView
import app.devlife.connect2sql.ui.widget.Toast
import app.devlife.connect2sql.ui.widget.dialog.ProgressDialog
......@@ -46,14 +44,14 @@ class ConnectionInfoEditorActivity : BaseActivity() {
intent?.extras?.getParcelable<ConnectionInfoEditorRequest>(EXTRA_CONNECTION_INFO_REQUEST)!!
}
private val nameBarBackgroundDrawable: Drawable by lazy {
private val actionBarBackgroundDrawable: Drawable by lazy {
val drawable = resources.getDrawable(R.drawable.action_bar_background)
drawable.alpha = 0
drawable
}
private lateinit var nameBarContainer: ActionBarContainer
private lateinit var form: BaseForm
private lateinit var form: Form
private var doOnValidationSuccess: ValidationAction? = null
private var progressDialog: ProgressDialog? = null
......@@ -73,25 +71,22 @@ class ConnectionInfoEditorActivity : BaseActivity() {
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
val activity = this
var connectionInfo: ConnectionInfo? = null
if (request.action == ConnectionInfoEditorRequest.Action.EDIT) {
connectionInfo = connectionInfoRepository.getConnectionInfo(request.connectionInfoId)
request.driverType = connectionInfo!!.driverType
}
form = FormFactory.get(activity, layoutInflater, request.driverType)
form = FormFactory.get(this, layoutInflater, request.driverType)
// ensure we bring up the keyboard at startup
form.nameEditText.post {
inputMethodManager.showSoftInput(form.nameEditText,
InputMethodManager.SHOW_IMPLICIT)
inputMethodManager.showSoftInput(form.nameEditText, InputMethodManager.SHOW_IMPLICIT)
}
// setup actionbar
nameBarContainer = form.actionBarContainer
nameBarContainer.background = nameBarBackgroundDrawable
nameBarContainer.background = actionBarBackgroundDrawable
nameBarContainer.titleView.alpha = 0f
nameBarContainer.logoView.setImageResource(DriverLogo.fromDriverType(request.driverType).resource)
......@@ -100,7 +95,6 @@ class ConnectionInfoEditorActivity : BaseActivity() {
form.testButton.setOnClickListener(onTestButtonClickListener)
form.saveButton.setOnClickListener(onSaveButtonClickListener)
form.scrollView.setOnScrollChangedListener(onScrollChangedListener)
form.setOnActionOnClickListener(onActionOnClickListener)
// populate form
if (connectionInfo != null) {
......@@ -118,18 +112,15 @@ class ConnectionInfoEditorActivity : BaseActivity() {
}
private fun saveConnection(): ConnectionInfo {
/**
* Save connection
*/
val connectionInfo = form.compileConnectionInfo()
when (request.action) {
return when (request.action) {
ConnectionInfoEditorRequest.Action.EDIT -> {
val id = connectionInfoRepository.save(connectionInfo.copy(id = request.connectionInfoId))
return connectionInfo.copy(id = id)
connectionInfo.copy(id = id)
}
ConnectionInfoEditorRequest.Action.NEW -> {
val id = connectionInfoRepository.save(connectionInfo)
return connectionInfo.copy(id = id)
connectionInfo.copy(id = id)
}
}
}
......@@ -143,20 +134,20 @@ class ConnectionInfoEditorActivity : BaseActivity() {
*/
if (TextUtils.isEmpty(connectionInfo.password)) {
val promptDialogView = LayoutInflater.from(this).inflate(R.layout.dialog_prompt, null)
val passwordText = promptDialogView.findViewById<EditText>(R.id.editView1)
(promptDialogView.findViewById(R.id.textView1) as TextView).visibility = View.GONE
val passwordText = promptDialogView.findViewById(R.id.editView1) as EditText
promptDialogView.findViewById<TextView>(R.id.textView1).visibility = View.GONE
passwordText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
val alertBuilder = AlertDialog.Builder(this)
alertBuilder.setTitle(R.string.dialog_password)
alertBuilder.setView(promptDialogView)
alertBuilder.setPositiveButton(R.string.dialog_ok) { dialog, which ->
executeTestConnection(connectionInfo.copy(password = passwordText.text.toString()))
}
alertBuilder.create().show()
AlertDialog.Builder(this)
.setTitle(R.string.dialog_password)
.setView(promptDialogView)
.setPositiveButton(R.string.dialog_ok) { _, _ ->
executeTestConnection(connectionInfo.copy(password = passwordText.text.toString()))
}
.create()
.show()
} else {
executeTestConnection(connectionInfo)
}
......@@ -249,34 +240,6 @@ class ConnectionInfoEditorActivity : BaseActivity() {
}
}
private val onActionOnClickListener = Field.OnActionClickListener { action, actionView, inputView ->
when (action) {
Field.Action.VISIBLE -> if (inputView.id == R.id.form_txt_password) {
val editText = inputView as EditText
togglePasswordVisibility(editText)
editText.setSelection(editText.text.length)
}
Field.Action.KEYBOARD_INPUT -> if (inputView.id == R.id.form_txt_host) {
toggleAlphaToNumeric(inputView as EditText, false, false)
}
Field.Action.HELP -> {
val helpMessageResource = form.getHelpMessageResource(inputView)
if (helpMessageResource > 0) {
showHelp(helpMessageResource)
}
}
else -> EzLogger.w("Unknown action: " + action)
}
}
private fun showHelp(resourceHelpMessage: Int) {
val builder = AlertDialog.Builder(this)
builder.setTitle(R.string.form_action_help)
builder.setMessage(resourceHelpMessage)
builder.setPositiveButton(R.string.help_positive_btn_label) { dialog, which -> dialog.dismiss() }
builder.show()
}
private val onScrollChangedListener = NotifyingScrollView.OnScrollChangedListener { who, l, t, oldl, oldt ->
val headerHeight = nameBarContainer.height / 2
val ratio = Math.min(Math.max(t, 0), headerHeight).toFloat() / headerHeight
......@@ -286,7 +249,7 @@ class ConnectionInfoEditorActivity : BaseActivity() {
} else {
nameBarContainer.titleView.alpha = 0f
}
nameBarBackgroundDrawable.alpha = newAlpha
actionBarBackgroundDrawable.alpha = newAlpha
}
private val nameTextWatcher = object : TextWatcher {
......@@ -311,39 +274,6 @@ class ConnectionInfoEditorActivity : BaseActivity() {
form.validate(validationListener)
}
private fun toggleAlphaToNumeric(editText: EditText, strict: Boolean, signed: Boolean) {
val inputType = editText.inputType
if (FormUtils.hasInputType(inputType, InputType.TYPE_CLASS_NUMBER)) {
var inputType1 = FormUtils.addInputType(inputType, InputType.TYPE_CLASS_TEXT)
inputType1 = FormUtils.removeInputType(inputType1, InputType.TYPE_CLASS_NUMBER)
if (signed) {
inputType1 = FormUtils.removeInputType(inputType1,
InputType.TYPE_NUMBER_FLAG_SIGNED)
}
editText.inputType = inputType1
} else {
var inputType2 = FormUtils.addInputType(inputType, InputType.TYPE_CLASS_NUMBER)
if (strict) {
inputType2 = FormUtils.removeInputType(inputType2, InputType.TYPE_CLASS_TEXT)
}
if (signed) {
inputType2 = FormUtils.addInputType(inputType2, InputType.TYPE_NUMBER_FLAG_SIGNED)
}
editText.inputType = inputType2
}
}
private fun togglePasswordVisibility(editText: EditText) {
val inputType = editText.inputType
if (FormUtils.hasInputType(inputType, InputType.TYPE_TEXT_VARIATION_PASSWORD)) {
editText.inputType = FormUtils.removeInputType(inputType,
InputType.TYPE_TEXT_VARIATION_PASSWORD)
} else {
editText.inputType = FormUtils.addInputType(inputType,
InputType.TYPE_TEXT_VARIATION_PASSWORD)
}
}
companion object {
private const val EXTRA_CONNECTION_INFO_REQUEST = "EXTRA_CONNECTION_INFO_REQUEST"
......
......@@ -9,31 +9,29 @@ import android.widget.ImageView
import android.widget.TextView
import com.gitlab.connect2sql.R
class ActionBarContainer @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0) : FrameLayout(context, attrs, defStyle) {
lateinit var logoView: ImageView
private set
lateinit var titleView: TextView
private set
private lateinit var background: FrameLayout
class ActionBarContainer @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) :
FrameLayout(context, attrs, defStyle) {
init {
init()
}
val logoView: ImageView
val titleView: TextView
private val backgroundView: FrameLayout
private fun init() {
init {
val view = LayoutInflater.from(context).inflate(R.layout.form_actionbar, this)
background = view.findViewById(R.id.form_actionbar_background)
backgroundView = view.findViewById(R.id.form_actionbar_background)
logoView = view.findViewById(R.id.form_actionbar_logo)
titleView = view.findViewById(R.id.form_actionbar_title)
}
override fun setBackgroundResource(resid: Int) {
background.setBackgroundResource(resid)
backgroundView.setBackgroundResource(resid)
}
override fun setBackground(background: Drawable) {
this.background.background = background
this.backgroundView.background = background
}
}
package app.devlife.connect2sql.ui.connection.form
import android.content.Context
import android.support.v7.app.AlertDialog
import android.text.InputType
import android.util.AttributeSet
import android.widget.EditText
import android.widget.ImageView
import app.devlife.connect2sql.ui.widget.Toast
import app.devlife.connect2sql.util.ext.findSiblingById
import app.devlife.connect2sql.util.ext.stringValue
import com.gitlab.connect2sql.R
class ActionImageView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ImageView(context, attrs, defStyleAttr) {
private val action: Action?
private val helpMessage: String?
private val associatedViewId: Int?
init {
val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.ActionImageView, 0, 0)
try {
action = styledAttrs
.getInt(R.styleable.ActionImageView_type, -1)
.let { Action.fromAttrValue(it) }
helpMessage = styledAttrs.getString(R.styleable.ActionImageView_helpText)
associatedViewId = styledAttrs
.getResourceId(R.styleable.ActionImageView_associatedWith, 0)
.let { if (it == 0) null else it }
} finally {
styledAttrs.recycle()
}
}
override fun performLongClick(): Boolean {
if (contentDescription?.isNotBlank() == true) {
Toast.makeText(context, contentDescription, Toast.LENGTH_SHORT).show()
return true
}
return super.performLongClick()
}
override fun performClick(): Boolean {
return when {
action == Action.KEYBOARD && associatedViewId != null -> {
findSiblingById<EditText>(associatedViewId)?.also { editText ->
toggleAlphaToNumeric(editText, false, true)
}
true
}
action == Action.OBSCURE && associatedViewId != null -> {
findSiblingById<EditText>(associatedViewId)?.also { editText ->
togglePasswordVisibility(editText)
editText.setSelection(editText.stringValue.length)
}
true
}
action == Action.HELP && helpMessage?.isNotBlank() == true -> {
AlertDialog.Builder(context)
.setTitle(R.string.form_action_help)
.setMessage(helpMessage)
.setPositiveButton(R.string.help_positive_btn_label) { dialog, _ ->
dialog.dismiss()
}
.show()
true
}
else -> super.performClick()
}
}
private fun toggleAlphaToNumeric(editText: EditText, strict: Boolean, signed: Boolean) {
val inputType = editText.inputType
if (FormUtils.hasInputType(inputType, InputType.TYPE_CLASS_NUMBER)) {
var inputType1 = FormUtils.addInputType(inputType, InputType.TYPE_CLASS_TEXT)
inputType1 = FormUtils.removeInputType(inputType1, InputType.TYPE_CLASS_NUMBER)
if (signed) {
inputType1 = FormUtils.removeInputType(inputType1,
InputType.TYPE_NUMBER_FLAG_SIGNED)
}
editText.inputType = inputType1
} else {
var inputType2 = FormUtils.addInputType(inputType, InputType.TYPE_CLASS_NUMBER)
if (strict) {
inputType2 = FormUtils.removeInputType(inputType2, InputType.TYPE_CLASS_TEXT)
}
if (signed) {
inputType2 = FormUtils.addInputType(inputType2, InputType.TYPE_NUMBER_FLAG_SIGNED)
}
editText.inputType = inputType2
}
}
private fun togglePasswordVisibility(editText: EditText) {
val inputType = editText.inputType
if (FormUtils.hasInputType(inputType, InputType.TYPE_TEXT_VARIATION_PASSWORD)) {
editText.inputType = FormUtils.removeInputType(inputType,
InputType.TYPE_TEXT_VARIATION_PASSWORD)
} else {
editText.inputType = FormUtils.addInputType(inputType,
InputType.TYPE_TEXT_VARIATION_PASSWORD)
}
}
enum class Action(val attrValue: Int) {
HELP(0),
KEYBOARD(1),
OBSCURE(2);
companion object {
fun fromAttrValue(attrValue: Int): Action {
val actions = values()
for (action in actions) {
if (action.attrValue == attrValue) {
return action
}
}
throw IllegalArgumentException("Action $attrValue not a valid action.")
}
}
}
}
\ No newline at end of file
package app.devlife.connect2sql.ui.connection.form
import android.content.Context
import android.view.View
import android.widget.EditText
import app.devlife.connect2sql.db.model.connection.ConnectionInfo
import com.gitlab.connect2sql.R
/**
* Created by javier.romero on 5/5/14.
*/
abstract class BaseMsSqlForm(context: Context, view: View) : BaseForm(context, view) {
val domainEditTextView: EditText
val instanceEditTextView: EditText
init {
domainEditTextView = view.findViewById(R.id.form_txt_domain) as EditText
instanceEditTextView = view.findViewById(R.id.form_txt_instance) as EditText
}
override fun compileConnectionInfo(): ConnectionInfo {
val connectionInfo = super.compileConnectionInfo()
val options = connectionInfo.options + hashMapOf(
Pair(ConnectionInfo.OPTION_DOMAIN, domainEditTextView.text.toString()),
Pair(ConnectionInfo.OPTION_INSTANCE, instanceEditTextView.text.toString())
)
return connectionInfo.copy(options = options)
}
override fun populate(connectionInfo: ConnectionInfo) {
super.populate(connectionInfo)
val domain = connectionInfo.options.get(ConnectionInfo.OPTION_DOMAIN) ?: ""
domainEditTextView.setText(domain)
val instance = connectionInfo.options.get(ConnectionInfo.OPTION_INSTANCE) ?: ""
instanceEditTextView.setText(instance)
}
override fun getHelpMessageResource(view: View): Int {
when (view.id) {
R.id.form_txt_domain -> return R.string.help_domain
R.id.form_txt_instance -> return R.string.help_instance
else -> return super.getHelpMessageResource(view)
}
}
}
package app.devlife.connect2sql.ui.connection.form;
import android.view.View;
/**
* Created by javier.romero on 5/4/14.
*/
public interface Field {
public void setOnActionClickListener(OnActionClickListener listener);
public void setOnActionLongClickListener(OnActionLongClickListener listener);
public static interface OnActionClickListener {
public void onActionClick(Action action, View actionView, View inputView);
}
public static interface OnActionLongClickListener {
public void onActionLongClick(Action action, View view, View inputView);
}
public static enum Action {
KEYBOARD_INPUT("action_keyboard_input"),
VISIBLE("action_visible"),
HELP("action_help");
private final String mTag;
private Action(String tag) {
mTag = tag;
}
public String getTag() {