Initial commit.

This commit is contained in:
Erik C. Thauvin 2016-08-24 19:05:38 -07:00
commit 548759c61e
90 changed files with 5692 additions and 0 deletions

1
app/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

56
app/build.gradle Normal file
View 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
View 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 *;
#}

View 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>

View file

@ -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, "&#10003;")))
}
}
}
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
}
}

View file

@ -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
}
}

View file

@ -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();
}
}
}

View file

@ -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;
}
}

View file

@ -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()
}
}
}

View file

@ -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 ""
}
}

View file

@ -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)
}
}

View file

@ -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>()
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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())
}
}
}

View file

@ -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()
}

View file

@ -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()
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 893 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 B

View 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>

View 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>

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View 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]*"
}
]
}

View 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>

View 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>

View 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>

View file

@ -0,0 +1,23 @@
<resources xmlns:xliff="http://schemas.android.com/tools">
<string name="about_message">&lt;p>&lt;b>Version <xliff:g id="type">%1$s</xliff:g>&lt;/b>&lt;/p>&lt;p>Developed by &lt;a href=\"http://erik.thauvin.net/\">&lt;b>Erik C. Thauvin&lt;/b>&lt;/a>. See all my apps &lt;a href=\"http://mobile.thauvin.net/android/\">&lt;b>here&lt;/b>&lt;/a>.&lt;/p>&lt;a href=\"http://m.thauvin.net/android\">&lt;b>m.thauvin.net/android&lt;/b>&lt;/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">&lt;p>&lt;b>opts[<xliff:g id="opts">%1$d</xliff:g>]&lt;/b>, &lt;b>fields[<xliff:g id="field">%2$d</xliff:g>]&lt;/b>: &lt;font color=\"red\"><xliff:g id="attr">%3$s</xliff:g>&lt;/font> invalid</string>
<string name="validate_invalid_dtmf">&lt;p>&lt;b>opts[<xliff:g id="opts">%1$d</xliff:g>]&lt;/b>, &lt;font color=\"red\">dtmf&lt;/font> invalid:&lt;br>&#160;&#160;&#160;&#160;&lt;font color=\"red\">&lt;small><xliff:g id="dtmf">%2$s</xliff:g>&lt;/small>&lt;/font>&lt;/p></string>
<string name="validate_invalid_param">&lt;p>&lt;b>params&lt;/b>: &lt;font color=\"red\"><xliff:g id="param">%1$s</xliff:g>&lt;/font> invalid&lt;/p></string>
<string name="validate_missing_fields">&lt;p>&lt;b>opts[<xliff:g id="opts">%1$d</xliff:g>]&lt;/b>: &lt;font color=\"red\">fields&lt;/font> missing</string>
<string name="validate_missing_param">&lt;p>&lt;p>&lt;b>params&lt;/b>: &lt;font color=\"red\"><xliff:g id="param">%1$s</xliff:g>&lt;/font> missing&lt;/p></string>
<string name="validate_missing_ops">&lt;font color=\"red\">opts&lt;/font> missing.</string>
</resources>

View 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>

View 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>