Initial commit.
1
app/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/build
|
56
app/build.gradle
Normal file
|
@ -0,0 +1,56 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion 24
|
||||
buildToolsVersion "24.0.1"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "net.thauvin.erik.android.tesremoteprogrammer"
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 24
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
|
||||
compile 'com.android.support:support-v13:24.1.1'
|
||||
compile 'com.android.support:appcompat-v7:24.1.1'
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
compile 'org.jetbrains.anko:anko-sdk23:0.9'
|
||||
compile 'org.jetbrains.anko:anko-appcompat-v7:0.9'
|
||||
compile 'org.jetbrains.anko:anko-support-v4:0.9'
|
||||
compile 'org.jetbrains.anko:anko-design:0.9'
|
||||
compile 'com.android.support:design:24.1.1'
|
||||
compile group: 'com.google.code.gson', name: 'gson', version: '2.7'
|
||||
|
||||
|
||||
// https://github.com/JakeWharton/ViewPagerIndicator
|
||||
compile 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
|
||||
|
||||
// https://github.com/hotchemi/PermissionsDispatcher
|
||||
compile 'com.github.hotchemi:permissionsdispatcher:2.1.3'
|
||||
kapt 'com.github.hotchemi:permissionsdispatcher-processor:2.1.3'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
kapt {
|
||||
generateStubs = true
|
||||
}
|
17
app/proguard-rules.pro
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in C:\Users\erik\AppData\Local\Android\android-sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
31
app/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="net.thauvin.erik.android.tesremoteprogrammer">
|
||||
|
||||
<uses-permission android:name="android.permission.CALL_PHONE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="@xml/mybackupscheme"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:screenOrientation="portrait">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ProgrammingActivity"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name=".StepsActivity"
|
||||
android:screenOrientation="landscape" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,385 @@
|
|||
/*
|
||||
* MainActivity.kt
|
||||
*
|
||||
* Copyright 2016 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.thauvin.erik.android.tesremoteprogrammer
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Typeface
|
||||
import android.os.Bundle
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.text.Html
|
||||
import android.text.InputFilter
|
||||
import android.text.InputType
|
||||
import android.text.TextUtils
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.util.TypedValue
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import net.thauvin.erik.android.tesremoteprogrammer.models.Config
|
||||
import net.thauvin.erik.android.tesremoteprogrammer.models.Configurations
|
||||
import net.thauvin.erik.android.tesremoteprogrammer.util.Dtmf
|
||||
import net.thauvin.erik.android.tesremoteprogrammer.util.plural
|
||||
import org.jetbrains.anko.*
|
||||
import org.jetbrains.anko.design.textInputLayout
|
||||
import permissions.dispatcher.NeedsPermission
|
||||
import permissions.dispatcher.RuntimePermissions
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.InputStreamReader
|
||||
import java.io.ObjectInputStream
|
||||
import java.io.ObjectOutputStream
|
||||
import java.util.*
|
||||
|
||||
@RuntimePermissions
|
||||
class MainActivity : AppCompatActivity(), AnkoLogger {
|
||||
lateinit var config: Config
|
||||
val configurations_data = "configurations.dat"
|
||||
val current_config_data = "config.dat"
|
||||
val read_request_code = 42
|
||||
|
||||
companion object {
|
||||
val PAUSE = ','
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == read_request_code && resultCode == Activity.RESULT_OK) {
|
||||
if (data != null) {
|
||||
MainActivityPermissionsDispatcher.validateConfigWithCheck(this, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val fields = arrayListOf<EditText>()
|
||||
|
||||
loadConfig()
|
||||
|
||||
verticalLayout {
|
||||
padding = dip(20)
|
||||
|
||||
textView {
|
||||
text = config.params.name.toUpperCase()
|
||||
bottomPadding = dip(5)
|
||||
typeface = Typeface.DEFAULT_BOLD
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_DIP, 24f)
|
||||
isFocusableInTouchMode = true
|
||||
singleLine = true
|
||||
ellipsize = TextUtils.TruncateAt.END
|
||||
}
|
||||
|
||||
textInputLayout {
|
||||
horizontalPadding = dip(40)
|
||||
val phone = editText() {
|
||||
lparams(width = matchParent)
|
||||
inputType = InputType.TYPE_CLASS_PHONE
|
||||
hint = getString(R.string.hint_phone_number)
|
||||
|
||||
if (config.params.phone.isNotBlank()) {
|
||||
setText(config.params.phone)
|
||||
}
|
||||
|
||||
setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_call_black_24dp, 0)
|
||||
setOnFocusChangeListener { view, hasFocus ->
|
||||
if (!hasFocus) {
|
||||
config.params.phone = (view as EditText).text.toString()
|
||||
saveConfig()
|
||||
}
|
||||
}
|
||||
}
|
||||
fields.add(phone)
|
||||
}
|
||||
|
||||
textInputLayout {
|
||||
horizontalPadding = dip(40)
|
||||
val masterCode = editText() {
|
||||
lparams(width = matchParent)
|
||||
inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD
|
||||
hint = getString(R.string.hint_master_code)
|
||||
filters = arrayOf(InputFilter.LengthFilter(config.params.size))
|
||||
setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_verified_user_black_24dp, 0)
|
||||
imeOptions = EditorInfo.IME_ACTION_DONE
|
||||
|
||||
if (config.params.master.isNotBlank()) {
|
||||
setText(config.params.master)
|
||||
}
|
||||
|
||||
setOnFocusChangeListener { view, hasFocus ->
|
||||
if (!hasFocus) {
|
||||
config.params.master = (view as EditText).text.toString()
|
||||
saveConfig()
|
||||
}
|
||||
}
|
||||
|
||||
setOnEditorActionListener { v, id, event ->
|
||||
if (id == EditorInfo.IME_ACTION_DONE) {
|
||||
clearFocus()
|
||||
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.hideSoftInputFromWindow(windowToken, 0)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
fields.add(masterCode)
|
||||
}
|
||||
|
||||
textView {
|
||||
topPadding = dip(10)
|
||||
text = getString(R.string.programming_heading)
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18f)
|
||||
typeface = Typeface.DEFAULT_BOLD
|
||||
}.lparams(width = matchParent)
|
||||
|
||||
val opts = config.opts.sorted()
|
||||
val titles = arrayListOf<String>()
|
||||
opts.all {
|
||||
titles.add(it.title)
|
||||
}
|
||||
|
||||
listView {
|
||||
adapter = ArrayAdapter<String>(this@MainActivity, android.R.layout.simple_list_item_1, titles)
|
||||
isTextFilterEnabled = true
|
||||
isScrollbarFadingEnabled = false
|
||||
onItemClickListener = AdapterView.OnItemClickListener { parent, v, position, id ->
|
||||
if (validateFields(fields, config.params.size)) {
|
||||
startActivity<ProgrammingActivity>(
|
||||
"net.thauvin.erik.android.tesremoteprogrammer.models.Params" to config.params,
|
||||
"net.thauvin.erik.android.tesremoteprogrammer.models.Option" to opts[position])
|
||||
}
|
||||
}
|
||||
}.lparams(width = matchParent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_main, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
||||
when (item?.itemId) {
|
||||
R.id.action_about -> {
|
||||
val alert = alert {
|
||||
title(R.string.app_name)
|
||||
message(Html.fromHtml(
|
||||
getString(R.string.about_message, BuildConfig.VERSION_NAME)))
|
||||
icon(R.mipmap.ic_launcher)
|
||||
okButton {}
|
||||
}.show()
|
||||
(alert.dialog?.findViewById(android.R.id.message) as TextView)
|
||||
.movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
R.id.action_config -> {
|
||||
val configs = loadConfigurations().configs
|
||||
val keys = configs.keys
|
||||
val checked = keys.indexOf(config.params.name)
|
||||
val alert = AlertDialog.Builder(this)
|
||||
|
||||
alert.setTitle(R.string.action_config)
|
||||
|
||||
alert.setSingleChoiceItems(keys.toTypedArray(), checked,
|
||||
{ dialogInterface, i ->
|
||||
if (i != checked) {
|
||||
config = configs.getOrElse(keys.elementAt(i), defaultValue = { config })
|
||||
saveConfig()
|
||||
this.recreate()
|
||||
}
|
||||
dialogInterface.dismiss()
|
||||
})
|
||||
|
||||
alert.setNegativeButton(android.R.string.cancel,
|
||||
{ dialogInterface, i ->
|
||||
dialogInterface.dismiss()
|
||||
})
|
||||
|
||||
alert.setNeutralButton(R.string.dialog_import,
|
||||
{ dialogInterface, i ->
|
||||
dialogInterface.dismiss()
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
intent.type = "application/json"
|
||||
startActivityForResult(intent, read_request_code)
|
||||
})
|
||||
|
||||
|
||||
alert.show()
|
||||
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults)
|
||||
}
|
||||
|
||||
fun loadConfigurations(): Configurations {
|
||||
try {
|
||||
ObjectInputStream(openFileInput(configurations_data)).use {
|
||||
return it.readObject() as Configurations
|
||||
}
|
||||
} catch (ex: FileNotFoundException) {
|
||||
return Configurations()
|
||||
}
|
||||
}
|
||||
|
||||
fun loadConfig() {
|
||||
try {
|
||||
ObjectInputStream(openFileInput(current_config_data)).use {
|
||||
config = it.readObject() as Config
|
||||
}
|
||||
} catch (ex: FileNotFoundException) {
|
||||
config = Gson().fromJson(InputStreamReader(resources.openRawResource(R.raw.dks_1802_epd)),
|
||||
Config::class.java)
|
||||
|
||||
saveConfig()
|
||||
}
|
||||
}
|
||||
|
||||
fun saveConfigurations(confs: Configurations) {
|
||||
ObjectOutputStream(openFileOutput(configurations_data, Context.MODE_PRIVATE)).use {
|
||||
it.writeObject(confs)
|
||||
}
|
||||
}
|
||||
fun saveConfig() {
|
||||
val confs = loadConfigurations()
|
||||
confs.configs.put(config.params.name, config)
|
||||
saveConfigurations(confs)
|
||||
|
||||
ObjectOutputStream(openFileOutput(current_config_data, Context.MODE_PRIVATE)).use {
|
||||
it.writeObject(config)
|
||||
}
|
||||
}
|
||||
|
||||
@NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
fun validateConfig(intent: Intent) {
|
||||
val errors = StringBuilder()
|
||||
|
||||
val tmp: Config? = try {
|
||||
Gson().fromJson(InputStreamReader(contentResolver.openInputStream(intent.data)),
|
||||
Config::class.java)
|
||||
} catch (jse: JsonSyntaxException) {
|
||||
val cause = jse.cause
|
||||
if (cause != null) {
|
||||
errors.append(cause.message)
|
||||
} else {
|
||||
errors.append(jse.message)
|
||||
}
|
||||
null
|
||||
}
|
||||
|
||||
if (tmp != null) {
|
||||
with(tmp) {
|
||||
if (params.name.isBlank()) {
|
||||
errors.append(getString(R.string.validate_missing_param, "name"))
|
||||
}
|
||||
|
||||
if (params.size < 1) {
|
||||
errors.append(getString(R.string.validate_invalid_param, "size"))
|
||||
}
|
||||
|
||||
if (params.star.isBlank()) {
|
||||
errors.append(getString(R.string.validate_missing_param, "star"))
|
||||
}
|
||||
|
||||
if (opts.size == 0) {
|
||||
errors.append(getString(R.string.validate_missing_ops))
|
||||
}
|
||||
|
||||
opts.forEachIndexed { i, option ->
|
||||
if (option.fields.size == 0) {
|
||||
errors.append(getString(R.string.validate_missing_fields, i + 1))
|
||||
}
|
||||
|
||||
option.fields.forEachIndexed { j, field ->
|
||||
if (field.size <= 0) {
|
||||
errors.append(getString(R.string.validate_invalid_attr, i + 1, j + 1, "size"))
|
||||
}
|
||||
|
||||
if (!field.alpha) {
|
||||
if (field.min >= 0 || field.max >= 0) {
|
||||
if (field.max < 1) {
|
||||
errors.append(getString(R.string.validate_invalid_attr, i + 1, j + 1, "max"))
|
||||
} else if (field.min < 0) {
|
||||
errors.append(getString(R.string.validate_invalid_attr, i + 1, j + 1, "min"))
|
||||
} else if (field.min > field.max) {
|
||||
errors.append(getString(R.string.validate_invalid_attr, i + 1, j + 1, "max/min"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val blank = "\\0"
|
||||
val dtmf = Dtmf.mock(option, blank)
|
||||
if (!Dtmf.validate(dtmf, "${MainActivity.PAUSE}${params.star}${params.hash}$blank")) {
|
||||
errors.append(getString(R.string.validate_invalid_dtmf, i + 1, dtmf.replace(blank, "✓")))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length == 0) {
|
||||
config = tmp
|
||||
saveConfig()
|
||||
recreate()
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
alert {
|
||||
title(R.string.alert_config_error)
|
||||
message(Html.fromHtml("$errors"))
|
||||
cancelButton { }
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
|
||||
fun validateFields(fields: ArrayList<EditText>, size: Int): Boolean {
|
||||
var isValid = true
|
||||
|
||||
fields.forEach {
|
||||
if (it.text.isNullOrBlank()) {
|
||||
it.error = getString(R.string.error_required)
|
||||
isValid = false
|
||||
}
|
||||
}
|
||||
if (size > 0 && (fields[1].text.length != size)) {
|
||||
isValid = false
|
||||
fields[1].error = getString(R.string.error_invalid_size, size,
|
||||
getString(R.string.error_digit).plural(size))
|
||||
}
|
||||
|
||||
return isValid
|
||||
}
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
/*
|
||||
* ProgrammingActivity.kt
|
||||
*
|
||||
* Copyright 2016 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.thauvin.erik.android.tesremoteprogrammer
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.graphics.Typeface
|
||||
import android.os.Bundle
|
||||
import android.support.design.widget.Snackbar
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.text.InputFilter
|
||||
import android.text.InputType
|
||||
import android.text.TextUtils
|
||||
import android.text.method.DigitsKeyListener
|
||||
import android.util.TypedValue
|
||||
import android.view.Gravity.*
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import net.thauvin.erik.android.tesremoteprogrammer.filters.AlphaFilter
|
||||
import net.thauvin.erik.android.tesremoteprogrammer.filters.MinMaxFilter
|
||||
import net.thauvin.erik.android.tesremoteprogrammer.models.Option
|
||||
import net.thauvin.erik.android.tesremoteprogrammer.models.Params
|
||||
import net.thauvin.erik.android.tesremoteprogrammer.util.Dtmf
|
||||
import net.thauvin.erik.android.tesremoteprogrammer.util.plural
|
||||
import net.thauvin.erik.android.tesremoteprogrammer.widget.ScrollAwareFABBehavior
|
||||
import org.jetbrains.anko.*
|
||||
import org.jetbrains.anko.design.coordinatorLayout
|
||||
import org.jetbrains.anko.design.floatingActionButton
|
||||
import org.jetbrains.anko.design.textInputLayout
|
||||
import org.jetbrains.anko.support.v4.nestedScrollView
|
||||
import permissions.dispatcher.NeedsPermission
|
||||
import permissions.dispatcher.RuntimePermissions
|
||||
import java.util.*
|
||||
|
||||
@RuntimePermissions
|
||||
class ProgrammingActivity : AppCompatActivity(), AnkoLogger {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val params: Params = intent.extras.getParcelable("net.thauvin.erik.android.tesremoteprogrammer.models.Params")
|
||||
val option: Option = intent.extras.getParcelable("net.thauvin.erik.android.tesremoteprogrammer.models.Option")
|
||||
val fields = arrayListOf<EditText>()
|
||||
|
||||
coordinatorLayout {
|
||||
|
||||
textView {
|
||||
padding = dip(20)
|
||||
text = option.title
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20f)
|
||||
typeface = Typeface.DEFAULT_BOLD
|
||||
isFocusableInTouchMode = true
|
||||
singleLine = true
|
||||
ellipsize = TextUtils.TruncateAt.END
|
||||
}
|
||||
|
||||
nestedScrollView {
|
||||
topPadding = dip(55)
|
||||
horizontalPadding = dip(20)
|
||||
|
||||
verticalLayout {
|
||||
lparams(width = matchParent, height = wrapContent)
|
||||
|
||||
|
||||
val it = option.fields.iterator()
|
||||
while (it.hasNext()) {
|
||||
val field = it.next()
|
||||
|
||||
textInputLayout {
|
||||
horizontalPadding = dip(40)
|
||||
lparams(width = matchParent)
|
||||
|
||||
val inputFilters: ArrayList<InputFilter> = ArrayList()
|
||||
|
||||
val editText = editText() {
|
||||
hint = field.hint
|
||||
|
||||
if (field.alpha) {
|
||||
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
|
||||
inputFilters.add(AlphaFilter())
|
||||
} else {
|
||||
inputType = InputType.TYPE_CLASS_NUMBER
|
||||
keyListener = DigitsKeyListener.getInstance("0,1,2,3,4,5,6,7,8,9" +
|
||||
if (field.hash) ",${params.hash}" else "")
|
||||
if (field.max != -1 && field.min != -1) {
|
||||
inputFilters.add(MinMaxFilter(field.min, field.max, field.size))
|
||||
}
|
||||
}
|
||||
|
||||
if (field.size != -1) {
|
||||
inputFilters.add(InputFilter.LengthFilter(field.size))
|
||||
}
|
||||
|
||||
if (inputFilters.isNotEmpty()) {
|
||||
filters = inputFilters.toTypedArray()
|
||||
}
|
||||
|
||||
if (!it.hasNext()) {
|
||||
imeOptions = EditorInfo.IME_ACTION_DONE
|
||||
setOnEditorActionListener { v, id, event ->
|
||||
if (id == EditorInfo.IME_ACTION_DONE) {
|
||||
clearFocus()
|
||||
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.hideSoftInputFromWindow(windowToken, 0)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fields.add(editText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.lparams(width = matchParent, height = matchParent)
|
||||
|
||||
floatingActionButton {
|
||||
imageResource = R.drawable.ic_menu_dialpad_lt
|
||||
|
||||
if (!option.nodial) {
|
||||
backgroundTintList = ColorStateList.valueOf(Color.GRAY)
|
||||
}
|
||||
|
||||
onClick {
|
||||
if (validateFields(fields, option)) {
|
||||
val dtmf = Dtmf.build(params.master, params.star, option, fields)
|
||||
if (Dtmf.validate(dtmf, "${MainActivity.PAUSE}${params.star}${params.hash}")) {
|
||||
startActivity<StepsActivity>(
|
||||
StepsActivity.EXTRA_STEPS to "$dtmf${MainActivity.PAUSE}${params.end}".split(','))
|
||||
} else {
|
||||
Snackbar.make(this@coordinatorLayout, getString(R.string.error_invalid_dtmf, dtmf),
|
||||
Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
} else {
|
||||
Snackbar.make(this@coordinatorLayout, R.string.error_invalid_field,
|
||||
Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}.lparams(width = wrapContent, height = wrapContent) {
|
||||
if (option.nodial) {
|
||||
gravity = BOTTOM or END
|
||||
rightMargin = dip(16)
|
||||
} else {
|
||||
gravity = BOTTOM or START
|
||||
leftMargin = dip(16)
|
||||
}
|
||||
|
||||
bottomMargin = dip(16)
|
||||
elevation = dip(6).toFloat()
|
||||
behavior = ScrollAwareFABBehavior()
|
||||
}
|
||||
|
||||
if (!option.nodial) {
|
||||
floatingActionButton {
|
||||
imageResource = R.drawable.fab_ic_call
|
||||
onClick {
|
||||
if (validateFields(fields, option)) {
|
||||
val dtmf = Dtmf.build(params.master, params.star, option, fields)
|
||||
if (Dtmf.validate(dtmf, "${MainActivity.PAUSE}${params.star}${params.hash}")) {
|
||||
ProgrammingActivityPermissionsDispatcher.callWithCheck(
|
||||
this@ProgrammingActivity, params.phone, dtmf)
|
||||
} else {
|
||||
Snackbar.make(this@coordinatorLayout, getString(R.string.error_invalid_dtmf, dtmf),
|
||||
Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
} else {
|
||||
Snackbar.make(this@coordinatorLayout, R.string.error_invalid_field,
|
||||
Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}.lparams(width = wrapContent, height = wrapContent) {
|
||||
gravity = BOTTOM or END
|
||||
bottomMargin = dip(16)
|
||||
rightMargin = dip(16)
|
||||
elevation = dip(6).toFloat()
|
||||
behavior = ScrollAwareFABBehavior()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
ProgrammingActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults)
|
||||
}
|
||||
|
||||
@NeedsPermission(Manifest.permission.CALL_PHONE)
|
||||
fun call(phone: String, dtmf: String) {
|
||||
info("$phone${MainActivity.PAUSE}${MainActivity.PAUSE}$dtmf")
|
||||
makeCall("$phone${MainActivity.PAUSE}${MainActivity.PAUSE}$dtmf")
|
||||
}
|
||||
|
||||
fun validateFields(fields: ArrayList<EditText>, option: Option): Boolean {
|
||||
var isValid = true
|
||||
|
||||
fields.forEachIndexed { i, v ->
|
||||
if (v.text.isNullOrBlank()) {
|
||||
v.error = getString(R.string.error_required)
|
||||
isValid = false
|
||||
} else {
|
||||
val size = option.fields[i].size
|
||||
if (!option.fields[i].alpha && size > 0 && (v.length() != size)) {
|
||||
v.error = getString(R.string.error_invalid_size, size,
|
||||
getString(R.string.error_digit).plural(size))
|
||||
isValid = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isValid
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* StepsActivity.java
|
||||
*
|
||||
* Copyright 2016 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.thauvin.erik.android.tesremoteprogrammer;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentManager;
|
||||
import android.os.Bundle;
|
||||
import android.support.v13.app.FragmentStatePagerAdapter;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.view.PagerAdapter;
|
||||
import android.support.v4.view.ViewPager;
|
||||
|
||||
import com.viewpagerindicator.UnderlinePageIndicator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class StepsActivity extends FragmentActivity {
|
||||
|
||||
public static final String EXTRA_STEPS = "steps";
|
||||
|
||||
private final ArrayList<String> steps = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_steps);
|
||||
|
||||
steps.clear();
|
||||
steps.addAll(getIntent().getExtras().getStringArrayList(EXTRA_STEPS));
|
||||
|
||||
final ViewPager pager = (ViewPager) findViewById(R.id.pager);
|
||||
final PagerAdapter pagerAdapter = new StepsAdapter(getFragmentManager(), steps);
|
||||
pager.setAdapter(pagerAdapter);
|
||||
final UnderlinePageIndicator pageIndicator = (UnderlinePageIndicator) findViewById(R.id.indicator);
|
||||
pageIndicator.setViewPager(pager, pager.getCurrentItem() - 1);
|
||||
pageIndicator.setFades(false);
|
||||
}
|
||||
|
||||
private class StepsAdapter extends FragmentStatePagerAdapter {
|
||||
private final ArrayList<String> stepsArray = new ArrayList<>();
|
||||
|
||||
public StepsAdapter(FragmentManager fm, ArrayList<String> steps) {
|
||||
super(fm);
|
||||
|
||||
stepsArray.clear();
|
||||
stepsArray.addAll(steps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
return StepsFragment.create(position, stepsArray);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return stepsArray.size();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* StepsFragment.java
|
||||
*
|
||||
* Copyright 2016 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.thauvin.erik.android.tesremoteprogrammer;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class StepsFragment extends Fragment {
|
||||
public static final String ARG_PAGE = "page";
|
||||
private static final ArrayList<String> stepsArray = new ArrayList<>();
|
||||
private int pageNumber;
|
||||
|
||||
public StepsFragment() {
|
||||
}
|
||||
|
||||
public static StepsFragment create(int pageNumber, ArrayList<String> steps) {
|
||||
final StepsFragment fragment = new StepsFragment();
|
||||
Bundle args = new Bundle();
|
||||
|
||||
args.putInt(ARG_PAGE, pageNumber);
|
||||
fragment.setArguments(args);
|
||||
|
||||
stepsArray.clear();
|
||||
stepsArray.addAll(steps);
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
pageNumber = getArguments().getInt(ARG_PAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
final ViewGroup rootView = (ViewGroup) inflater
|
||||
.inflate(R.layout.fragment_steps, container, false);
|
||||
|
||||
((TextView) rootView.findViewById(R.id.frag_steps_title)).setText(
|
||||
getString(R.string.title_template_step, pageNumber + 1, stepsArray.size()));
|
||||
((TextView) rootView.findViewById(R.id.frag_steps)).setText(stepsArray.get(pageNumber));
|
||||
return rootView;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* AlphaFilter.kt
|
||||
*
|
||||
* Copyright 2016 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.thauvin.erik.android.tesremoteprogrammer.filters
|
||||
|
||||
import android.text.InputFilter
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
|
||||
class AlphaFilter : InputFilter {
|
||||
override fun filter(source: CharSequence,
|
||||
start: Int,
|
||||
end: Int,
|
||||
dest: Spanned,
|
||||
dstart: Int,
|
||||
dend: Int): CharSequence? {
|
||||
|
||||
if (source is SpannableStringBuilder) {
|
||||
for (i in end - 1 downTo start) {
|
||||
val c = source[i]
|
||||
if (!c.isLetterOrDigit() && !Character.isSpaceChar(c)) {
|
||||
source.delete(i, i + 1)
|
||||
}
|
||||
}
|
||||
return source
|
||||
} else {
|
||||
val sb = StringBuilder()
|
||||
for (i in start..end - 1) {
|
||||
val c = source[i]
|
||||
if (c.isLetterOrDigit() || Character.isSpaceChar(c)) {
|
||||
sb.append(c.toUpperCase())
|
||||
}
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* MinMaxFilter.kt
|
||||
*
|
||||
* Copyright 2016 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.thauvin.erik.android.tesremoteprogrammer.filters
|
||||
|
||||
import android.text.InputFilter
|
||||
import android.text.Spanned
|
||||
|
||||
class MinMaxFilter : InputFilter {
|
||||
private var min: Int = 0
|
||||
private var max: Int = 0
|
||||
private var size: Int = 0
|
||||
|
||||
constructor(min: Int, max: Int, size: Int) {
|
||||
this.min = min
|
||||
this.max = max
|
||||
this.size = size
|
||||
}
|
||||
|
||||
override fun filter(source: CharSequence, start: Int, end: Int, dest: Spanned, dstart: Int, dend: Int): CharSequence? {
|
||||
val input = (dest.toString() + source.toString()).toInt()
|
||||
val len = dest.length + source.length
|
||||
if ((min > 0 && size > 1 && len < size && input == 0) || input in IntRange(min, max)) {
|
||||
return null
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Config.kt
|
||||
*
|
||||
* Copyright 2016 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.thauvin.erik.android.tesremoteprogrammer.models
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import java.io.Serializable
|
||||
|
||||
data class Config(var params: Params, var opts: List<Option>) : Parcelable, Serializable {
|
||||
companion object {
|
||||
private @JvmStatic val serialVersionUID: Long = 1
|
||||
|
||||
@JvmField val CREATOR: Parcelable.Creator<Config> = object : Parcelable.Creator<Config> {
|
||||
override fun createFromParcel(source: Parcel): Config = Config(source)
|
||||
override fun newArray(size: Int): Array<Config?> = arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
|
||||
constructor() : this (Params(), emptyList<Option>())
|
||||
|
||||
constructor(source: Parcel) : this(
|
||||
source.readParcelable<Params>(Params::class.java.classLoader),
|
||||
source.createTypedArrayList(Option.CREATOR))
|
||||
|
||||
override fun describeContents() = 0
|
||||
|
||||
override fun writeToParcel(dest: Parcel?, flags: Int) {
|
||||
dest?.writeParcelable(params, 0)
|
||||
dest?.writeTypedList(opts)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Configurations.kt
|
||||
*
|
||||
* Copyright 2016 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.thauvin.erik.android.tesremoteprogrammer.models
|
||||
|
||||
import java.io.Serializable
|
||||
import java.util.*
|
||||
|
||||
class Configurations : Serializable {
|
||||
companion object {
|
||||
private @JvmStatic val serialVersionUID: Long = 1
|
||||
}
|
||||
|
||||
val configs = HashMap<String, Config>()
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Field.kt
|
||||
*
|
||||
* Copyright 2016 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.thauvin.erik.android.tesremoteprogrammer.models
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import java.io.Serializable
|
||||
|
||||
data class Field(var hint: String,
|
||||
var alpha: Boolean,
|
||||
val hash: Boolean,
|
||||
var size: Int,
|
||||
var min: Int,
|
||||
var max: Int) : Parcelable, Serializable {
|
||||
|
||||
companion object {
|
||||
private @JvmStatic val serialVersionUID: Long = 1
|
||||
|
||||
@JvmField val CREATOR: Parcelable.Creator<Field> = object : Parcelable.Creator<Field> {
|
||||
override fun createFromParcel(source: Parcel): Field = Field(source)
|
||||
override fun newArray(size: Int): Array<Field?> = arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
|
||||
constructor() : this("", false, false, -1, -1, -1)
|
||||
|
||||
constructor(source: Parcel) : this(
|
||||
source.readString(),
|
||||
1.equals(source.readInt()),
|
||||
1.equals(source.readInt()),
|
||||
source.readInt(),
|
||||
source.readInt(),
|
||||
source.readInt())
|
||||
|
||||
override fun describeContents() = 0
|
||||
|
||||
override fun writeToParcel(dest: Parcel?, flags: Int) {
|
||||
dest?.writeString(hint)
|
||||
dest?.writeInt((if (alpha) 1 else 0))
|
||||
dest?.writeInt((if (hash) 1 else 0))
|
||||
dest?.writeInt(size)
|
||||
dest?.writeInt(min)
|
||||
dest?.writeInt(max)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Option.kt
|
||||
*
|
||||
* Copyright 2016 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.thauvin.erik.android.tesremoteprogrammer.models
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import java.io.Serializable
|
||||
|
||||
data class Option(var title: String,
|
||||
var fields: List<Field>,
|
||||
var nodial: Boolean,
|
||||
var dtmf: String) : Parcelable, Serializable, Comparable<Option> {
|
||||
|
||||
companion object {
|
||||
private @JvmStatic val serialVersionUID: Long = 1
|
||||
|
||||
@JvmField val CREATOR: Parcelable.Creator<Option> = object : Parcelable.Creator<Option> {
|
||||
override fun createFromParcel(source: Parcel): Option = Option(source)
|
||||
override fun newArray(size: Int): Array<Option?> = arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
|
||||
constructor() : this("", emptyList(), false, "")
|
||||
|
||||
constructor(source: Parcel) : this(
|
||||
source.readString(),
|
||||
source.createTypedArrayList(Field.CREATOR),
|
||||
1.equals(source.readInt()),
|
||||
source.readString())
|
||||
|
||||
override fun compareTo(other: Option): Int {
|
||||
return title.compareTo(other.title)
|
||||
}
|
||||
override fun describeContents() = 0
|
||||
|
||||
override fun writeToParcel(dest: Parcel?, flags: Int) {
|
||||
dest?.writeString(title)
|
||||
dest?.writeTypedList(fields)
|
||||
dest?.writeInt((if (nodial) 1 else 0))
|
||||
dest?.writeString(dtmf)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Params.kt
|
||||
*
|
||||
* Copyright 2016 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.thauvin.erik.android.tesremoteprogrammer.models
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import java.io.Serializable
|
||||
|
||||
data class Params(var name: String,
|
||||
var phone: String,
|
||||
var master: String,
|
||||
var size: Int,
|
||||
var star: String,
|
||||
var hash: String,
|
||||
var end: String) : Parcelable, Serializable {
|
||||
|
||||
companion object {
|
||||
private @JvmStatic val serialVersionUID: Long = 1
|
||||
|
||||
@JvmField val CREATOR: Parcelable.Creator<Params> = object : Parcelable.Creator<Params> {
|
||||
override fun createFromParcel(source: Parcel): Params = Params(source)
|
||||
override fun newArray(size: Int): Array<Params?> = arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
|
||||
constructor() : this("", "", "", -1, "", "", "")
|
||||
|
||||
constructor(source: Parcel) : this(
|
||||
source.readString(),
|
||||
source.readString(),
|
||||
source.readString(),
|
||||
source.readInt(),
|
||||
source.readString(),
|
||||
source.readString(),
|
||||
source.readString())
|
||||
|
||||
override fun describeContents() = 0
|
||||
|
||||
override fun writeToParcel(dest: Parcel?, flags: Int) {
|
||||
dest?.writeString(name)
|
||||
dest?.writeString(phone)
|
||||
dest?.writeString(master)
|
||||
dest?.writeInt(size)
|
||||
dest?.writeString(star)
|
||||
dest?.writeString(hash)
|
||||
dest?.writeString(end)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Dtmf.kt
|
||||
*
|
||||
* Copyright 2016 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.thauvin.erik.android.tesremoteprogrammer.util
|
||||
|
||||
import android.widget.EditText
|
||||
import net.thauvin.erik.android.tesremoteprogrammer.MainActivity
|
||||
import net.thauvin.erik.android.tesremoteprogrammer.models.Option
|
||||
import java.util.*
|
||||
|
||||
class Dtmf {
|
||||
companion object {
|
||||
private val DTMF_MASTER = "[MASTER]"
|
||||
private val DTMF_FIELD = "[FIELD:%1\$d]"
|
||||
|
||||
private fun alphaToDigits(text: String, star: String): String {
|
||||
val result = StringBuffer()
|
||||
|
||||
text.toUpperCase().forEach { c ->
|
||||
when (c) {
|
||||
'A' -> result.append("2$star${MainActivity.PAUSE}")
|
||||
'B' -> result.append("22$star${MainActivity.PAUSE}")
|
||||
'C' -> result.append("2222$star${MainActivity.PAUSE}")
|
||||
|
||||
'D' -> result.append("3$star${MainActivity.PAUSE}")
|
||||
'E' -> result.append("33$star${MainActivity.PAUSE}")
|
||||
'F' -> result.append("333$star${MainActivity.PAUSE}")
|
||||
|
||||
'G' -> result.append("4$star${MainActivity.PAUSE}")
|
||||
'H' -> result.append("44$star${MainActivity.PAUSE}")
|
||||
'I' -> result.append("444$star${MainActivity.PAUSE}")
|
||||
|
||||
'J' -> result.append("5$star${MainActivity.PAUSE}")
|
||||
'K' -> result.append("55$star${MainActivity.PAUSE}")
|
||||
'L' -> result.append("555$star${MainActivity.PAUSE}")
|
||||
|
||||
'M' -> result.append("6$star${MainActivity.PAUSE}")
|
||||
'N' -> result.append("66$star${MainActivity.PAUSE}")
|
||||
'O' -> result.append("666$star${MainActivity.PAUSE}")
|
||||
|
||||
'P' -> result.append("7$star${MainActivity.PAUSE}")
|
||||
'Q' -> result.append("77$star${MainActivity.PAUSE}")
|
||||
'R' -> result.append("777$star${MainActivity.PAUSE}")
|
||||
'S' -> result.append("7777$star${MainActivity.PAUSE}")
|
||||
|
||||
'T' -> result.append("8$star${MainActivity.PAUSE}")
|
||||
'U' -> result.append("88$star${MainActivity.PAUSE}")
|
||||
'V' -> result.append("888$star${MainActivity.PAUSE}")
|
||||
|
||||
'W' -> result.append("9$star${MainActivity.PAUSE}")
|
||||
'X' -> result.append("99$star${MainActivity.PAUSE}")
|
||||
'Y' -> result.append("999$star${MainActivity.PAUSE}")
|
||||
'Z' -> result.append("9999$star${MainActivity.PAUSE}")
|
||||
|
||||
'0' -> result.append("0$star${MainActivity.PAUSE}")
|
||||
'1' -> result.append("11$star${MainActivity.PAUSE}")
|
||||
'2' -> result.append("2222$star${MainActivity.PAUSE}")
|
||||
'3' -> result.append("3333$star${MainActivity.PAUSE}")
|
||||
'4' -> result.append("4444$star${MainActivity.PAUSE}")
|
||||
'5' -> result.append("5555$star${MainActivity.PAUSE}")
|
||||
'6' -> result.append("6666$star${MainActivity.PAUSE}")
|
||||
'7' -> result.append("77777$star${MainActivity.PAUSE}")
|
||||
'8' -> result.append("8888$star${MainActivity.PAUSE}")
|
||||
'9' -> result.append("99999$star${MainActivity.PAUSE}")
|
||||
|
||||
' ' -> result.append("1$star${MainActivity.PAUSE}")
|
||||
}
|
||||
}
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
fun build(master: String,
|
||||
star: String,
|
||||
option: Option,
|
||||
fields: ArrayList<EditText>): String {
|
||||
val replace = arrayListOf(Pair("$DTMF_MASTER", master))
|
||||
|
||||
fields.forEachIndexed { i, field ->
|
||||
replace.add(Pair(DTMF_FIELD.format(i + 1),
|
||||
if (option.fields[i].alpha) {
|
||||
alphaToDigits(field.text.toString(), star)
|
||||
} else {
|
||||
field.text.toString()
|
||||
}))
|
||||
}
|
||||
|
||||
return option.dtmf.replaceAll(replace.toTypedArray())
|
||||
}
|
||||
|
||||
fun validate(dtmf: String, extra: String): Boolean {
|
||||
dtmf.forEach {
|
||||
if (!(it.isDigit() || it.equals(',') || extra.contains(it))) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun mock(option: Option, blank: String): String {
|
||||
val replace = arrayListOf(Pair("$DTMF_MASTER", blank))
|
||||
|
||||
option.fields.forEachIndexed { i, field ->
|
||||
replace.add(Pair(DTMF_FIELD.format(i + 1), blank))
|
||||
}
|
||||
|
||||
return option.dtmf.replaceAll(replace.toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Strings.kt
|
||||
*
|
||||
* Copyright 2016 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.thauvin.erik.android.tesremoteprogrammer.util
|
||||
|
||||
fun String.plural(size: Int): String {
|
||||
val consonants = "bcdfghjklmnpqrstvwxz"
|
||||
|
||||
if (size > 1 && this.length > 2) {
|
||||
if ((this.endsWith("o", true) || this.endsWith("s", true)) &&
|
||||
consonants.contains(this[this.length - 2], true)) {
|
||||
return this + "es"
|
||||
}
|
||||
|
||||
if (this.endsWith("y", true) &&
|
||||
consonants.contains(this[this.length - 2], true)) {
|
||||
return this + "ies"
|
||||
}
|
||||
}
|
||||
|
||||
return this + "s"
|
||||
}
|
||||
|
||||
fun String.replaceAll(replace: Array<Pair<String, String>>): String {
|
||||
val result = StringBuilder(this)
|
||||
var offset: Int
|
||||
|
||||
replace.forEach {
|
||||
with(it) {
|
||||
if (first.isNotEmpty()) {
|
||||
offset = result.indexOf(first)
|
||||
|
||||
while (offset != -1) {
|
||||
result.replace(offset, first.length + offset, second)
|
||||
offset = result.indexOf(first, offset + second.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.toString()
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* ScrollAwareFABBehavior.kt
|
||||
*
|
||||
* Copyright 2016 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.thauvin.erik.android.tesremoteprogrammer.widget
|
||||
|
||||
import android.support.design.widget.CoordinatorLayout
|
||||
import android.support.design.widget.FloatingActionButton
|
||||
import android.support.v4.view.ViewCompat
|
||||
import android.view.View
|
||||
|
||||
class ScrollAwareFABBehavior() : FloatingActionButton.Behavior() {
|
||||
override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout?,
|
||||
child: FloatingActionButton?,
|
||||
directTargetChild: View?,
|
||||
target: View?,
|
||||
nestedScrollAxes: Int): Boolean {
|
||||
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL ||
|
||||
super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes)
|
||||
}
|
||||
|
||||
override fun onNestedScroll(coordinatorLayout: CoordinatorLayout?,
|
||||
child: FloatingActionButton?,
|
||||
target: View?,
|
||||
dxConsumed: Int,
|
||||
dyConsumed: Int,
|
||||
dxUnconsumed: Int,
|
||||
dyUnconsumed: Int) {
|
||||
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed)
|
||||
if (dyConsumed > 0 && child!!.visibility == View.VISIBLE) {
|
||||
child.hide()
|
||||
} else if (dyConsumed < 0 && child!!.visibility != View.VISIBLE) {
|
||||
child.show()
|
||||
}
|
||||
}
|
||||
}
|
BIN
app/src/main/res/drawable-hdpi/fab_ic_call.png
Normal file
After Width: | Height: | Size: 572 B |
BIN
app/src/main/res/drawable-hdpi/ic_call_black_24dp.png
Normal file
After Width: | Height: | Size: 326 B |
BIN
app/src/main/res/drawable-hdpi/ic_verified_user_black_24dp.png
Normal file
After Width: | Height: | Size: 390 B |
BIN
app/src/main/res/drawable-mdpi/fab_ic_call.png
Normal file
After Width: | Height: | Size: 438 B |
BIN
app/src/main/res/drawable-mdpi/ic_call_black_24dp.png
Normal file
After Width: | Height: | Size: 244 B |
BIN
app/src/main/res/drawable-mdpi/ic_verified_user_black_24dp.png
Normal file
After Width: | Height: | Size: 272 B |
BIN
app/src/main/res/drawable-xhdpi/fab_ic_call.png
Normal file
After Width: | Height: | Size: 681 B |
BIN
app/src/main/res/drawable-xhdpi/ic_call_black_24dp.png
Normal file
After Width: | Height: | Size: 408 B |
BIN
app/src/main/res/drawable-xhdpi/ic_menu_dialpad_lt.png
Normal file
After Width: | Height: | Size: 893 B |
BIN
app/src/main/res/drawable-xhdpi/ic_verified_user_black_24dp.png
Normal file
After Width: | Height: | Size: 445 B |
BIN
app/src/main/res/drawable-xxhdpi/fab_ic_call.png
Normal file
After Width: | Height: | Size: 670 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_call_black_24dp.png
Normal file
After Width: | Height: | Size: 574 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_menu_dialpad_lt.png
Normal file
After Width: | Height: | Size: 225 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_verified_user_black_24dp.png
Normal file
After Width: | Height: | Size: 634 B |
BIN
app/src/main/res/drawable-xxxhdpi/fab_ic_call.png
Normal file
After Width: | Height: | Size: 905 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_call_black_24dp.png
Normal file
After Width: | Height: | Size: 758 B |
After Width: | Height: | Size: 788 B |
18
app/src/main/res/layout/activity_steps.xml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<com.viewpagerindicator.UnderlinePageIndicator
|
||||
android:id="@+id/indicator"
|
||||
style="@style/UnderlinePageIndicator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="5dp" />
|
||||
</LinearLayout>
|
35
app/src/main/res/layout/fragment_steps.xml
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:weightSum="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/frag_steps_title"
|
||||
style="?android:textAppearanceLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/frag_steps"
|
||||
style="?android:textAppearanceLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:lineSpacingMultiplier="1.2"
|
||||
android:gravity="center"
|
||||
android:text=""
|
||||
android:layout_weight="0.8"
|
||||
android:textSize="78sp" />
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
15
app/src/main/res/menu/menu_main.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".MainActivity">
|
||||
<item
|
||||
android:id="@+id/action_config"
|
||||
android:orderInCategory="100"
|
||||
android:title="@string/action_config"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/action_about"
|
||||
android:orderInCategory="101"
|
||||
android:title="@string/action_about"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 13 KiB |
272
app/src/main/res/raw/dks_1802_epd.json
Normal file
|
@ -0,0 +1,272 @@
|
|||
{
|
||||
"params": {
|
||||
"name": "DKS 1802-EPD",
|
||||
"star": "*",
|
||||
"hash": "#",
|
||||
"end": "0 + #",
|
||||
"size": 4
|
||||
},
|
||||
"opts": [
|
||||
{
|
||||
"title": "Set Relay Strike Time",
|
||||
"fields": [
|
||||
{
|
||||
"hint": "Relay (1 or 2)",
|
||||
"min": 1,
|
||||
"max": 2,
|
||||
"size": 1
|
||||
},
|
||||
{
|
||||
"hint": "Strike Time (00=1/4...99 seconds)",
|
||||
"min": 0,
|
||||
"max": 99,
|
||||
"size": 2
|
||||
}
|
||||
],
|
||||
"dtmf": "*03[MASTER],[FIELD:1]*,[FIELD:2]*"
|
||||
},
|
||||
{
|
||||
"title": "Set Open Tone On/Off",
|
||||
"fields": [
|
||||
{
|
||||
"hint": "1=ON 0=OFF",
|
||||
"min": 0,
|
||||
"max": 1,
|
||||
"size": 1
|
||||
}
|
||||
],
|
||||
"dtmf": "*17[MASTER],[FIELD:1]*"
|
||||
},
|
||||
{
|
||||
"title": "Set Talk Time",
|
||||
"fields": [
|
||||
{
|
||||
"hint": "001-255 seconds",
|
||||
"min": 1,
|
||||
"max": 255,
|
||||
"size": 3
|
||||
}
|
||||
],
|
||||
"dtmf": "*08[MASTER],[FIELD:1]*"
|
||||
},
|
||||
{
|
||||
"title": "Set Number of Rings to Answer",
|
||||
"fields": [
|
||||
{
|
||||
"hint": "Number of Rings",
|
||||
"min": 1,
|
||||
"max": 99,
|
||||
"size": 2
|
||||
}
|
||||
],
|
||||
"dtmf": "*18[MASTER],[FIELD:1]*"
|
||||
},
|
||||
{
|
||||
"title": "Set PBX Line Access Code",
|
||||
"fields": [
|
||||
{
|
||||
"hint": "Line Access Code Number",
|
||||
"min": 1,
|
||||
"max": 9,
|
||||
"size": 1
|
||||
}
|
||||
],
|
||||
"dtmf": "*21[MASTER],[FIELD:1]*"
|
||||
},
|
||||
{
|
||||
"title": "Delete 4-Digit Entry Code",
|
||||
"fields": [
|
||||
{
|
||||
"hint": "Entry Code",
|
||||
"size": 4
|
||||
}
|
||||
],
|
||||
"dtmf": "*14[MASTER],[FIELD:1]*"
|
||||
},
|
||||
{
|
||||
"title": "Add 4-Digit Entry Code",
|
||||
"fields": [
|
||||
{
|
||||
"hint": "Entry Code",
|
||||
"size": 4
|
||||
}
|
||||
],
|
||||
"dtmf": "*02[MASTER],[FIELD:1]*"
|
||||
},
|
||||
{
|
||||
"title": "Add Name",
|
||||
"fields": [
|
||||
{
|
||||
"hint": "Directory Code",
|
||||
"size": 3
|
||||
},
|
||||
{
|
||||
"hint": "Name",
|
||||
"alpha": true,
|
||||
"size": 11
|
||||
}
|
||||
],
|
||||
"nodial": true,
|
||||
"dtmf": "*66[MASTER],[FIELD:1]*,[FIELD:2]*"
|
||||
},
|
||||
{
|
||||
"title": "Add 7-digit Phone Number",
|
||||
"fields": [
|
||||
{
|
||||
"hint": "Directory Code",
|
||||
"size": 3
|
||||
},
|
||||
{
|
||||
"hint": "Phone Number",
|
||||
"hash": true,
|
||||
"size": 7
|
||||
}
|
||||
],
|
||||
"dtmf": "*01[MASTER],[FIELD:1]*,[FIELD:2]*"
|
||||
},
|
||||
{
|
||||
"title": "Set Area Codes",
|
||||
"fields": [
|
||||
{
|
||||
"hint": "2-Digit Area Code Ref Number",
|
||||
"min": 1,
|
||||
"max" : 15,
|
||||
"size": 2
|
||||
},
|
||||
{
|
||||
"hint": "Area Code (e.g. 1800 or #800)",
|
||||
"hash" : true,
|
||||
"size": 4,
|
||||
"min": 1,
|
||||
"max": 15
|
||||
}
|
||||
],
|
||||
"dtmf": "*24[MASTER],[FIELD:1]*,[FIELD:2]*"
|
||||
},
|
||||
{
|
||||
"title": "Add Phone Number w/ Area Code",
|
||||
"fields": [
|
||||
{
|
||||
"hint": "Directory Code",
|
||||
"size": 3
|
||||
},
|
||||
{
|
||||
"hint": "Area Code",
|
||||
"size": 2,
|
||||
"min": 1,
|
||||
"max": 15
|
||||
},
|
||||
{
|
||||
"hint": "Phone Number",
|
||||
"hash": true,
|
||||
"size": 7
|
||||
}
|
||||
],
|
||||
"dtmf": "*41[MASTER],[FIELD:1]*,[FIELD:2]*,[FIELD:3]*"
|
||||
},
|
||||
{
|
||||
"title": "Remote Relay Activation",
|
||||
"fields": [
|
||||
{
|
||||
"hint": "Open=9, Hold=8, Release=7, 1hr=6",
|
||||
"size": 1,
|
||||
"min": 6,
|
||||
"max": 9
|
||||
}
|
||||
],
|
||||
"dtmf": "*16[MASTER],[FIELD:1]*"
|
||||
},
|
||||
{
|
||||
"title": "Set Welcome Message",
|
||||
"fields": [
|
||||
{
|
||||
"hint": "Message (Line 1)",
|
||||
"size": 16,
|
||||
"alpha" : true
|
||||
},
|
||||
{
|
||||
"hint": "Message (Line 2)",
|
||||
"size": 16,
|
||||
"alpha" : true
|
||||
},
|
||||
{
|
||||
"hint": "Message (Line 3)",
|
||||
"size": 16,
|
||||
"alpha" : true
|
||||
}
|
||||
],
|
||||
"nodial": true,
|
||||
"dtmf": "*80[MASTER],[FIELD:1]*,[FIELD:2]*,[FIELD:3]*"
|
||||
},
|
||||
{
|
||||
"title": "Set Instruction Message",
|
||||
"fields": [
|
||||
{
|
||||
"hint": "Message (Line 1)",
|
||||
"size": 16,
|
||||
"alpha" : true
|
||||
},
|
||||
{
|
||||
"hint": "Message (Line 2)",
|
||||
"size": 16,
|
||||
"alpha" : true
|
||||
},
|
||||
{
|
||||
"hint": "Message (Line 3)",
|
||||
"size": 16,
|
||||
"alpha" : true
|
||||
}
|
||||
],
|
||||
"nodial": true,
|
||||
"dtmf": "*81[MASTER],[FIELD:1]*,[FIELD:2]*,[FIELD:3]*"
|
||||
},
|
||||
{
|
||||
"title": "Set Time Clock",
|
||||
"fields": [
|
||||
{
|
||||
"hint": "Hour (01..12)",
|
||||
"size": 2,
|
||||
"min": 1,
|
||||
"max": 12
|
||||
},
|
||||
{
|
||||
"hint": "Minutes (00..59)",
|
||||
"size": 2,
|
||||
"min": 0,
|
||||
"max": 59
|
||||
},
|
||||
{
|
||||
"hint": "AM=0 PM=1",
|
||||
"size": 1,
|
||||
"min": 0,
|
||||
"max": 1
|
||||
},
|
||||
{
|
||||
"hint": "Month (01..12)",
|
||||
"size": 2,
|
||||
"min": 1,
|
||||
"max": 12
|
||||
},
|
||||
{
|
||||
"hint": "Day of Month (01..31)",
|
||||
"size": 2,
|
||||
"min": 1,
|
||||
"max": 31
|
||||
},
|
||||
{
|
||||
"hint": "Year (Even=00, Odd=01)",
|
||||
"size": 2,
|
||||
"min": 0,
|
||||
"max": 1
|
||||
},
|
||||
{
|
||||
"hint": "Day of Week (Sun=1..Sat=7)",
|
||||
"size": 1,
|
||||
"min": 1,
|
||||
"max": 7
|
||||
}
|
||||
],
|
||||
"dtmf": "*33[MASTER],[FIELD:1][FIELD:2]*,[FIELD:3]*,[FIELD:4]*,[FIELD:5]*,[FIELD:6]*,[FIELD:7]*"
|
||||
}
|
||||
]
|
||||
}
|
6
app/src/main/res/values-w820dp/dimens.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
|
||||
(such as screen margins) for screens with more than 820dp of available width. This
|
||||
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
|
||||
<dimen name="activity_horizontal_margin">64dp</dimen>
|
||||
</resources>
|
6
app/src/main/res/values/colors.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#3F51B5</color>
|
||||
<color name="colorPrimaryDark">#303F9F</color>
|
||||
<color name="colorAccent">#3F51B5</color>
|
||||
</resources>
|
5
app/src/main/res/values/dimens.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
</resources>
|
23
app/src/main/res/values/strings.xml
Normal file
|
@ -0,0 +1,23 @@
|
|||
<resources xmlns:xliff="http://schemas.android.com/tools">
|
||||
<string name="about_message"><p><b>Version <xliff:g id="type">%1$s</xliff:g></b></p><p>Developed by <a href=\"http://erik.thauvin.net/\"><b>Erik C. Thauvin</b></a>. See all my apps <a href=\"http://mobile.thauvin.net/android/\"><b>here</b></a>.</p><a href=\"http://m.thauvin.net/android\"><b>m.thauvin.net/android</b></a></string>
|
||||
<string name="action_about">About</string>
|
||||
<string name="action_config">Configurations</string>
|
||||
<string name="alert_config_error">Configuration Errors</string>
|
||||
<string name="app_name">TES Remote Programmer</string>
|
||||
<string name="dialog_import">Import</string>
|
||||
<string name="error_digit">digit</string>
|
||||
<string name="error_invalid_dtmf">Invalid DTMF: <xliff:g id="type">%1$s</xliff:g></string>
|
||||
<string name="error_invalid_field">Missing or invalid fields data.</string>
|
||||
<string name="error_invalid_size">Invalid: <xliff:g id="size">%1$d</xliff:g> <xliff:g id="type">%2$s</xliff:g> required</string>
|
||||
<string name="error_required">Required</string>
|
||||
<string name="hint_master_code">Master Code</string>
|
||||
<string name="hint_phone_number">Phone Number</string>
|
||||
<string name="programming_heading">PROGRAMMING</string>
|
||||
<string name="title_template_step">Step <xliff:g id="step_number">%1$d</xliff:g> of <xliff:g id="steps_count">%2$d</xliff:g></string>
|
||||
<string name="validate_invalid_attr"><p><b>opts[<xliff:g id="opts">%1$d</xliff:g>]</b>, <b>fields[<xliff:g id="field">%2$d</xliff:g>]</b>: <font color=\"red\"><xliff:g id="attr">%3$s</xliff:g></font> invalid</string>
|
||||
<string name="validate_invalid_dtmf"><p><b>opts[<xliff:g id="opts">%1$d</xliff:g>]</b>, <font color=\"red\">dtmf</font> invalid:<br>    <font color=\"red\"><small><xliff:g id="dtmf">%2$s</xliff:g></small></font></p></string>
|
||||
<string name="validate_invalid_param"><p><b>params</b>: <font color=\"red\"><xliff:g id="param">%1$s</xliff:g></font> invalid</p></string>
|
||||
<string name="validate_missing_fields"><p><b>opts[<xliff:g id="opts">%1$d</xliff:g>]</b>: <font color=\"red\">fields</font> missing</string>
|
||||
<string name="validate_missing_param"><p><p><b>params</b>: <font color=\"red\"><xliff:g id="param">%1$s</xliff:g></font> missing</p></string>
|
||||
<string name="validate_missing_ops"><font color=\"red\">opts</font> missing.</string>
|
||||
</resources>
|
13
app/src/main/res/values/styles.xml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<resources>
|
||||
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="UnderlinePageIndicator">
|
||||
<item name="selectedColor">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
5
app/src/main/res/xml/mybackupscheme.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<full-backup-content>
|
||||
<include domain="file" path="configurations.dat"/>
|
||||
<include domain="file" path="config.dat"/>
|
||||
</full-backup-content>
|