├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── thanksmister
│ │ └── iot
│ │ └── esp8266
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── thanksmister
│ │ │ └── iot
│ │ │ └── esp8266
│ │ │ ├── BaseActivity.kt
│ │ │ ├── BaseApplication.kt
│ │ │ ├── BaseFragment.kt
│ │ │ ├── api
│ │ │ ├── ApiResponse.java
│ │ │ ├── EspApi.kt
│ │ │ ├── EspService.kt
│ │ │ ├── MessageResponse.java
│ │ │ ├── NetworkResponse.java
│ │ │ ├── Status.java
│ │ │ └── adapters
│ │ │ │ └── DataTypeAdapterFactory.java
│ │ │ ├── di
│ │ │ ├── ActivityModule.java
│ │ │ ├── ActivityScope.java
│ │ │ ├── AndroidBindingModule.kt
│ │ │ ├── ApplicationComponent.java
│ │ │ ├── ApplicationModule.java
│ │ │ ├── ApplicationScope.java
│ │ │ ├── DaggerViewModelFactory.kt
│ │ │ ├── DaggerViewModelInjectionModule.kt
│ │ │ └── ViewModelKey.kt
│ │ │ ├── manager
│ │ │ ├── ConnectionManager.kt
│ │ │ ├── WiFiReceiverManager.kt
│ │ │ ├── WiFiResponse.java
│ │ │ └── WiFiStatus.java
│ │ │ ├── persistence
│ │ │ ├── AppDatabase.kt
│ │ │ ├── MessageDao.kt
│ │ │ └── Preferences.kt
│ │ │ ├── ui
│ │ │ ├── ConnectionLiveData.kt
│ │ │ ├── LogActivity.kt
│ │ │ ├── LogFragment.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── MainFragment.kt
│ │ │ ├── SettingsActivity.kt
│ │ │ ├── TransmitFragment.kt
│ │ │ ├── adapters
│ │ │ │ └── MessageAdapter.kt
│ │ │ ├── controls
│ │ │ │ └── CustomViewPager.java
│ │ │ └── views
│ │ │ │ └── DialogTextView.kt
│ │ │ ├── util
│ │ │ ├── DateUtils.java
│ │ │ ├── DialogUtils.kt
│ │ │ ├── Extensions.kt
│ │ │ └── IntentUtils.kt
│ │ │ ├── viewmodel
│ │ │ ├── AlertMessage.java
│ │ │ ├── MainViewModel.kt
│ │ │ ├── MessageViewModel.kt
│ │ │ ├── SingleLiveEvent.java
│ │ │ ├── SnackbarMessage.java
│ │ │ ├── ToastMessage.java
│ │ │ └── TransmitViewModel.kt
│ │ │ └── vo
│ │ │ ├── Message.kt
│ │ │ ├── Resource.java
│ │ │ └── Status.java
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── button_blue.xml
│ │ ├── button_blue_pressed.xml
│ │ ├── button_blue_selector.xml
│ │ ├── button_disabled.xml
│ │ ├── button_gray.xml
│ │ ├── button_gray_pressed.xml
│ │ ├── button_gray_selector.xml
│ │ ├── button_green.xml
│ │ ├── button_green_pressed.xml
│ │ ├── button_green_selector.xml
│ │ ├── button_green_small.xml
│ │ ├── button_green_small_pressed.xml
│ │ ├── button_green_small_selector.xml
│ │ ├── button_red.xml
│ │ ├── button_red_pressed.xml
│ │ ├── button_red_selector.xml
│ │ ├── button_red_small.xml
│ │ ├── button_red_small_pressed.xml
│ │ ├── button_red_small_selector.xml
│ │ ├── button_text_selector.xml
│ │ ├── button_transparent.xml
│ │ ├── button_white_small.xml
│ │ ├── button_white_small_pressed.xml
│ │ ├── button_white_small_selector.xml
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ ├── activity_logs.xml
│ │ ├── activity_main.xml
│ │ ├── activity_settings.xml
│ │ ├── adapter_logs.xml
│ │ ├── content_main.xml
│ │ ├── content_transmit.xml
│ │ ├── dialog_password_view.xml
│ │ ├── dialog_text_view.xml
│ │ ├── fragment_logs.xml
│ │ ├── fragment_main.xml
│ │ └── fragment_transmit.xml
│ │ ├── menu
│ │ └── menu_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── thanksmister
│ └── iot
│ └── esp8266
│ └── ExampleUnitTest.kt
├── build.gradle
├── connect.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
11 |
12 | # Built application files
13 | *.apk
14 | *.ap_
15 |
16 | # Files for the ART/Dalvik VM
17 | *.dex
18 |
19 | # Java class files
20 | *.class
21 |
22 | # Generated files
23 | bin/
24 | gen/
25 | out/
26 |
27 | # Gradle files
28 | .gradle/
29 | build/
30 |
31 | # Local configuration file (sdk path, etc)
32 | local.properties
33 |
34 | # Proguard folder generated by Eclipse
35 | proguard/
36 |
37 | # Log Files
38 | *.log
39 |
40 | # Android Studio Navigation editor temp files
41 | .navigation/
42 |
43 | # Android Studio captures folder
44 | captures/
45 |
46 | # Intellij
47 | *.iml
48 | .idea/workspace.xml
49 | .idea/tasks.xml
50 | .idea/gradle.xml
51 | .idea/dictionaries
52 | .idea/libraries
53 |
54 | # Keystore files
55 | *.jks
56 |
57 | # External native build folder generated in Android Studio 2.2 and later
58 | .externalNativeBuild
59 |
60 | # Google Services (e.g. APIs or Firebase)
61 | google-services.json
62 |
63 | # Freeline
64 | freeline.py
65 | freeline/
66 | freeline_project_description.json
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ESP8266 Connect Application
2 |
3 | An Android application that connects to an ESP8266 WiFi module through a shared access point and sends command to the server to turn on and off a light. See the accompanying [Arduino project](https://github.com/thanksmister/arduino-ESP8266-connect) for the ESP8266 for software and hardware requirements to run the project.
4 |
5 | [Hackster Project](https://www.hackster.io/thanksmister/android-to-esp8266-comunication-a84f50)
6 |
7 | # Hardware Requirements
8 |
9 | - Android Device running Android SDK 15 or above
10 | - ESP8266 and other hardware (see the [Arduino project](https://github.com/thanksmister/arduino-ESP8266-connect)).
11 |
12 | # Software Requirements
13 |
14 | - Android IDE if you wish to edit the code, otherwise side load the APK file from the release secton.
15 | - Android SDK 15 or above
16 |
17 | # Setup
18 |
19 | From the release section download the APK file from the Android Github repository release section and side load onto your mobile device. If you have the ESP8266 project setup and running, the Android application will be able to automatically connect to the access point using the default IP address, access point name and password. These can be changed in the application settings.
20 |
21 | Open the installed application on your device and select the "Connect" button. This will connect to the access point with the default name and password. If you wish to change these, you can do so under the settings from the drop down menu.
22 |
23 | Once you connect, you can use the "Send" buttons below the values to toggle on/off the LED light. Use the "Disconnect" button to disconnect from the access point and return reconnect to your previous WiFi connection.
24 |
25 | 
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
11 |
12 | # Built application files
13 | *.apk
14 | *.ap_
15 |
16 | # Files for the ART/Dalvik VM
17 | *.dex
18 |
19 | # Java class files
20 | *.class
21 |
22 | # Generated files
23 | bin/
24 | gen/
25 | out/
26 |
27 | # Gradle files
28 | .gradle/
29 | build/
30 |
31 | # Local configuration file (sdk path, etc)
32 | local.properties
33 |
34 | # Proguard folder generated by Eclipse
35 | proguard/
36 |
37 | # Log Files
38 | *.log
39 |
40 | # Android Studio Navigation editor temp files
41 | .navigation/
42 |
43 | # Android Studio captures folder
44 | captures/
45 |
46 | # Intellij
47 | *.iml
48 | .idea/workspace.xml
49 | .idea/tasks.xml
50 | .idea/gradle.xml
51 | .idea/dictionaries
52 | .idea/libraries
53 |
54 | # Keystore files
55 | *.jks
56 |
57 | # External native build folder generated in Android Studio 2.2 and later
58 | .externalNativeBuild
59 |
60 | # Google Services (e.g. APIs or Firebase)
61 | google-services.json
62 |
63 | # Freeline
64 | freeline.py
65 | freeline/
66 | freeline_project_description.json
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | apply plugin: 'com.android.application'
18 | apply plugin: 'kotlin-android-extensions'
19 | apply plugin: 'kotlin-android'
20 | apply plugin: 'kotlin-kapt'
21 |
22 | def versionMajor = 0
23 | def versionMinor = 1
24 | def versionPatch = 0
25 | def versionBuild = 2 // bump for dog food builds, public betas, etc.
26 |
27 | android {
28 | kapt {
29 | generateStubs = true
30 | }
31 | compileSdkVersion 27
32 | defaultConfig {
33 | applicationId "com.thanksmister.iot.esp8266"
34 | minSdkVersion 15
35 | targetSdkVersion 27
36 | versionCode 1
37 | versionName "1.0"
38 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
39 | }
40 | buildTypes {
41 | release {
42 | minifyEnabled false
43 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
44 | }
45 | }
46 |
47 | flavorDimensions "default"
48 | productFlavors {
49 | def BASE_ENVIRONMENT = "BASE_ENVIRONMENT"
50 | dev {
51 | dimension "default"
52 | buildConfigField "String", BASE_ENVIRONMENT, '"DEV_ENVIRONMENT"'
53 | applicationId "com.thanksmister.iot.esp8266"
54 | versionName "${versionMajor}.${versionMinor}.${versionPatch} Build ${versionBuild}-DEV"
55 | }
56 | qa {
57 | dimension "default"
58 | buildConfigField "String", BASE_ENVIRONMENT, '"QA_ENVIRONMENT"'
59 | applicationId "com.thanksmister.iot.esp8266"
60 | versionName "${versionMajor}.${versionMinor}.${versionPatch} Build ${versionBuild}-QA"
61 | }
62 | prod {
63 | dimension "default"
64 | buildConfigField "String", BASE_ENVIRONMENT, '"PROD_ENVIRONMENT"'
65 | applicationId "com.thanksmister.iot.esp8266"
66 | versionName "${versionMajor}.${versionMinor}.${versionPatch}"
67 | }
68 | }
69 |
70 | compileOptions {
71 | sourceCompatibility JavaVersion.VERSION_1_8
72 | targetCompatibility JavaVersion.VERSION_1_8
73 | }
74 | }
75 |
76 | ext {
77 | archVersion = '1.1.0'
78 | room = '1.0.0'
79 | supporVersion = "27.+"
80 | dagger = "2.12"
81 | retrofit = "2.2.0"
82 | stetho = "1.3.1"
83 | }
84 |
85 | dependencies {
86 | implementation 'com.android.support:support-v4:27.0.2'
87 | compile fileTree(dir: 'libs', include: ['*.jar'])
88 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
89 | exclude group: 'com.android.support', module: 'support-annotations'
90 | })
91 |
92 | compile "com.android.support:appcompat-v7:${supporVersion}"
93 | compile "com.android.support:support-v4:${supporVersion}"
94 | compile 'com.android.support.constraint:constraint-layout:1.0.2'
95 | compile "com.android.support:cardview-v7:${supporVersion}"
96 | compile "com.android.support:recyclerview-v7:${supporVersion}"
97 | compile "com.android.support:design:${supporVersion}"
98 | compile "com.android.support:preference-v14:${supporVersion}"
99 |
100 | // RxJava
101 | compile 'io.reactivex.rxjava2:rxjava:2.1.7'
102 | compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
103 |
104 | // Android architecture
105 | implementation "android.arch.lifecycle:runtime:${archVersion}"
106 | implementation "android.arch.lifecycle:extensions:${archVersion}"
107 | implementation "android.arch.lifecycle:common-java8:${archVersion}"
108 | implementation "android.arch.lifecycle:reactivestreams:${archVersion}"
109 |
110 | // Room
111 | implementation "android.arch.persistence.room:runtime:${room}"
112 | kapt "android.arch.persistence.room:compiler:${room}"
113 | implementation "android.arch.persistence.room:rxjava2:${room}"
114 |
115 | // Preferences
116 | compile 'net.grandcentrix.tray:tray:0.12.0'
117 |
118 | // Retrofit
119 | compile "com.squareup.retrofit2:retrofit:${retrofit}"
120 | compile "com.squareup.retrofit2:converter-gson:${retrofit}"
121 | compile "com.squareup.retrofit2:adapter-rxjava2:${retrofit}"
122 |
123 | // OKHttp
124 | compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
125 | compile 'com.squareup.okhttp3:okhttp:3.7.0'
126 |
127 | // Dagger
128 | compile "com.google.dagger:dagger:${dagger}"
129 | annotationProcessor "com.google.dagger:dagger-compiler:${dagger}"
130 | compile "com.google.dagger:dagger-android-support:${dagger}"
131 | kapt "com.google.dagger:dagger-compiler:${dagger}"
132 | compile "com.google.dagger:dagger-android:${dagger}"
133 | annotationProcessor "com.google.dagger:dagger-android-processor:${dagger}"
134 | kapt "com.google.dagger:dagger-android-processor:${dagger}"
135 |
136 | // Logging
137 | compile 'com.jakewharton.timber:timber:4.5.1'
138 |
139 | // Dates
140 | compile 'joda-time:joda-time:2.9.9'
141 |
142 | // Stetho
143 | compile "com.facebook.stetho:stetho:${stetho}"
144 | compile "com.facebook.stetho:stetho-okhttp3:${stetho}"
145 | compile "com.facebook.stetho:stetho-urlconnection:${stetho}"
146 |
147 | testCompile 'junit:junit:4.12'
148 | compile "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
149 | }
150 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/thanksmister/iot/esp8266/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266
18 |
19 | import android.support.test.InstrumentationRegistry
20 | import android.support.test.runner.AndroidJUnit4
21 |
22 | import org.junit.Test
23 | import org.junit.runner.RunWith
24 |
25 | import org.junit.Assert.*
26 |
27 | /**
28 | * Instrumented test, which will execute on an Android device.
29 | *
30 | * See [testing documentation](http://d.android.com/tools/testing).
31 | */
32 | @RunWith(AndroidJUnit4::class)
33 | class ExampleInstrumentedTest {
34 | @Test
35 | fun useAppContext() {
36 | // Context of the app under test.
37 | val appContext = InstrumentationRegistry.getTargetContext()
38 | assertEquals("com.thanksmister.iot.esp8266", appContext.packageName)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
42 |
44 |
45 |
46 |
47 |
48 |
49 |
51 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266
18 |
19 | import android.Manifest
20 | import android.content.BroadcastReceiver
21 | import android.content.Context
22 | import android.content.Intent
23 | import android.content.IntentFilter
24 | import android.content.pm.PackageManager
25 | import android.net.ConnectivityManager
26 | import android.os.Build
27 | import android.os.Bundle
28 | import android.os.Handler
29 | import android.support.annotation.NonNull
30 | import android.support.v4.app.ActivityCompat
31 | import android.view.Menu
32 | import android.view.MenuItem
33 | import com.thanksmister.iot.esp8266.persistence.Preferences
34 | import com.thanksmister.iot.esp8266.ui.ConnectionLiveData
35 | import dagger.android.support.DaggerAppCompatActivity
36 | import io.reactivex.disposables.CompositeDisposable
37 | import timber.log.Timber
38 | import java.util.concurrent.atomic.AtomicBoolean
39 | import javax.inject.Inject
40 |
41 | abstract class BaseActivity : DaggerAppCompatActivity() {
42 |
43 | private val REQUEST_PERMISSIONS = 88
44 |
45 | @Inject lateinit var preferences: Preferences
46 |
47 | private val inactivityHandler: Handler = Handler()
48 | private var hasNetwork = AtomicBoolean(true)
49 | val disposable = CompositeDisposable()
50 |
51 | private var userPresent: Boolean = false
52 |
53 | abstract fun getLayoutId(): Int
54 |
55 | private val inactivityCallback = Runnable {
56 | userPresent = false
57 | }
58 |
59 | override fun onCreate(savedInstanceState: Bundle?) {
60 | super.onCreate(savedInstanceState)
61 |
62 | }
63 |
64 | override fun onStart(){
65 | super.onStart()
66 | }
67 |
68 | /*private fun checkPermissions() {
69 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
70 | if (ActivityCompat.checkSelfPermission(this@BaseActivity, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
71 | && ActivityCompat.checkSelfPermission(this@BaseActivity, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED
72 | && ActivityCompat.checkSelfPermission(this@BaseActivity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
73 | requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.CAMERA), REQUEST_PERMISSIONS)
74 | return
75 | }
76 | }
77 | }
78 |
79 | override fun onRequestPermissionsResult(requestCode: Int, @NonNull permissions: Array, @NonNull grantResults: IntArray) {
80 | when (requestCode) {
81 | REQUEST_PERMISSIONS -> {
82 | if (grantResults.isNotEmpty()) {
83 | var permissionsDenied = false
84 | for (permission in grantResults) {
85 | if (permission != PackageManager.PERMISSION_GRANTED) {
86 | permissionsDenied = true
87 | break
88 | }
89 | }
90 | }
91 | }
92 | }
93 | super.onRequestPermissionsResult(requestCode, permissions, grantResults)
94 | }*/
95 |
96 | override fun onDestroy() {
97 | super.onDestroy()
98 | inactivityHandler.removeCallbacks(inactivityCallback)
99 | disposable.dispose()
100 | }
101 |
102 | fun resetInactivityTimer() {
103 | inactivityHandler.removeCallbacks(inactivityCallback)
104 | inactivityHandler.postDelayed(inactivityCallback, preferences.inactivityTime())
105 | }
106 |
107 | override fun onUserInteraction() {
108 | Timber.d("onUserInteraction")
109 | userPresent = true
110 | resetInactivityTimer()
111 | }
112 |
113 | public override fun onStop() {
114 | super.onStop()
115 | inactivityHandler.removeCallbacks(inactivityCallback)
116 | }
117 |
118 | public override fun onResume() {
119 | super.onResume()
120 | //checkPermissions()
121 | }
122 |
123 | override fun onPause() {
124 | super.onPause()
125 | }
126 |
127 | override fun onCreateOptionsMenu(menu: Menu): Boolean {
128 | return true
129 | }
130 |
131 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
132 | return item.itemId == android.R.id.home
133 | }
134 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/BaseApplication.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266
18 |
19 | import com.facebook.stetho.Stetho
20 | import com.thanksmister.iot.esp8266.di.DaggerApplicationComponent
21 |
22 | import dagger.android.AndroidInjector
23 | import dagger.android.DaggerApplication
24 | import timber.log.Timber
25 |
26 | class BaseApplication : DaggerApplication() {
27 |
28 | override fun applicationInjector(): AndroidInjector {
29 | return DaggerApplicationComponent.builder().create(this);
30 | }
31 |
32 | override fun onCreate() {
33 | super.onCreate()
34 |
35 | if (BuildConfig.DEBUG) {
36 | Timber.plant(Timber.DebugTree())
37 | Stetho.initialize(Stetho.newInitializerBuilder(this)
38 | .enableDumpapp(Stetho.defaultDumperPluginsProvider(this))
39 | .enableWebKitInspector(Stetho.defaultInspectorModulesProvider(this))
40 | .build())
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/BaseFragment.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266
18 |
19 | import android.arch.lifecycle.ViewModelProvider
20 | import android.content.Context
21 | import android.os.Bundle
22 | import android.view.View
23 | import com.thanksmister.iot.esp8266.persistence.Preferences
24 | import com.thanksmister.iot.esp8266.ui.ConnectionLiveData
25 | import dagger.android.support.DaggerFragment
26 | import io.reactivex.disposables.CompositeDisposable
27 | import javax.inject.Inject
28 |
29 | open class BaseFragment : DaggerFragment() {
30 |
31 | @Inject lateinit var preferences: Preferences
32 | val disposable = CompositeDisposable()
33 |
34 | override fun onAttach(context: Context?) {
35 | super.onAttach(context)
36 | }
37 |
38 | override fun onCreate(savedInstanceState: Bundle?) {
39 | super.onCreate(savedInstanceState)
40 | }
41 |
42 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
43 | super.onViewCreated(view, savedInstanceState)
44 | }
45 |
46 | override fun onActivityCreated(savedInstanceState: Bundle?) {
47 | super.onActivityCreated(savedInstanceState)
48 | }
49 |
50 | override fun onDetach() {
51 | super.onDetach()
52 | disposable.dispose()
53 | }
54 | }// Required empty public constructor
55 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/api/ApiResponse.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.api;
18 |
19 | import android.support.annotation.NonNull;
20 | import android.support.annotation.Nullable;
21 | import android.support.v4.util.ArrayMap;
22 |
23 | import java.io.IOException;
24 | import java.util.Collections;
25 | import java.util.Map;
26 | import java.util.regex.Matcher;
27 | import java.util.regex.Pattern;
28 |
29 | import retrofit2.Response;
30 | import timber.log.Timber;
31 |
32 | /**
33 | * Common class used by API responses.
34 | * @param
35 | */
36 | public class ApiResponse {
37 | private static final Pattern LINK_PATTERN = Pattern
38 | .compile("<([^>]*)>[\\s]*;[\\s]*rel=\"([a-zA-Z0-9]+)\"");
39 | private static final Pattern PAGE_PATTERN = Pattern.compile("\\bpage=(\\d+)");
40 | private static final String NEXT_LINK = "next";
41 | public final int code;
42 | @Nullable
43 | public final T body;
44 | @Nullable
45 | public final String errorMessage;
46 | @NonNull
47 | public final Map links;
48 |
49 | public ApiResponse(Throwable error) {
50 | code = 500;
51 | body = null;
52 | errorMessage = error.getMessage();
53 | links = Collections.emptyMap();
54 | }
55 |
56 | public ApiResponse(Response response) {
57 | code = response.code();
58 | if(response.isSuccessful()) {
59 | body = response.body();
60 | errorMessage = null;
61 | } else {
62 | String message = null;
63 | if (response.errorBody() != null) {
64 | try {
65 | message = response.errorBody().string();
66 | } catch (IOException ignored) {
67 | Timber.e(ignored, "error while parsing response");
68 | }
69 | }
70 | if (message == null || message.trim().length() == 0) {
71 | message = response.message();
72 | }
73 | errorMessage = message;
74 | body = null;
75 | }
76 | String linkHeader = response.headers().get("link");
77 | if (linkHeader == null) {
78 | links = Collections.emptyMap();
79 | } else {
80 | links = new ArrayMap<>();
81 | Matcher matcher = LINK_PATTERN.matcher(linkHeader);
82 |
83 | while (matcher.find()) {
84 | int count = matcher.groupCount();
85 | if (count == 2) {
86 | links.put(matcher.group(2), matcher.group(1));
87 | }
88 | }
89 | }
90 | }
91 |
92 | public boolean isSuccessful() {
93 | return code >= 200 && code < 300;
94 | }
95 |
96 | public Integer getNextPage() {
97 | String next = links.get(NEXT_LINK);
98 | if (next == null) {
99 | return null;
100 | }
101 | Matcher matcher = PAGE_PATTERN.matcher(next);
102 | if (!matcher.find() || matcher.groupCount() != 1) {
103 | return null;
104 | }
105 | try {
106 | return Integer.parseInt(matcher.group(1));
107 | } catch (NumberFormatException ex) {
108 | Timber.w("cannot parse next page from %s", next);
109 | return null;
110 | }
111 | }
112 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/api/EspApi.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.api
18 |
19 | import com.google.gson.GsonBuilder
20 | import com.thanksmister.iot.esp8266.api.adapters.DataTypeAdapterFactory
21 | import io.reactivex.Observable
22 |
23 | import okhttp3.OkHttpClient
24 |
25 | import okhttp3.logging.HttpLoggingInterceptor
26 | import retrofit2.Response
27 | import retrofit2.Retrofit
28 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
29 | import retrofit2.converter.gson.GsonConverterFactory
30 |
31 | class EspApi(private val address: String) {
32 |
33 | private val service: EspService
34 |
35 | init {
36 |
37 | val base_url = "http://$address/"
38 | val logging = HttpLoggingInterceptor()
39 | logging.level = HttpLoggingInterceptor.Level.BODY
40 |
41 | val httpClient = OkHttpClient.Builder()
42 | .addInterceptor(logging)
43 | .addNetworkInterceptor(logging)
44 | .build()
45 |
46 | val gson = GsonBuilder()
47 | .setLenient()
48 | .registerTypeAdapterFactory(DataTypeAdapterFactory())
49 | .create()
50 |
51 | val retrofit = Retrofit.Builder()
52 | .client(httpClient)
53 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
54 | .addConverterFactory(GsonConverterFactory.create(gson))
55 | .baseUrl(base_url)
56 | .build()
57 |
58 | service = retrofit.create(EspService::class.java)
59 | }
60 |
61 | fun sendState(state:String): Observable {
62 | return service.sendState(state)
63 | }
64 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/api/EspService.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 |
18 | package com.thanksmister.iot.esp8266.api
19 |
20 |
21 | import android.arch.lifecycle.LiveData
22 | import com.thanksmister.iot.esp8266.vo.Message
23 | import io.reactivex.Observable
24 |
25 | import retrofit2.Call
26 | import retrofit2.http.*
27 |
28 | interface EspService {
29 | @GET("message/{message}")
30 | fun sendMessage(@Path("message") message:String): LiveData>
31 |
32 | @GET("led/{state}")
33 | fun sendState(@Path("state") state:String): Observable
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/api/MessageResponse.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.api;
18 |
19 | import com.google.gson.annotations.Expose;
20 | import com.google.gson.annotations.SerializedName;
21 |
22 | public class MessageResponse {
23 |
24 | @SerializedName("message")
25 | @Expose
26 | private String message = null;
27 |
28 | @SerializedName("value")
29 | @Expose
30 | private String value = null;
31 |
32 | public String getMessage() {
33 | return message;
34 | }
35 |
36 | public void setMessage(String message) {
37 | this.message = message;
38 | }
39 |
40 | public String getValue() {
41 | return value;
42 | }
43 |
44 | public void setValue(String value) {
45 | this.value = value;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/api/NetworkResponse.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.api;
18 |
19 |
20 | import android.support.annotation.NonNull;
21 |
22 | import javax.annotation.Nullable;
23 |
24 | import static com.thanksmister.iot.esp8266.api.Status.ERROR;
25 | import static com.thanksmister.iot.esp8266.api.Status.LOADING;
26 | import static com.thanksmister.iot.esp8266.api.Status.SUCCESS;
27 |
28 | /**
29 | * Response holder provided to the UI
30 | * https://proandroiddev.com/mvvm-architecture-using-livedata-rxjava-and-new-dagger-android-injection-639837b1eb6c
31 | */
32 | public class NetworkResponse {
33 |
34 | public Status status;
35 |
36 | @Nullable
37 | public final String data;
38 |
39 | @Nullable
40 | public final Throwable error;
41 |
42 | private NetworkResponse(Status status, @Nullable String data, @Nullable Throwable error) {
43 | this.status = status;
44 | this.data = data;
45 | this.error = error;
46 | }
47 |
48 | public static NetworkResponse loading() {
49 | return new NetworkResponse(LOADING, null, null);
50 | }
51 |
52 | public static NetworkResponse success(@NonNull String data) {
53 | return new NetworkResponse(SUCCESS, data, null);
54 | }
55 |
56 | public static NetworkResponse error(@NonNull Throwable error) {
57 | return new NetworkResponse(ERROR, null, error);
58 | }
59 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/api/Status.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.api;
18 |
19 | public enum Status {
20 | START,
21 | LOADING,
22 | SUCCESS,
23 | ERROR
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/api/adapters/DataTypeAdapterFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.api.adapters;
18 |
19 | import com.google.gson.Gson;
20 | import com.google.gson.JsonArray;
21 | import com.google.gson.JsonElement;
22 | import com.google.gson.JsonObject;
23 | import com.google.gson.TypeAdapter;
24 | import com.google.gson.TypeAdapterFactory;
25 | import com.google.gson.reflect.TypeToken;
26 | import com.google.gson.stream.JsonReader;
27 | import com.google.gson.stream.JsonWriter;
28 |
29 | import java.io.IOException;
30 |
31 | public class DataTypeAdapterFactory implements TypeAdapterFactory {
32 | @Override
33 | public TypeAdapter create(Gson gson, final TypeToken type) {
34 | final TypeAdapter delegate = gson.getDelegateAdapter(this, type);
35 | final TypeAdapter elementAdapter = gson.getAdapter(JsonElement.class);
36 |
37 | return new TypeAdapter() {
38 | @Override
39 | public void write(JsonWriter out, T value) throws IOException {
40 | delegate.write(out, value);
41 | }
42 | @Override
43 | public T read(JsonReader in) throws IOException {
44 | JsonElement jsonElement = elementAdapter.read(in);
45 | JsonObject dataObject = new JsonObject();
46 | JsonArray items = new JsonArray();
47 | if (jsonElement.isJsonObject()) {
48 | JsonObject jsonObject = jsonElement.getAsJsonObject();
49 | if (jsonObject.has("data") && jsonObject.get("data").isJsonObject()) {
50 | dataObject = jsonObject.getAsJsonObject("data");
51 | jsonElement = dataObject;
52 | }
53 | }
54 | return delegate.fromJsonTree(jsonElement);
55 | }
56 | }.nullSafe();
57 | }
58 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/di/ActivityModule.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.di;
18 |
19 | import android.app.Application;
20 | import android.content.Context;
21 |
22 |
23 | import android.content.res.Resources;
24 | import android.location.LocationManager;
25 | import android.view.LayoutInflater;
26 |
27 | import com.thanksmister.iot.esp8266.persistence.Preferences;
28 | import com.thanksmister.iot.esp8266.util.DialogUtils;
29 |
30 | import net.grandcentrix.tray.AppPreferences;
31 |
32 | import dagger.Module;
33 | import dagger.Provides;
34 |
35 | @Module
36 | class ActivityModule {
37 |
38 | @Provides
39 | static Resources providesResources(Application application) {
40 | return application.getResources();
41 | }
42 |
43 | @Provides
44 | static LayoutInflater providesInflater(Application application) {
45 | return (LayoutInflater) application.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
46 | }
47 |
48 | @Provides
49 | static LocationManager provideLocationManager(Application application) {
50 | return (LocationManager) application.getSystemService(Context.LOCATION_SERVICE);
51 | }
52 |
53 | @Provides
54 | static AppPreferences provideAppPreferences(Application application) {
55 | return new AppPreferences(application);
56 | }
57 |
58 | @Provides
59 | static Preferences provideConfiguration(AppPreferences appPreferences) {
60 | return new Preferences(appPreferences);
61 | }
62 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/di/ActivityScope.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.di;
18 |
19 | import java.lang.annotation.Documented;
20 | import java.lang.annotation.Retention;
21 |
22 | import javax.inject.Scope;
23 |
24 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
25 |
26 | @Scope
27 | @Documented
28 | @Retention(RUNTIME)
29 | public @interface ActivityScope {
30 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/di/AndroidBindingModule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.di
18 |
19 | import android.arch.lifecycle.ViewModel
20 | import com.thanksmister.iot.esp8266.BaseActivity
21 | import com.thanksmister.iot.esp8266.BaseFragment
22 | import com.thanksmister.iot.esp8266.ui.*
23 | import com.thanksmister.iot.esp8266.viewmodel.MainViewModel
24 | import com.thanksmister.iot.esp8266.viewmodel.MessageViewModel
25 | import com.thanksmister.iot.esp8266.viewmodel.TransmitViewModel
26 |
27 | import dagger.Binds
28 | import dagger.Module
29 | import dagger.android.ContributesAndroidInjector
30 | import dagger.multibindings.IntoMap
31 |
32 | @Module
33 | internal abstract class AndroidBindingModule {
34 |
35 | @Binds
36 | @IntoMap
37 | @ViewModelKey(MainViewModel::class)
38 | abstract fun bindsMainViewModel(viewModel: MainViewModel): ViewModel
39 |
40 | @Binds
41 | @IntoMap
42 | @ViewModelKey(TransmitViewModel::class)
43 | abstract fun bindsTransmitViewModel(viewModel: TransmitViewModel): ViewModel
44 |
45 | @Binds
46 | @IntoMap
47 | @ViewModelKey(MessageViewModel::class)
48 | abstract fun bindsMessageViewModel(viewModel: MessageViewModel): ViewModel
49 |
50 | @ContributesAndroidInjector
51 | internal abstract fun baseActivity(): BaseActivity
52 |
53 | @ContributesAndroidInjector
54 | internal abstract fun mainActivity(): MainActivity
55 |
56 | @ContributesAndroidInjector
57 | internal abstract fun logActivity(): LogActivity
58 |
59 | @ContributesAndroidInjector
60 | internal abstract fun settingsActivity(): SettingsActivity
61 |
62 | @ContributesAndroidInjector
63 | internal abstract fun mainFragment(): MainFragment
64 |
65 | @ContributesAndroidInjector
66 | internal abstract fun transmitFragment(): TransmitFragment
67 |
68 | @ContributesAndroidInjector
69 | internal abstract fun baseFragment(): BaseFragment
70 |
71 | @ContributesAndroidInjector
72 | internal abstract fun logFragment(): LogFragment
73 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/di/ApplicationComponent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.di;
18 |
19 | import com.thanksmister.iot.esp8266.BaseApplication;
20 |
21 | import javax.inject.Singleton;
22 |
23 | import dagger.Component;
24 | import dagger.android.AndroidInjector;
25 | import dagger.android.support.AndroidSupportInjectionModule;
26 |
27 | @Singleton
28 | @Component(modules = {
29 | AndroidSupportInjectionModule.class,
30 | ApplicationModule.class,
31 | ActivityModule.class,
32 | AndroidBindingModule.class,
33 | DaggerViewModelInjectionModule.class
34 | })
35 |
36 | @ApplicationScope
37 | public interface ApplicationComponent extends AndroidInjector {
38 | @Component.Builder
39 | abstract class Builder extends AndroidInjector.Builder{
40 | }
41 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/di/ApplicationModule.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.di;
18 |
19 | import android.app.Application;
20 | import android.content.Context;
21 |
22 |
23 | import com.thanksmister.iot.esp8266.BaseApplication;
24 | import com.thanksmister.iot.esp8266.persistence.AppDatabase;
25 | import com.thanksmister.iot.esp8266.persistence.MessageDao;
26 |
27 | import javax.inject.Singleton;
28 |
29 | import dagger.Binds;
30 | import dagger.Module;
31 | import dagger.Provides;
32 |
33 | @Module
34 | abstract class ApplicationModule {
35 |
36 | @Binds
37 | abstract Application application(BaseApplication baseApplication);
38 |
39 | @Provides
40 | @Singleton
41 | static Context provideContext(Application application) {
42 | return application;
43 | }
44 |
45 | @Singleton
46 | @Provides
47 | static AppDatabase provideDatabase(Application app) {
48 | return AppDatabase.getInstance(app);
49 | }
50 |
51 | @Singleton
52 | @Provides
53 | static MessageDao provideMessageDao(AppDatabase database) {
54 | return database.messageDao();
55 | }
56 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/di/ApplicationScope.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.di;
18 |
19 | import java.lang.annotation.Documented;
20 | import java.lang.annotation.Retention;
21 |
22 | import javax.inject.Scope;
23 |
24 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
25 |
26 | @Scope
27 | @Documented
28 | @Retention(RUNTIME)
29 | public @interface ApplicationScope {
30 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/di/DaggerViewModelFactory.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.di
18 |
19 | import android.arch.lifecycle.ViewModel
20 | import android.arch.lifecycle.ViewModelProvider
21 | import javax.inject.Inject
22 | import javax.inject.Provider
23 | import javax.inject.Singleton
24 |
25 | @Suppress("UNCHECKED_CAST")
26 | @Singleton
27 | class DaggerViewModelFactory
28 | @Inject constructor(
29 | private val creators: Map, @JvmSuppressWildcards Provider>) : ViewModelProvider.Factory {
30 |
31 | override fun create(modelClass: Class): T {
32 | val creator = creators[modelClass] ?:
33 | creators.asIterable().firstOrNull { modelClass.isAssignableFrom(it.key) }?.value
34 | ?: throw IllegalArgumentException("unknown model class " + modelClass)
35 |
36 | return try {
37 | creator.get() as T
38 | } catch (e: Exception) {
39 | throw RuntimeException(e)
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/di/DaggerViewModelInjectionModule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.di
18 |
19 | import android.arch.lifecycle.ViewModelProvider
20 | import dagger.Binds
21 | import dagger.Module
22 |
23 | @Module
24 | abstract class DaggerViewModelInjectionModule {
25 | @Binds
26 | internal abstract fun bindViewModelFactory(factory: DaggerViewModelFactory): ViewModelProvider.Factory
27 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/di/ViewModelKey.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.di
18 |
19 | import android.arch.lifecycle.ViewModel
20 | import dagger.MapKey
21 | import kotlin.reflect.KClass
22 |
23 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
24 | @MapKey
25 | annotation class ViewModelKey(val value: KClass)
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/manager/ConnectionManager.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.manager
18 |
19 | import android.arch.lifecycle.Lifecycle
20 | import android.arch.lifecycle.LifecycleObserver
21 | import android.arch.lifecycle.OnLifecycleEvent
22 | import android.content.BroadcastReceiver
23 | import android.content.Context
24 | import android.content.Intent
25 | import android.content.IntentFilter
26 | import android.net.ConnectivityManager
27 | import timber.log.Timber
28 | import java.util.concurrent.atomic.AtomicBoolean
29 |
30 | class ConnectionManager(private val context: Context, private val callbackListener: ConnCallbackListener) : LifecycleObserver {
31 |
32 | init {
33 | }
34 |
35 | @OnLifecycleEvent(Lifecycle.Event.ON_START)
36 | internal fun registerYourReceiver() {
37 | context.registerReceiver(connectionReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
38 | }
39 |
40 | @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
41 | internal fun unRegisterYourReceiver() {
42 | context.unregisterReceiver(connectionReceiver)
43 | }
44 |
45 | @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
46 | fun onDestroy() {
47 | }
48 |
49 | private val connectionReceiver = object : BroadcastReceiver() {
50 | override fun onReceive(context: Context, intent: Intent) {
51 | val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
52 | val currentNetworkInfo = connectivityManager.activeNetworkInfo
53 | if (currentNetworkInfo != null && currentNetworkInfo.isConnected) {
54 | Timber.d("Network Connected")
55 | hasNetwork.set(true)
56 | callbackListener.networkConnect()
57 | } else if (hasNetwork.get()) {
58 | Timber.d("Network Disconnected")
59 | hasNetwork.set(false)
60 | callbackListener.networkDisconnect()
61 | }
62 | }
63 | }
64 |
65 | interface ConnCallbackListener {
66 | fun networkConnect()
67 | fun networkDisconnect()
68 | }
69 |
70 | companion object {
71 | var hasNetwork = AtomicBoolean(true)
72 | }
73 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/manager/WiFiResponse.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.manager;
18 |
19 |
20 | import android.support.annotation.NonNull;
21 |
22 | import javax.annotation.Nullable;
23 |
24 | import static com.thanksmister.iot.esp8266.manager.WiFiStatus.CONNECTED;
25 | import static com.thanksmister.iot.esp8266.manager.WiFiStatus.CONNECTING;
26 | import static com.thanksmister.iot.esp8266.manager.WiFiStatus.DISCONNECTED;
27 | import static com.thanksmister.iot.esp8266.manager.WiFiStatus.DISCONNECTING;
28 | import static com.thanksmister.iot.esp8266.manager.WiFiStatus.ERROR;
29 |
30 |
31 | /**
32 | * Response holder provided to the UI
33 | * https://proandroiddev.com/mvvm-architecture-using-livedata-rxjava-and-new-dagger-android-injection-639837b1eb6c
34 | */
35 | public class WiFiResponse {
36 |
37 | public WiFiStatus status;
38 |
39 | @Nullable
40 | public final String data;
41 |
42 | @Nullable
43 | public final Throwable error;
44 |
45 | private WiFiResponse(WiFiStatus status, @Nullable String data, @Nullable Throwable error) {
46 | this.status = status;
47 | this.data = data;
48 | this.error = error;
49 | }
50 |
51 | public static WiFiResponse connecting() {
52 | return new WiFiResponse(CONNECTING, null, null);
53 | }
54 |
55 | public static WiFiResponse connected() {
56 | return new WiFiResponse(CONNECTED, null, null);
57 | }
58 |
59 | public static WiFiResponse disconnected() {
60 | return new WiFiResponse(DISCONNECTED, null, null);
61 | }
62 |
63 | public static WiFiResponse disconnecting() {
64 | return new WiFiResponse(DISCONNECTING, null, null);
65 | }
66 |
67 | public static WiFiResponse error(@NonNull Throwable error) {
68 | return new WiFiResponse(ERROR, null, error);
69 | }
70 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/manager/WiFiStatus.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.manager;
18 |
19 | public enum WiFiStatus {
20 | CONNECTING,
21 | CONNECTED,
22 | DISCONNECTED,
23 | DISCONNECTING,
24 | ERROR
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/persistence/AppDatabase.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.persistence
18 |
19 | import android.arch.persistence.room.Database
20 | import android.arch.persistence.room.Room
21 | import android.arch.persistence.room.RoomDatabase
22 | import android.content.Context
23 | import com.thanksmister.iot.esp8266.vo.Message
24 |
25 | /**
26 | * The Room database that contains the Messages table
27 | */
28 | @Database(entities = [(Message::class)], version = 2, exportSchema = false)
29 | abstract class AppDatabase : RoomDatabase() {
30 |
31 | abstract fun messageDao(): MessageDao
32 |
33 | companion object {
34 |
35 | @Volatile private var INSTANCE: AppDatabase? = null
36 |
37 | @JvmStatic fun getInstance(context: Context): AppDatabase =
38 | INSTANCE ?: synchronized(this) {
39 | INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
40 | }
41 |
42 | private fun buildDatabase(context: Context) =
43 | Room.databaseBuilder(context.applicationContext,
44 | AppDatabase::class.java, "esp8266_connect.db")
45 | .fallbackToDestructiveMigration()
46 | .build()
47 | }
48 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/persistence/MessageDao.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.persistence
18 |
19 | import android.arch.persistence.room.Dao
20 | import android.arch.persistence.room.Insert
21 | import android.arch.persistence.room.OnConflictStrategy
22 | import android.arch.persistence.room.Query
23 | import com.thanksmister.iot.esp8266.vo.Message
24 | import io.reactivex.Flowable
25 |
26 | /**
27 | * Data Access Object for the messages table.
28 | */
29 | @Dao
30 | interface MessageDao {
31 |
32 | /**
33 | * Get all messages
34 | * @return list of all messages
35 | */
36 | @Query("SELECT * FROM Messages")
37 | fun getMessages(): Flowable>
38 |
39 | /**
40 | * Insert a message in the database. If the message already exists, replace it.
41 | * @param user the message to be inserted.
42 | */
43 | @Insert(onConflict = OnConflictStrategy.REPLACE)
44 | fun insertMessage(message: Message)
45 |
46 | /**
47 | * Delete all messages.
48 | */
49 | @Query("DELETE FROM Messages")
50 | fun deleteAllMessages()
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/persistence/Preferences.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.persistence
18 |
19 | import net.grandcentrix.tray.AppPreferences
20 | import javax.inject.Inject
21 |
22 | /**
23 | * Store preferences
24 | */
25 | class Preferences @Inject
26 | constructor(private val preferences: AppPreferences) {
27 |
28 | fun inactivityTime():Long {
29 | return this.preferences.getLong(PREF_INACTIVITY_TIME, 300000)
30 | }
31 |
32 | fun inactivityTime(value:Long) {
33 | this.preferences.put(PREF_INACTIVITY_TIME, value)
34 | }
35 |
36 | fun address(): String? {
37 | return this.preferences.getString(PREF_SSID, "192.168.4.1")
38 | }
39 |
40 | fun address(value:String) {
41 | this.preferences.put(PREF_SSID, value)
42 | }
43 |
44 | fun ssID():String? {
45 | return this.preferences.getString(PREF_SSID, "Esp8266TestNet")
46 | }
47 |
48 | fun ssId(value:String) {
49 | this.preferences.put(PREF_SSID, value)
50 | }
51 |
52 | fun password():String? {
53 | return this.preferences.getString(PREF_PASSWORD, "Esp8266Test")
54 | }
55 |
56 | fun password(value:String) {
57 | this.preferences.put(PREF_PASSWORD, value)
58 | }
59 |
60 | /**
61 | * Reset the `SharedPreferences` and database
62 | */
63 | fun reset() {
64 | preferences.clear()
65 | }
66 |
67 | companion object {
68 | @JvmField val PREF_SSID = "pref_ssid"
69 | @JvmField val PREF_PASSWORD = "pref_password"
70 | @JvmField val PREF_INACTIVITY_TIME = "pref_inactivity_time"
71 | }
72 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/ui/ConnectionLiveData.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.ui
18 |
19 | import android.arch.lifecycle.MutableLiveData
20 | import android.content.BroadcastReceiver
21 | import android.content.Context
22 | import android.content.Intent
23 | import android.content.IntentFilter
24 | import android.net.ConnectivityManager
25 |
26 | import com.thanksmister.iot.esp8266.manager.ConnectionManager
27 | import timber.log.Timber
28 | import java.util.concurrent.atomic.AtomicBoolean
29 |
30 | class ConnectionLiveData(private val context: Context) : MutableLiveData() {
31 |
32 | private var connCallbackListener: ConnCallbackListener? = null
33 |
34 | init {
35 | connCallbackListener = object : ConnCallbackListener {
36 | override fun networkConnect() {
37 | value = true
38 | }
39 | override fun networkDisconnect() {
40 | value = false
41 | }
42 | }
43 | }
44 |
45 | fun hasNetworkConnection(): Boolean {
46 | return hasNetwork.get()
47 | }
48 |
49 | override fun onActive() {
50 | super.onActive()
51 | context.registerReceiver(connectionReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
52 | }
53 |
54 | override fun onInactive() {
55 | super.onInactive()
56 | context.unregisterReceiver(connectionReceiver)
57 | }
58 |
59 | private val connectionReceiver = object : BroadcastReceiver() {
60 | override fun onReceive(context: Context, intent: Intent) {
61 | val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
62 | val currentNetworkInfo = connectivityManager.activeNetworkInfo
63 | if (currentNetworkInfo != null && currentNetworkInfo.isConnected) {
64 | Timber.d("Network Connected")
65 | hasNetwork.set(true)
66 | value = true
67 | } else if (ConnectionManager.hasNetwork.get()) {
68 | Timber.d("Network Disconnected")
69 | hasNetwork.set(false)
70 | value = false
71 | }
72 | }
73 | }
74 |
75 | interface ConnCallbackListener {
76 | fun networkConnect()
77 | fun networkDisconnect()
78 | }
79 |
80 | companion object {
81 | var hasNetwork = AtomicBoolean(true)
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/ui/LogActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.ui
18 |
19 | import android.app.Activity
20 | import android.arch.lifecycle.Observer
21 | import android.content.Context
22 | import android.content.Intent
23 | import android.os.Bundle
24 | import android.support.annotation.LayoutRes
25 | import android.support.design.widget.Snackbar
26 | import android.support.v4.app.Fragment
27 | import android.support.v4.app.FragmentManager
28 | import android.support.v4.app.FragmentStatePagerAdapter
29 | import android.support.v4.view.PagerAdapter
30 | import android.support.v4.view.ViewPager
31 | import android.view.Menu
32 | import android.view.MenuItem
33 | import android.view.View
34 | import android.widget.Toast
35 | import com.thanksmister.iot.esp8266.BaseActivity
36 | import com.thanksmister.iot.esp8266.R
37 | import com.thanksmister.iot.esp8266.manager.WiFiReceiverManager
38 | import com.thanksmister.iot.esp8266.manager.WiFiResponse
39 | import com.thanksmister.iot.esp8266.manager.WiFiStatus
40 | import com.thanksmister.iot.esp8266.util.IntentUtils
41 | import kotlinx.android.synthetic.main.activity_main.*
42 | import kotlinx.android.synthetic.main.activity_settings.*
43 | import timber.log.Timber
44 |
45 |
46 | class LogActivity : BaseActivity() {
47 |
48 | override fun onCreate(savedInstanceState: Bundle?) {
49 | super.onCreate(savedInstanceState)
50 |
51 | setContentView(getLayoutId())
52 |
53 | setSupportActionBar(settings_toolbar)
54 |
55 | supportActionBar?.setDisplayHomeAsUpEnabled(true)
56 | supportActionBar?.setDisplayShowHomeEnabled(true)
57 | supportActionBar?.title = getString(R.string.activity_logs)
58 |
59 | if (savedInstanceState == null) {
60 | supportFragmentManager.beginTransaction().replace(R.id.contentFrame, LogFragment.newInstance(), LOGS_FRAGMENT).commit()
61 | }
62 | resetInactivityTimer()
63 | }
64 |
65 | override fun getLayoutId(): Int {
66 | return R.layout.activity_logs
67 | }
68 |
69 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
70 | val id = item.itemId
71 | if (id == android.R.id.home) {
72 | onBackPressed()
73 | return true
74 | }
75 | return super.onOptionsItemSelected(item)
76 | }
77 |
78 | companion object {
79 | private val LOGS_FRAGMENT = "com.thanksmister.fragment.LOGS_FRAGMENT"
80 | fun start(activity: Activity) {
81 | activity.startActivity(newIntent(activity))
82 | }
83 | private fun newIntent(context: Context): Intent {
84 | return IntentUtils.newIntent(context, LogActivity::class.java)
85 | }
86 | }
87 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/ui/LogFragment.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.ui
18 |
19 | import android.arch.lifecycle.ViewModelProvider
20 | import android.arch.lifecycle.ViewModelProviders
21 | import android.content.Context
22 | import android.os.Bundle
23 | import android.support.v7.widget.LinearLayoutManager
24 | import android.support.v7.widget.RecyclerView
25 | import android.view.LayoutInflater
26 | import android.view.View
27 | import android.view.ViewGroup
28 | import com.thanksmister.iot.esp8266.BaseFragment
29 | import com.thanksmister.iot.esp8266.R
30 | import com.thanksmister.iot.esp8266.ui.adapters.MessageAdapter
31 | import com.thanksmister.iot.esp8266.viewmodel.MessageViewModel
32 | import com.thanksmister.iot.esp8266.vo.Message
33 | import io.reactivex.android.schedulers.AndroidSchedulers
34 | import io.reactivex.schedulers.Schedulers
35 | import kotlinx.android.synthetic.main.fragment_logs.*
36 | import timber.log.Timber
37 | import javax.inject.Inject
38 |
39 | class LogFragment : BaseFragment() {
40 |
41 | @Inject
42 | lateinit var viewModelFactory: ViewModelProvider.Factory
43 | public lateinit var viewModel: MessageViewModel
44 |
45 | override fun onAttach(context: Context?) {
46 | super.onAttach(context)
47 | }
48 |
49 | override fun onCreate(savedInstanceState: Bundle?) {
50 | super.onCreate(savedInstanceState)
51 | }
52 |
53 | override fun onActivityCreated(savedInstanceState: Bundle?) {
54 | super.onActivityCreated(savedInstanceState)
55 | viewModel = ViewModelProviders.of(this, viewModelFactory).get(MessageViewModel::class.java)
56 | observeViewModel(viewModel)
57 | }
58 |
59 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
60 | super.onViewCreated(view, savedInstanceState)
61 | if (view is RecyclerView) {
62 | val context = view.getContext()
63 | view.layoutManager = LinearLayoutManager(context)
64 | view.adapter = MessageAdapter(ArrayList())
65 | }
66 | }
67 |
68 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
69 | return inflater.inflate(R.layout.fragment_logs, container, false)
70 | }
71 |
72 | override fun onDetach() {
73 | super.onDetach()
74 | disposable.dispose()
75 | }
76 |
77 | private fun observeViewModel(viewModel: MessageViewModel) {
78 | disposable.add(viewModel.getMessages()
79 | .subscribeOn(Schedulers.io())
80 | .observeOn(AndroidSchedulers.mainThread())
81 | .subscribe({messages ->
82 | list.adapter = MessageAdapter(messages)
83 | list.invalidate()
84 | }, { error -> Timber.e("Unable to get messages: " + error)}))
85 | }
86 |
87 | companion object {
88 | /**
89 | * Use this factory method to create a new instance of
90 | * this fragment using the provided parameters.
91 | */
92 | fun newInstance(): LogFragment {
93 | return LogFragment()
94 | }
95 | }
96 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/ui/MainFragment.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.ui
18 |
19 | import android.arch.lifecycle.ViewModelProvider
20 | import android.arch.lifecycle.ViewModelProviders
21 | import android.content.Context
22 | import android.os.Bundle
23 | import android.view.LayoutInflater
24 | import android.view.View
25 | import android.view.ViewGroup
26 | import com.thanksmister.iot.esp8266.BaseFragment
27 | import com.thanksmister.iot.esp8266.R
28 | import com.thanksmister.iot.esp8266.viewmodel.MainViewModel
29 | import kotlinx.android.synthetic.main.content_main.*
30 | import timber.log.Timber
31 | import javax.inject.Inject
32 |
33 | class MainFragment : BaseFragment() {
34 |
35 | @Inject lateinit var viewModelFactory: ViewModelProvider.Factory
36 | @Inject lateinit var viewModel: MainViewModel
37 | private var listener: OnFragmentInteractionListener? = null
38 |
39 | override fun onCreate(savedInstanceState: Bundle?) {
40 | super.onCreate(savedInstanceState)
41 | }
42 |
43 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
44 | savedInstanceState: Bundle?): View? {
45 | // Inflate the layout for this fragment
46 | return inflater.inflate(R.layout.fragment_main, container, false)
47 | }
48 |
49 | override fun onAttach(context: Context?) {
50 | super.onAttach(context)
51 | if (context is OnFragmentInteractionListener) {
52 | listener = context
53 | } else {
54 | throw RuntimeException(context!!.toString() + " must implement OnFragmentInteractionListener")
55 | }
56 | }
57 |
58 | override fun onActivityCreated(savedInstanceState: Bundle?) {
59 | super.onActivityCreated(savedInstanceState)
60 | viewModel = ViewModelProviders.of(this, viewModelFactory).get(MainViewModel::class.java)
61 | observeViewModel(viewModel)
62 | }
63 |
64 | /*Snackbar.make(activity!!.findViewById(android.R.id.content), "Replace with your own action", Snackbar.LENGTH_LONG)
65 | .setAction("Action", null).show()*/
66 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
67 | super.onViewCreated(view, savedInstanceState)
68 | buttonConnect.setOnClickListener {
69 | Timber.d("WiFi Connect")
70 | listener?.connectWifi()
71 | }
72 | }
73 |
74 | override fun onDetach() {
75 | super.onDetach()
76 | listener = null
77 | }
78 |
79 | private fun observeViewModel(viewModel: MainViewModel) {
80 |
81 | }
82 |
83 | interface OnFragmentInteractionListener {
84 | fun wifiConnected()
85 | fun connectWifi()
86 | }
87 |
88 | companion object {
89 | fun newInstance(): MainFragment {
90 | return MainFragment()
91 | }
92 | }
93 | }// Required empty public constructor
94 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/ui/SettingsActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.ui
18 |
19 | import android.app.Activity
20 | import android.content.Context
21 | import android.content.Intent
22 | import android.os.Bundle
23 | import android.support.v7.app.AlertDialog
24 | import android.text.TextUtils
25 | import android.view.MenuItem
26 | import com.thanksmister.iot.esp8266.BaseActivity
27 | import com.thanksmister.iot.esp8266.R
28 | import com.thanksmister.iot.esp8266.ui.views.DialogTextView
29 | import com.thanksmister.iot.esp8266.util.DialogUtils
30 | import com.thanksmister.iot.esp8266.util.IntentUtils
31 | import kotlinx.android.synthetic.main.activity_settings.*
32 |
33 | class SettingsActivity : BaseActivity() {
34 |
35 | private var dialog: AlertDialog? = null
36 |
37 | override fun onCreate(savedInstanceState: Bundle?) {
38 |
39 | super.onCreate(savedInstanceState)
40 |
41 | setContentView(getLayoutId())
42 |
43 | setSupportActionBar(settings_toolbar)
44 | supportActionBar?.setDisplayHomeAsUpEnabled(true)
45 | supportActionBar?.setDisplayShowHomeEnabled(true)
46 | supportActionBar?.title = getString(R.string.title_settings)
47 |
48 | if(TextUtils.isEmpty(preferences.address())) {
49 | api_text.text = getString(R.string.pref_address_description)
50 | } else {
51 | api_text.text = preferences.address()
52 | }
53 | api_button.setOnClickListener {
54 | val address : String? = preferences.address()
55 | dialog = DialogUtils.dialogEditText(this@SettingsActivity, getString(R.string.pref_address), address!!,
56 | object : DialogTextView.ViewListener {
57 | override fun onTextChange(value: String?) {
58 | if(!TextUtils.isEmpty(value)) {
59 | preferences.address(value!!)
60 | api_text.text = preferences.address()
61 | }
62 | }
63 | override fun onCancel() {
64 | dialog?.dismiss()
65 | }
66 | })
67 | }
68 | password_button.setOnClickListener {
69 | val password : String? = preferences.password()
70 | dialog = DialogUtils.dialogPasswordText(this@SettingsActivity, getString(R.string.pref_password), password!!,
71 | object : DialogTextView.ViewListener {
72 | override fun onTextChange(value: String?) {
73 | if(!TextUtils.isEmpty(value)) {
74 | preferences.password(value!!)
75 | }
76 | }
77 | override fun onCancel() {
78 | dialog?.dismiss()
79 | }
80 | })
81 | }
82 | }
83 |
84 | override fun onDestroy() {
85 | super.onDestroy()
86 | dialog?.dismiss()
87 | dialog = null
88 | }
89 |
90 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
91 | val id = item.itemId
92 | if (id == android.R.id.home) {
93 | onBackPressed()
94 | return true
95 | }
96 | return super.onOptionsItemSelected(item)
97 | }
98 |
99 | override fun getLayoutId(): Int {
100 | return R.layout.activity_settings
101 | }
102 |
103 | companion object {
104 | fun start(activity: Activity) {
105 | activity.startActivity(newIntent(activity))
106 | }
107 | private fun newIntent(context: Context): Intent {
108 | return IntentUtils.newIntent(context, SettingsActivity::class.java)
109 | }
110 | }
111 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/ui/TransmitFragment.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.ui
18 |
19 | import android.arch.lifecycle.Observer
20 | import android.arch.lifecycle.ViewModelProvider
21 | import android.arch.lifecycle.ViewModelProviders
22 | import android.content.Context
23 | import android.content.DialogInterface
24 | import android.os.Bundle
25 | import android.support.design.widget.Snackbar
26 | import android.text.TextUtils
27 | import android.view.LayoutInflater
28 | import android.view.View
29 | import android.view.ViewGroup
30 | import android.widget.Toast
31 | import com.thanksmister.iot.esp8266.BaseFragment
32 | import com.thanksmister.iot.esp8266.R
33 | import com.thanksmister.iot.esp8266.api.NetworkResponse
34 | import com.thanksmister.iot.esp8266.api.Status
35 | import com.thanksmister.iot.esp8266.util.DialogUtils
36 | import com.thanksmister.iot.esp8266.viewmodel.TransmitViewModel
37 | import kotlinx.android.synthetic.main.content_transmit.*
38 | import javax.inject.Inject
39 |
40 | class TransmitFragment : BaseFragment() {
41 |
42 | @Inject lateinit var viewModelFactory: ViewModelProvider.Factory
43 | @Inject lateinit var viewModel: TransmitViewModel
44 | private var listener: OnFragmentInteractionListener? = null
45 | private var networkStatus: Status = Status.START
46 |
47 | override fun onCreate(savedInstanceState: Bundle?) {
48 | super.onCreate(savedInstanceState)
49 | }
50 |
51 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
52 | savedInstanceState: Bundle?): View? {
53 | // Inflate the layout for this fragment
54 | return inflater.inflate(R.layout.fragment_transmit, container, false)
55 | }
56 |
57 | override fun onAttach(context: Context?) {
58 | super.onAttach(context)
59 | if (context is OnFragmentInteractionListener) {
60 | listener = context
61 | } else {
62 | throw RuntimeException(context!!.toString() + " must implement OnFragmentInteractionListener")
63 | }
64 | }
65 |
66 | override fun onActivityCreated(savedInstanceState: Bundle?) {
67 | super.onActivityCreated(savedInstanceState)
68 | viewModel = ViewModelProviders.of(this, viewModelFactory).get(TransmitViewModel::class.java)
69 | observeViewModel(viewModel)
70 | }
71 |
72 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
73 | super.onViewCreated(view, savedInstanceState)
74 | buttonOn.setOnClickListener {
75 | val value = editOn.text.toString()
76 | if(TextUtils.isEmpty(value)) {
77 | Toast.makeText(activity!!, getString(R.string.toast_blak_value), Toast.LENGTH_SHORT).show()
78 | } else if (networkStatus != Status.LOADING){
79 | viewModel.sendMessage(value)
80 | }
81 | }
82 |
83 | buttonOff.setOnClickListener {
84 | val value = editOff.text.toString()
85 | if(TextUtils.isEmpty(value)) {
86 | Toast.makeText(activity!!, getString(R.string.toast_blak_value), Toast.LENGTH_SHORT).show()
87 | } else if (networkStatus != Status.LOADING) {
88 | viewModel.sendMessage(value)
89 | }
90 | }
91 |
92 | buttonDisconnect.setOnClickListener {
93 | listener?.disconnectFromWifi()
94 | }
95 | }
96 |
97 | override fun onDetach() {
98 | super.onDetach()
99 | listener = null
100 | }
101 |
102 | interface OnFragmentInteractionListener {
103 | fun disconnectFromWifi()
104 | fun wifiDisconnected()
105 | }
106 |
107 | private fun observeViewModel(viewModel: TransmitViewModel) {
108 | viewModel.networkResponse().observe(this, Observer {response -> processNetworkResponse(response)})
109 | viewModel.getToastMessage().observe(this, Observer {message -> Toast.makeText(activity!!, message, Toast.LENGTH_LONG).show()})
110 | viewModel.getAlertMessage().observe(this, Observer {message ->
111 | DialogUtils.dialogMessage(activity!!, getString(R.string.alert_title_error), message!!, DialogInterface.OnClickListener { _, _ -> SettingsActivity.start(activity!!) })
112 | })
113 | }
114 |
115 | private fun processNetworkResponse(response: NetworkResponse?) {
116 | if(response?.status != null) {
117 | networkStatus = response.status
118 | when (networkStatus) {
119 | Status.LOADING -> {
120 | //
121 | }
122 | Status.SUCCESS -> {
123 | //
124 | }
125 | Status.ERROR -> {
126 | val message = response.error?.message.toString()
127 | Snackbar.make(activity!!.findViewById(android.R.id.content), message, Snackbar.LENGTH_LONG).show()
128 | }
129 | else -> {
130 | }
131 | }
132 | }
133 | }
134 |
135 | companion object {
136 | fun newInstance(): TransmitFragment {
137 | return TransmitFragment()
138 | }
139 | }
140 | }// Required empty public constructor
141 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/ui/adapters/MessageAdapter.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.ui.adapters
18 |
19 | import android.support.v7.widget.RecyclerView
20 | import android.view.LayoutInflater
21 | import android.view.View
22 | import android.view.ViewGroup
23 | import com.thanksmister.iot.esp8266.R
24 | import com.thanksmister.iot.esp8266.util.DateUtils
25 | import com.thanksmister.iot.esp8266.vo.Message
26 | import kotlinx.android.synthetic.main.adapter_logs.view.*
27 |
28 | class MessageAdapter(private val items: List?) : RecyclerView.Adapter() {
29 |
30 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageAdapter.ViewHolder {
31 | val v = LayoutInflater.from(parent.context).inflate(R.layout.adapter_logs, parent, false)
32 | return ViewHolder(v)
33 | }
34 |
35 | override fun getItemCount(): Int {
36 | if (items == null) return 0
37 | return if (items.isNotEmpty()) items.size else 0
38 | }
39 |
40 | override fun onBindViewHolder(holder: MessageAdapter.ViewHolder, position: Int) {
41 | holder.bindItems(items!![position])
42 | }
43 |
44 | class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
45 | fun bindItems(item: Message) {
46 | itemView.textMessage.text = item.message
47 | itemView.textValue.text = item.value
48 | itemView.textDate.text = DateUtils.parseCreatedAtDate(item.createdAt)
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/ui/controls/CustomViewPager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.ui.controls;
18 |
19 | import android.content.Context;
20 | import android.support.v4.view.ViewPager;
21 | import android.util.AttributeSet;
22 | import android.view.MotionEvent;
23 |
24 | /**
25 | * https://stackoverflow.com/questions/31000076/how-to-make-swipe-disable-in-a-view-pager-in-android
26 | */
27 | public class CustomViewPager extends ViewPager {
28 |
29 | private boolean enabled;
30 |
31 | public CustomViewPager(Context context, AttributeSet attrs) {
32 | super(context, attrs);
33 | this.enabled = true;
34 | }
35 |
36 | @Override
37 | public boolean onTouchEvent(MotionEvent event) {
38 | return this.enabled && super.onTouchEvent(event);
39 | }
40 |
41 | @Override
42 | public boolean onInterceptTouchEvent(MotionEvent event) {
43 | return this.enabled && super.onInterceptTouchEvent(event);
44 | }
45 |
46 | public void setPagingEnabled(boolean enabled) {
47 | this.enabled = enabled;
48 | }
49 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/ui/views/DialogTextView.kt:
--------------------------------------------------------------------------------
1 | /*
2 | *
17 | */
18 |
19 | package com.thanksmister.iot.esp8266.ui.views
20 |
21 | import android.content.Context
22 | import android.text.Editable
23 | import android.text.TextUtils
24 | import android.text.TextWatcher
25 | import android.util.AttributeSet
26 | import android.widget.LinearLayout
27 | import kotlinx.android.synthetic.main.dialog_text_view.view.*
28 |
29 | class DialogTextView : LinearLayout {
30 |
31 | private var listener: ViewListener? = null
32 | private var value : String? = null
33 |
34 | constructor(context: Context) : super(context) {}
35 |
36 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}
37 |
38 | override fun onFinishInflate() {
39 | super.onFinishInflate()
40 | value_text.addTextChangedListener(object : TextWatcher {
41 | override fun beforeTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {}
42 | override fun onTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {
43 | if (!TextUtils.isEmpty(charSequence)) {
44 | value = charSequence.toString()
45 | }
46 | }
47 | override fun afterTextChanged(editable: Editable) {}
48 | })
49 | }
50 |
51 | fun getValue() : String? {
52 | return value
53 | }
54 |
55 | fun setListener(listener: ViewListener) {
56 | this.listener = listener
57 | }
58 |
59 | interface ViewListener {
60 | fun onTextChange(value : String?)
61 | fun onCancel()
62 | }
63 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/util/DateUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.util;
18 |
19 | import org.joda.time.DateTime;
20 | import org.joda.time.format.DateTimeFormat;
21 |
22 | import java.text.DateFormat;
23 | import java.text.SimpleDateFormat;
24 | import java.util.Date;
25 | import java.util.Locale;
26 | import java.util.concurrent.TimeUnit;
27 |
28 | /**
29 | * Date utils
30 | */
31 | public final class DateUtils {
32 |
33 | private DateUtils(){
34 | }
35 |
36 | public static String parseCreatedAtDate(String dateString) {
37 | String fmt = DateTimeFormat.patternForStyle("SS", Locale.getDefault());
38 | DateTime dateTime = new DateTime(dateString);
39 | dateTime.toLocalDateTime();
40 | return dateTime.toLocalDateTime().toString(fmt);
41 | }
42 |
43 | public static String generateCreatedAtDate() {
44 | DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US);
45 | return dateFormat.format(new Date());
46 | }
47 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/util/DialogUtils.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.util
18 |
19 | import android.app.Dialog
20 | import android.content.Context
21 | import android.content.ContextWrapper
22 | import android.support.annotation.NonNull
23 | import android.support.design.widget.Snackbar
24 | import android.support.v7.app.AlertDialog
25 | import android.view.LayoutInflater
26 | import android.view.View
27 | import android.widget.TextView
28 |
29 | import com.thanksmister.iot.esp8266.R
30 | import com.thanksmister.iot.esp8266.ui.views.DialogTextView
31 | import android.content.DialogInterface
32 | import android.widget.EditText
33 |
34 |
35 |
36 | class DialogUtils(base: Context) : ContextWrapper(base) {
37 |
38 | companion object {
39 |
40 | fun dialogMessage(context: Context, title: String, message: String) {
41 | val builder = AlertDialog.Builder(context)
42 | builder.setTitle(title)
43 | builder.setMessage(message)
44 | builder.setPositiveButton(android.R.string.ok, null)
45 | builder.create().show();
46 | }
47 |
48 | fun dialogMessage(context: Context, title: String, message: String, listener: DialogInterface.OnClickListener) {
49 | val builder = AlertDialog.Builder(context)
50 | builder.setTitle(title)
51 | builder.setMessage(message)
52 | builder.setPositiveButton(android.R.string.ok, listener)
53 | builder.create().show();
54 | }
55 |
56 | fun dialogMessage(context: Context, message: String?) {
57 | val builder = AlertDialog.Builder(context)
58 | builder.setMessage(message)
59 | builder.setPositiveButton(android.R.string.ok, null)
60 | builder.create().show();
61 | }
62 |
63 | fun dialogMessageHtml(context: Context, title: String, message: String) {
64 | val builder = AlertDialog.Builder(context)
65 | builder.setTitle(title)
66 | builder.setMessage(fromHtml(message))
67 | builder.setPositiveButton(android.R.string.ok, null)
68 | builder.create().show();
69 | }
70 |
71 | fun dialogEditText(context: Context, title:String, value: String, listener: DialogTextView.ViewListener) : AlertDialog {
72 | val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
73 | val view = inflater.inflate(R.layout.dialog_text_view, null, false)
74 | val dialogView = view.findViewById(R.id.dialog_text_view)
75 | dialogView.setListener(listener)
76 | val valueText = view.findViewById(R.id.value_text)
77 | valueText.text = value;
78 | val dialog = AlertDialog.Builder(context)
79 | dialog.setTitle(title)
80 | dialog.setView(view)
81 | dialog.setPositiveButton(android.R.string.ok) { _, _ ->
82 | listener.onTextChange(dialogView.getValue())
83 | }
84 | dialog.setNegativeButton(android.R.string.cancel) { _, _ ->
85 | listener.onCancel()
86 | }
87 | dialog.setOnDismissListener({ listener.onCancel() })
88 | return dialog.show()
89 | }
90 |
91 | fun dialogPasswordText(context: Context, title:String, value: String, listener: DialogTextView.ViewListener) : AlertDialog {
92 | val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
93 | val view = inflater.inflate(R.layout.dialog_password_view, null, false)
94 | val dialogView = view.findViewById(R.id.dialog_text_view)
95 | dialogView.setListener(listener)
96 | val valueText = view.findViewById(R.id.value_text)
97 | valueText.text = value;
98 | val dialog = AlertDialog.Builder(context)
99 | dialog.setTitle(title)
100 | dialog.setView(view)
101 | dialog.setPositiveButton(android.R.string.ok) { _, _ ->
102 | listener.onTextChange(dialogView.getValue())
103 | }
104 | dialog.setNegativeButton(android.R.string.cancel) { _, _ ->
105 | listener.onCancel()
106 | }
107 | dialog.setOnDismissListener({ listener.onCancel() })
108 | return dialog.show()
109 | }
110 |
111 | /**
112 | * Generate a dismissible SnackBar component.
113 | */
114 | fun createSnackBar(context: Context, message: String, retry: Boolean, @NonNull view: View, listener: View.OnClickListener): Snackbar {
115 | return when {
116 | retry -> {
117 | val snackBar = Snackbar.make(view, message, Snackbar.LENGTH_INDEFINITE)
118 | .setAction(context.getString(R.string.button_retry), listener)
119 | val textView = snackBar.view.findViewById(android.support.design.R.id.snackbar_text) as TextView
120 | textView.setTextColor(context.resources.getColor(R.color.white))
121 | snackBar
122 | }
123 | else -> {
124 | val snackBar = Snackbar.make(view, message, Snackbar.LENGTH_LONG)
125 | val textView = snackBar.view.findViewById(android.support.design.R.id.snackbar_text) as TextView
126 | textView.setTextColor(context.resources.getColor(R.color.white))
127 | snackBar
128 | }
129 | }
130 | }
131 | }
132 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/util/Extensions.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.util
18 |
19 | import android.content.BroadcastReceiver
20 | import android.content.Context
21 | import android.content.Intent
22 | import android.text.Html
23 | import android.text.Spanned
24 | import android.widget.Toast
25 |
26 | fun Context.toast(message: CharSequence, duration: Int = Toast.LENGTH_LONG) {
27 | Toast.makeText(this, message, duration).show()
28 | }
29 |
30 | fun fromHtml(html: String?): Spanned {
31 | val result: Spanned
32 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
33 | result = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY)
34 | } else {
35 | result = Html.fromHtml(html)
36 | }
37 | return result
38 | }
39 |
40 | fun broadcastReceiver(init: (Context, Intent?) -> Unit): BroadcastReceiver {
41 | return object : BroadcastReceiver() {
42 | public override fun onReceive(context: Context, intent: Intent?) {
43 | init(context, intent)
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/util/IntentUtils.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.util
18 |
19 | import android.content.Context
20 | import android.content.Intent
21 | import android.net.Uri
22 | import android.os.Bundle
23 |
24 | class IntentUtils private constructor() {
25 |
26 | init {
27 | throw IllegalStateException("No instances")
28 | }
29 |
30 | companion object {
31 |
32 | fun share(text: String): Intent {
33 | val sendIntent = Intent()
34 | sendIntent.action = Intent.ACTION_SEND
35 | sendIntent.putExtra(Intent.EXTRA_TEXT, text)
36 | sendIntent.type = "text/plain"
37 |
38 | return sendIntent
39 | }
40 |
41 | fun showUri(context: Context, uri: String) {
42 | showUri(context, Uri.parse(uri))
43 | }
44 |
45 | fun showUri(context: Context, uri: Uri) {
46 | val intent = Intent(Intent.ACTION_VIEW)
47 | intent.data = uri
48 |
49 | context.startActivity(intent)
50 | }
51 |
52 | fun startActivity(context: Context, kls: Class<*>) {
53 | context.startActivity(newIntent(context, kls))
54 | }
55 |
56 | fun startActivity(context: Context, kls: Class<*>, bundle: Bundle) {
57 | context.startActivity(newIntent(context, kls, bundle))
58 | }
59 |
60 | @JvmOverloads
61 | fun newIntent(context: Context, kls: Class<*>, bundle: Bundle = Bundle()): Intent {
62 | val intent = Intent(context, kls)
63 | intent.putExtras(bundle)
64 |
65 | return intent
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/viewmodel/AlertMessage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.viewmodel;
18 |
19 | import android.arch.lifecycle.LifecycleOwner;
20 | import android.arch.lifecycle.Observer;
21 | import android.support.annotation.Nullable;
22 | import android.text.TextUtils;
23 |
24 | /**
25 | * A SingleLiveEvent used for Alert dialog messages. Like a {@link SingleLiveEvent} but also prevents
26 | * null messages and uses a custom observer.
27 | *
28 | * Note that only one observer is going to be notified of changes.
29 | * https://github.com/googlesamples/android-architecture/blob/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/SnackbarMessage.java
30 | */
31 | public class AlertMessage extends SingleLiveEvent {
32 | public void observe(LifecycleOwner owner, final AlertObserver observer) {
33 | super.observe(owner, new Observer() {
34 | @Override
35 | public void onChanged(@Nullable String t) {
36 | if (TextUtils.isEmpty(t)) {
37 | return;
38 | }
39 | observer.onNewMessage(t);
40 | }
41 | });
42 | }
43 |
44 | public interface AlertObserver {
45 | /**
46 | * Called when there is a new message to be shown.
47 | * @param alertMessage The new message, non-null.
48 | */
49 | void onNewMessage(String alertMessage);
50 | }
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/viewmodel/MainViewModel.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.viewmodel
18 |
19 | import android.app.Application
20 | import android.arch.lifecycle.AndroidViewModel
21 | import com.thanksmister.iot.esp8266.persistence.MessageDao
22 | import com.thanksmister.iot.esp8266.persistence.Preferences
23 | import javax.inject.Inject
24 |
25 | class MainViewModel @Inject
26 | constructor(application: Application, private val dataSource: MessageDao,
27 | private val configuration: Preferences) : AndroidViewModel(application) {
28 |
29 | companion object {
30 |
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/viewmodel/MessageViewModel.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.viewmodel
18 |
19 | import android.app.Application
20 | import android.arch.lifecycle.AndroidViewModel
21 |
22 | import com.thanksmister.iot.esp8266.persistence.MessageDao
23 | import com.thanksmister.iot.esp8266.util.DateUtils
24 | import com.thanksmister.iot.esp8266.vo.Message
25 | import io.reactivex.Completable
26 | import io.reactivex.Flowable
27 | import javax.inject.Inject
28 |
29 | class MessageViewModel @Inject
30 | constructor(application: Application, private val dataSource: MessageDao) : AndroidViewModel(application) {
31 |
32 | /**
33 | * Get the messages.
34 | * @return a [Flowable] that will emit every time the messages have been updated.
35 | */
36 | fun getMessages():Flowable> {
37 | return dataSource.getMessages()
38 | .filter {messages -> messages.isNotEmpty()}
39 | }
40 |
41 | init {
42 | }
43 |
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/viewmodel/SingleLiveEvent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.viewmodel;
18 |
19 | import android.arch.lifecycle.LifecycleOwner;
20 | import android.arch.lifecycle.MutableLiveData;
21 | import android.arch.lifecycle.Observer;
22 | import android.support.annotation.MainThread;
23 | import android.support.annotation.NonNull;
24 | import android.support.annotation.Nullable;
25 |
26 | import java.util.concurrent.atomic.AtomicBoolean;
27 |
28 | import timber.log.Timber;
29 |
30 | /**
31 | * Created by michaelritchie on 1/23/18.
32 | * From https://github.com/googlesamples/android-architecture/blob/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/SingleLiveEvent.java
33 | */
34 |
35 | public class SingleLiveEvent extends MutableLiveData {
36 |
37 | private final AtomicBoolean mPending = new AtomicBoolean(false);
38 |
39 | @MainThread
40 | public void observe(@NonNull LifecycleOwner owner, @NonNull final Observer observer) {
41 |
42 | if (hasActiveObservers()) {
43 | Timber.w("Multiple observers registered but only one will be notified of changes.");
44 | }
45 |
46 | // Observe the internal MutableLiveData
47 | super.observe(owner, new Observer() {
48 | @Override
49 | public void onChanged(@Nullable T t) {
50 | if (mPending.compareAndSet(true, false)) {
51 | observer.onChanged(t);
52 | }
53 | }
54 | });
55 | }
56 |
57 | @MainThread
58 | public void setValue(@Nullable T t) {
59 | mPending.set(true);
60 | super.setValue(t);
61 | }
62 |
63 | /**
64 | * Used for cases where T is Void, to make calls cleaner.
65 | */
66 | @MainThread
67 | public void call() {
68 | setValue(null);
69 | }
70 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/viewmodel/SnackbarMessage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.viewmodel;
18 |
19 | import android.arch.lifecycle.LifecycleOwner;
20 | import android.arch.lifecycle.Observer;
21 | import android.support.annotation.Nullable;
22 | import android.support.annotation.StringRes;
23 |
24 | /**
25 | * A SingleLiveEvent used for Snackbar messages. Like a {@link SingleLiveEvent} but also prevents
26 | * null messages and uses a custom observer.
27 | *
28 | * Note that only one observer is going to be notified of changes.
29 | * https://github.com/googlesamples/android-architecture/blob/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/SnackbarMessage.java
30 | */
31 | public class SnackbarMessage extends SingleLiveEvent {
32 | public void observe(LifecycleOwner owner, final SnackbarObserver observer) {
33 | super.observe(owner, new Observer() {
34 | @Override
35 | public void onChanged(@Nullable Integer t) {
36 | if (t == null) {
37 | return;
38 | }
39 | observer.onNewMessage(t);
40 | }
41 | });
42 | }
43 |
44 | public interface SnackbarObserver {
45 | /**
46 | * Called when there is a new message to be shown.
47 | * @param snackbarMessageResourceId The new message, non-null.
48 | */
49 | void onNewMessage(@StringRes int snackbarMessageResourceId);
50 | }
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/viewmodel/ToastMessage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.viewmodel;
18 |
19 | import android.arch.lifecycle.LifecycleOwner;
20 | import android.arch.lifecycle.Observer;
21 | import android.support.annotation.Nullable;
22 | import android.text.TextUtils;
23 |
24 | /**
25 | * A SingleLiveEvent used for Alert dialog messages. Like a {@link SingleLiveEvent} but also prevents
26 | * null messages and uses a custom observer.
27 | *
28 | * Note that only one observer is going to be notified of changes.
29 | * https://github.com/googlesamples/android-architecture/blob/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/SnackbarMessage.java
30 | */
31 | public class ToastMessage extends SingleLiveEvent {
32 | public void observe(LifecycleOwner owner, final AlertObserver observer) {
33 | super.observe(owner, new Observer() {
34 | @Override
35 | public void onChanged(@Nullable String t) {
36 | if (TextUtils.isEmpty(t)) {
37 | return;
38 | }
39 | observer.onNewMessage(t);
40 | }
41 | });
42 | }
43 |
44 | public interface AlertObserver {
45 | /**
46 | * Called when there is a new message to be shown.
47 | * @param alertMessage The new message, non-null.
48 | */
49 | void onNewMessage(String alertMessage);
50 | }
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/viewmodel/TransmitViewModel.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.viewmodel
18 |
19 | import android.app.Application
20 | import android.arch.lifecycle.AndroidViewModel
21 | import android.arch.lifecycle.MutableLiveData
22 | import android.text.TextUtils
23 | import com.thanksmister.iot.esp8266.R
24 | import com.thanksmister.iot.esp8266.api.EspApi
25 | import com.thanksmister.iot.esp8266.api.MessageResponse
26 | import com.thanksmister.iot.esp8266.api.NetworkResponse
27 | import com.thanksmister.iot.esp8266.persistence.MessageDao
28 | import com.thanksmister.iot.esp8266.persistence.Preferences
29 | import com.thanksmister.iot.esp8266.util.DateUtils
30 | import com.thanksmister.iot.esp8266.vo.Message
31 | import io.reactivex.Completable
32 | import io.reactivex.android.schedulers.AndroidSchedulers
33 | import io.reactivex.disposables.CompositeDisposable
34 | import io.reactivex.observers.DisposableObserver
35 | import io.reactivex.schedulers.Schedulers
36 | import timber.log.Timber
37 | import javax.inject.Inject
38 |
39 | class TransmitViewModel @Inject
40 | constructor(application: Application, private val dataSource: MessageDao,
41 | private val configuration: Preferences) : AndroidViewModel(application) {
42 |
43 | private val toastText = ToastMessage()
44 | private val snackbarText = SnackbarMessage()
45 | private val alertText = AlertMessage()
46 | private val networkResponse = MutableLiveData()
47 | private val disposable = CompositeDisposable()
48 |
49 | fun networkResponse(): MutableLiveData {
50 | return networkResponse
51 | }
52 |
53 | fun getToastMessage(): ToastMessage {
54 | return toastText
55 | }
56 |
57 | fun getAlertMessage(): AlertMessage {
58 | return alertText
59 | }
60 |
61 | fun getSnackbarMessage(): SnackbarMessage {
62 | return snackbarText
63 | }
64 |
65 | init {
66 | // na-da
67 | }
68 |
69 | private fun showSnackbarMessage(message: Int?) {
70 | snackbarText.value = message
71 | }
72 |
73 | private fun showAlertMessage(message: String?) {
74 | toastText.value = message
75 | }
76 |
77 | private fun showToastMessage(message: String?) {
78 | toastText.value = message
79 | }
80 |
81 | fun sendMessage(message: String) {
82 | if(!TextUtils.isEmpty(configuration.address())) {
83 | val api = EspApi(configuration.address()!!)
84 | disposable.add(api.sendState(message)
85 | .subscribeOn(Schedulers.computation())
86 | .observeOn(AndroidSchedulers.mainThread())
87 | .doOnSubscribe { networkResponse.value = NetworkResponse.loading() }
88 | .subscribeWith( object : DisposableObserver() {
89 | override fun onNext(response: MessageResponse) {
90 | if (!TextUtils.isEmpty(response.message) && !TextUtils.isEmpty(response.value)){
91 | insertMessageResponse(response.message, response.value)
92 | networkResponse.value = NetworkResponse.success(response.message)
93 | }
94 | }
95 | override fun onComplete() {
96 | Timber.d("complete");
97 | networkResponse.value = NetworkResponse.success("complete")
98 | }
99 | override fun onError(error: Throwable) {
100 | networkResponse.value = NetworkResponse.error(error)
101 | var errorMessage: String? = "Server error"
102 | if(!TextUtils.isEmpty(error.message)) {
103 | errorMessage = error.message
104 | }
105 | insertMessageResponse("error", errorMessage!!)
106 | Timber.e("error: " + error.message);
107 | }
108 | }));
109 | } else {
110 | showAlertMessage(getApplication().getString(R.string.error_empty_address))
111 | }
112 | }
113 |
114 | /**
115 | * Insert new message into the database.
116 | */
117 | private fun insertMessageResponse(msg: String, value: String) {
118 | disposable.add(Completable.fromAction {
119 | val createdAt = DateUtils.generateCreatedAtDate()
120 | val message = Message()
121 | message.value = value
122 | message.message = msg
123 | message.createdAt = createdAt
124 | dataSource.insertMessage(message)
125 | }
126 | .subscribeOn(Schedulers.io())
127 | .observeOn(AndroidSchedulers.mainThread())
128 | .subscribe({
129 | }, { error -> Timber.e("Database error" + error.message)}))
130 | }
131 |
132 | public override fun onCleared() {
133 | //prevents memory leaks by disposing pending observable objects
134 | if ( !disposable.isDisposed) {
135 | disposable.clear()
136 | }
137 | }
138 |
139 | /**
140 | * Network connectivity receiver to notify client of the network disconnect issues and
141 | * to clear any network notifications when reconnected. It is easy for network connectivity
142 | * to run amok that is why we only notify the user once for network disconnect with
143 | * a boolean flag.
144 | */
145 | companion object {
146 |
147 | }
148 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/vo/Message.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.vo
18 |
19 | import android.arch.persistence.room.ColumnInfo
20 | import android.arch.persistence.room.Entity
21 | import android.arch.persistence.room.PrimaryKey
22 |
23 | @Entity(tableName = "Messages")
24 | class Message {
25 | @PrimaryKey(autoGenerate = true)
26 | var uid: Int = 0
27 |
28 | @ColumnInfo(name = "createdAt")
29 | var createdAt: String? = null
30 |
31 | @ColumnInfo(name = "message")
32 | var message: String? = null
33 |
34 | @ColumnInfo(name = "value")
35 | var value: String? = null
36 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/vo/Resource.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.vo;
18 |
19 | import android.support.annotation.NonNull;
20 | import android.support.annotation.Nullable;
21 |
22 | import static com.thanksmister.iot.esp8266.vo.Status.ERROR;
23 | import static com.thanksmister.iot.esp8266.vo.Status.LOADING;
24 | import static com.thanksmister.iot.esp8266.vo.Status.SUCCESS;
25 |
26 | /**
27 | * A generic class that holds a value with its loading status.
28 | * @param
29 | */
30 | public class Resource {
31 |
32 | @NonNull
33 | public final Status status;
34 |
35 | @Nullable
36 | public final String message;
37 |
38 | @Nullable
39 | public final T data;
40 |
41 | public Resource(@NonNull Status status, @Nullable T data, @Nullable String message) {
42 | this.status = status;
43 | this.data = data;
44 | this.message = message;
45 | }
46 |
47 | public static Resource success(@Nullable T data) {
48 | return new Resource<>(SUCCESS, data, null);
49 | }
50 |
51 | public static Resource error(String msg, @Nullable T data) {
52 | return new Resource<>(ERROR, data, msg);
53 | }
54 |
55 | public static Resource loading(@Nullable T data) {
56 | return new Resource<>(LOADING, data, null);
57 | }
58 |
59 | @Override
60 | public boolean equals(Object o) {
61 | if (this == o) {
62 | return true;
63 | }
64 | if (o == null || getClass() != o.getClass()) {
65 | return false;
66 | }
67 |
68 | Resource> resource = (Resource>) o;
69 |
70 | if (status != resource.status) {
71 | return false;
72 | }
73 | if (message != null ? !message.equals(resource.message) : resource.message != null) {
74 | return false;
75 | }
76 | return data != null ? data.equals(resource.data) : resource.data == null;
77 | }
78 |
79 | @Override
80 | public int hashCode() {
81 | int result = status.hashCode();
82 | result = 31 * result + (message != null ? message.hashCode() : 0);
83 | result = 31 * result + (data != null ? data.hashCode() : 0);
84 | return result;
85 | }
86 |
87 | @Override
88 | public String toString() {
89 | return "Resource{" +
90 | "status=" + status +
91 | ", message='" + message + '\'' +
92 | ", data=" + data +
93 | '}';
94 | }
95 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/thanksmister/iot/esp8266/vo/Status.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266.vo;
18 |
19 | /**
20 | * Status of a resource that is provided to the UI.
21 | *
22 | * These are usually created by the Repository classes where they return
23 | * {@code LiveData>} to pass back the latest data to the UI with its fetch status.
24 | */
25 | public enum Status {
26 | SUCCESS,
27 | ERROR,
28 | LOADING
29 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
23 |
28 |
29 |
35 |
38 |
41 |
42 |
43 |
44 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_blue.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_blue_pressed.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_blue_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_disabled.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_gray.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_gray_pressed.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_gray_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_green.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_green_pressed.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_green_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_green_small.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_green_small_pressed.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_green_small_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_red.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_red_pressed.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_red_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_red_small.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_red_small_pressed.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_red_small_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_text_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_transparent.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_white_small.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_white_small_pressed.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_white_small_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
23 |
26 |
31 |
36 |
41 |
46 |
51 |
56 |
61 |
66 |
71 |
76 |
81 |
86 |
91 |
96 |
101 |
106 |
111 |
116 |
121 |
126 |
131 |
136 |
141 |
146 |
151 |
156 |
161 |
166 |
171 |
176 |
181 |
186 |
187 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_logs.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
28 |
29 |
33 |
34 |
40 |
41 |
42 |
43 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
24 |
25 |
29 |
30 |
36 |
37 |
38 |
39 |
44 |
45 |
54 |
55 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_settings.xml:
--------------------------------------------------------------------------------
1 |
16 |
24 |
25 |
29 |
30 |
36 |
37 |
38 |
39 |
43 |
44 |
50 |
51 |
60 |
61 |
68 |
69 |
76 |
77 |
85 |
86 |
87 |
88 |
91 |
92 |
99 |
100 |
107 |
108 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/adapter_logs.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
25 |
26 |
33 |
34 |
41 |
42 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
24 |
25 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_transmit.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
24 |
25 |
35 |
36 |
37 |
45 |
46 |
47 |
56 |
57 |
58 |
59 |
60 |
70 |
71 |
80 |
81 |
90 |
91 |
92 |
93 |
94 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_password_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
30 |
31 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_text_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
32 |
33 |
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_logs.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_main.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_transmit.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thanksmister/android-esp8266-connect/19b9af633c6e5ab70cd22120c4d19e24b050fd8a/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thanksmister/android-esp8266-connect/19b9af633c6e5ab70cd22120c4d19e24b050fd8a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thanksmister/android-esp8266-connect/19b9af633c6e5ab70cd22120c4d19e24b050fd8a/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thanksmister/android-esp8266-connect/19b9af633c6e5ab70cd22120c4d19e24b050fd8a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thanksmister/android-esp8266-connect/19b9af633c6e5ab70cd22120c4d19e24b050fd8a/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thanksmister/android-esp8266-connect/19b9af633c6e5ab70cd22120c4d19e24b050fd8a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thanksmister/android-esp8266-connect/19b9af633c6e5ab70cd22120c4d19e24b050fd8a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thanksmister/android-esp8266-connect/19b9af633c6e5ab70cd22120c4d19e24b050fd8a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thanksmister/android-esp8266-connect/19b9af633c6e5ab70cd22120c4d19e24b050fd8a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thanksmister/android-esp8266-connect/19b9af633c6e5ab70cd22120c4d19e24b050fd8a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | #ff0083b2
20 | #036e94
21 | #ff7dac2f
22 |
23 | #DE000000
24 | #bf000000
25 | #99000000
26 | #8A000000
27 | #61000000
28 | #1f000000
29 |
30 | #DEFFFFFF
31 |
32 | #00000000
33 | #ff7dac2f
34 | #ff67932c
35 | #ff67932c
36 | #eeee22
37 | #D5B213
38 | #fe818181
39 | #0099cc
40 | #ff0083b2
41 | #000000
42 | #FFFFFF
43 | #efefef
44 | #d7523e
45 | #fff4492c
46 | #ffd3402a
47 | #eeeeee
48 | #f7f7f7
49 | #e5e5e5
50 | #cfcfcf
51 | #ff707070
52 | #0099cc
53 |
54 |
55 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | 11sp
19 | 12sp
20 | 13sp
21 | 14sp
22 | 16sp
23 | 18sp
24 | 20sp
25 | 24sp
26 | 36sp
27 | 32sp
28 | 11.0sp
29 | 12.0sp
30 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | ESP8266 Connect
19 | Connect
20 | Retry
21 | Network disconnected…
22 | Settings
23 | Address
24 | WiFi Name (SSID)
25 | http://192.168.4.1
26 | Access Point
27 | Enter the name for the access point.
28 | WiFi Password
29 | Enter the password for the access point.
30 | Network connected…
31 | Send
32 | Disconnect
33 | Logs
34 | Settings
35 | Error
36 | The value not be blank...
37 | The network address is empty, please enter the address in the settings.
38 |
39 |
--------------------------------------------------------------------------------
/app/src/test/java/com/thanksmister/iot/esp8266/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.thanksmister.iot.esp8266
18 |
19 | import org.junit.Test
20 |
21 | import org.junit.Assert.*
22 |
23 | /**
24 | * Example local unit test, which will execute on the development machine (host).
25 | *
26 | * See [testing documentation](http://d.android.com/tools/testing).
27 | */
28 | class ExampleUnitTest {
29 | @Test
30 | fun addition_isCorrect() {
31 | assertEquals(4, 2 + 2)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
18 | buildscript {
19 | ext.kotlin_version = '1.2.31'
20 | repositories {
21 | jcenter()
22 | google()
23 | }
24 | dependencies {
25 | classpath 'com.android.tools.build:gradle:3.1.2'
26 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
27 | // NOTE: Do not place your application dependencies here; they belong
28 | // in the individual module build.gradle files
29 | }
30 | }
31 |
32 | allprojects {
33 | repositories {
34 | jcenter()
35 | mavenCentral()
36 | google()
37 | }
38 | }
39 |
40 | task clean(type: Delete) {
41 | delete rootProject.buildDir
42 | }
43 |
--------------------------------------------------------------------------------
/connect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thanksmister/android-esp8266-connect/19b9af633c6e5ab70cd22120c4d19e24b050fd8a/connect.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thanksmister/android-esp8266-connect/19b9af633c6e5ab70cd22120c4d19e24b050fd8a/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Apr 03 14:16:48 ART 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 ThanksMister LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed
11 | * under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | include ':app'
18 |
--------------------------------------------------------------------------------