├── .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 | ![ESP8266 Connect](https://github.com/thanksmister/android-esp8266-connect/blob/master/connect.png) 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 |