diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 7ec01475..f7d81810 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -1,21 +1,54 @@ name: Android CI -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] +on: [push] jobs: - build: + unit-test: runs-on: ubuntu-latest - steps: - uses: actions/checkout@v2 + - name: set up Ruby to install fastlane + uses: actions/setup-ruby@v1 + with: + ruby-version: '2.7' - name: set up JDK 1.8 uses: actions/setup-java@v1 with: java-version: 1.8 - - name: Build with Gradle - run: ./gradlew build + - name: setup fastlane + run: bundle install + - name: run unit tests + run: bundle exec fastlane android run_unit_tests + - name: Unit tests results + uses: actions/upload-artifact@v1 + with: + name: unit-test-results + path: app/build/reports/tests/testDebugUnitTest/index.html + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Lint + run: bash ./gradlew lint --stacktrace + - name: Lint results + uses: actions/upload-artifact@v1 + with: + name: app + path: app/build/reports/lint-results.html + + ui-test: + runs-on: macOS-latest + steps: + - name: checkout + uses: actions/checkout@v2 + - name: run tests + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 29 + script: ./gradlew connectedCheck diff --git a/.gitignore b/.gitignore index d3062615..92da7791 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,16 @@ lint/outputs/ lint/tmp/ # lint/reports/ app/src/main/res/values/api_keys.xml +.idea/compiler.xml +.idea/compiler.xml +.idea/compiler.xml +.idea/jarRepositories.xml +.idea/misc.xml +app/build.gradle +.idea/misc.xml +.idea/misc.xml +.idea/misc.xml +.idea/misc.xml +.idea/misc.xml +.idea/.name +.idea/codeStyles/Project.xml diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..adc90d98 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gem "fastlane" \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..40f07aee --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,184 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.3) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + artifactory (3.0.15) + atomos (0.1.3) + aws-eventstream (1.1.0) + aws-partitions (1.416.0) + aws-sdk-core (3.111.0) + aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (~> 1, >= 1.239.0) + aws-sigv4 (~> 1.1) + jmespath (~> 1.0) + aws-sdk-kms (1.41.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-s3 (1.87.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.1) + aws-sigv4 (1.2.2) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + claide (1.0.3) + colored (1.2) + colored2 (3.1.2) + commander-fastlane (4.4.6) + highline (~> 1.7.2) + declarative (0.0.20) + declarative-option (0.1.0) + digest-crc (0.6.3) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.7.6) + emoji_regex (3.2.1) + excon (0.78.1) + faraday (1.3.0) + faraday-net_http (~> 1.0) + multipart-post (>= 1.2, < 3) + ruby2_keywords + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) + http-cookie (~> 1.0.0) + faraday-net_http (1.0.1) + faraday_middleware (1.0.0) + faraday (~> 1.0) + fastimage (2.2.1) + fastlane (2.172.0) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.3, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored + commander-fastlane (>= 4.4.6, < 5.0.0) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 1.0) + fastimage (>= 2.1.0, < 3.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-api-client (>= 0.37.0, < 0.39.0) + google-cloud-storage (>= 1.15.0, < 2.0.0) + highline (>= 1.7.2, < 2.0.0) + json (< 3.0.0) + jwt (>= 2.1.0, < 3) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (~> 2.0.0) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.3) + simctl (~> 1.6.3) + slack-notifier (>= 2.0.0, < 3.0.0) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (>= 1.4.5, < 2.0.0) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.3.0) + xcpretty-travis-formatter (>= 0.0.3) + gh_inspector (1.1.3) + google-api-client (0.38.0) + addressable (~> 2.5, >= 2.5.1) + googleauth (~> 0.9) + httpclient (>= 2.8.1, < 3.0) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.0) + signet (~> 0.12) + google-cloud-core (1.5.0) + google-cloud-env (~> 1.0) + google-cloud-errors (~> 1.0) + google-cloud-env (1.4.0) + faraday (>= 0.17.3, < 2.0) + google-cloud-errors (1.0.1) + google-cloud-storage (1.29.2) + addressable (~> 2.5) + digest-crc (~> 0.4) + google-api-client (~> 0.33) + google-cloud-core (~> 1.2) + googleauth (~> 0.9) + mini_mime (~> 1.0) + googleauth (0.14.0) + faraday (>= 0.17.3, < 2.0) + jwt (>= 1.4, < 3.0) + memoist (~> 0.16) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (~> 0.14) + highline (1.7.10) + http-cookie (1.0.3) + domain_name (~> 0.5) + httpclient (2.8.3) + jmespath (1.4.0) + json (2.5.1) + jwt (2.2.2) + memoist (0.16.2) + mini_magick (4.11.0) + mini_mime (1.0.2) + multi_json (1.15.0) + multipart-post (2.0.0) + nanaimo (0.3.0) + naturally (2.2.0) + os (1.1.1) + plist (3.6.0) + public_suffix (4.0.6) + rake (13.0.3) + representable (3.0.4) + declarative (< 0.1.0) + declarative-option (< 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rouge (2.0.7) + ruby2_keywords (0.0.2) + rubyzip (2.3.0) + security (0.1.3) + signet (0.14.0) + addressable (~> 2.3) + faraday (>= 0.17.3, < 2.0) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simctl (1.6.8) + CFPropertyList + naturally + slack-notifier (2.3.2) + terminal-notifier (2.0.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + tty-cursor (0.7.1) + tty-screen (0.8.1) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + uber (0.1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.7-x64-mingw32) + unicode-display_width (1.7.0) + word_wrap (1.0.0) + xcodeproj (1.19.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + xcpretty (0.3.0) + rouge (~> 2.0.7) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + x64-mingw32 + +DEPENDENCIES + fastlane + +BUNDLED WITH + 2.1.4 diff --git a/README.md b/README.md index 8c74bca3..af798577 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ # arduino-usb-terminal Terminal-like app to send commands to Arduino through USB - +![Android CI](https://github.com/k4biri/arduino-usb-terminal/workflows/Android%20CI/badge.svg) + This app simplifies testing your Arduino components that work with direct usb commands by giving you the ability to send custom commands and view the returned message from your Arduino device. diff --git a/app/build.gradle b/app/build.gradle index 100b66dc..f354c54e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,5 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' apply plugin: 'koin' repositories { @@ -8,8 +7,7 @@ repositories { } android { - compileSdkVersion 29 - buildToolsVersion "29.0.3" + compileSdkVersion 30 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -19,7 +17,7 @@ android { defaultConfig { applicationId "org.kabiri.android.usbterminal" minSdkVersion 23 - targetSdkVersion 29 + targetSdkVersion 30 versionCode 7 versionName "0.7.0" @@ -33,15 +31,19 @@ android { } } + buildFeatures { + viewBinding true + } + } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.core:core-ktx:1.2.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.core:core-ktx:1.3.2' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' // Sentry Tracking implementation 'io.sentry:sentry-android-core:2.0.2' @@ -52,13 +54,22 @@ dependencies { // Coroutines implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2' // testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.0-RC2' - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test.ext:junit:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + // unit test libs + testImplementation 'junit:junit:4.13.1' + testImplementation "com.google.truth:truth:1.1.2" + + // instrumented test libs + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + // Espresso + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + // Hamcrest for view matching + androidTestImplementation 'org.hamcrest:hamcrest-library:1.3' + androidTestImplementation 'androidx.test:runner:1.3.0' + androidTestImplementation 'androidx.test:rules:1.3.0' /** * This library helps to automate some parts of the USB serial connection. diff --git a/app/src/androidTest/java/org/kabiri/android/usbterminal/ExampleInstrumentedTest.kt b/app/src/androidTest/java/org/kabiri/android/usbterminal/ExampleInstrumentedTest.kt deleted file mode 100644 index 5c85d7e2..00000000 --- a/app/src/androidTest/java/org/kabiri/android/usbterminal/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package org.kabiri.android.usbterminal - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("org.kabiri.android.usbterminal", appContext.packageName) - } -} diff --git a/app/src/androidTest/java/org/kabiri/android/usbterminal/MainActivityTest.kt b/app/src/androidTest/java/org/kabiri/android/usbterminal/MainActivityTest.kt new file mode 100644 index 00000000..f49d7fbe --- /dev/null +++ b/app/src/androidTest/java/org/kabiri/android/usbterminal/MainActivityTest.kt @@ -0,0 +1,23 @@ +package org.kabiri.android.usbterminal + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.ext.junit.rules.ActivityScenarioRule +import org.junit.Rule +import org.junit.Test + + +class MainActivityTest { + + @get:Rule + var rule = ActivityScenarioRule(MainActivity::class.java) + + @Test + fun checkUiViewsAreDisplayed() { + onView(withId(R.id.tvOutput)).check(matches(isDisplayed())) + onView(withId(R.id.btEnter)).check(matches(isDisplayed())) + onView(withId(R.id.etInput)).check(matches(isDisplayed())) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5a260235..f89a8e95 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,7 +16,8 @@ android:supportsRtl="true" android:theme="@style/AppTheme"> - + + diff --git a/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt b/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt index 43f3a631..dd5d9661 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt @@ -8,9 +8,7 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.Observer -import io.sentry.core.Sentry -import kotlinx.android.synthetic.main.activity_main.* +import org.kabiri.android.usbterminal.databinding.ActivityMainBinding import org.kabiri.android.usbterminal.viewmodel.MainActivityViewModel import org.koin.android.viewmodel.ext.android.viewModel @@ -22,34 +20,36 @@ class MainActivity : AppCompatActivity() { } private val viewModel: MainActivityViewModel by viewModel() + private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) + setContentView(R.layout.activity_main) + binding = ActivityMainBinding.inflate(layoutInflater) // make the text view scrollable: - tvOutput.movementMethod = ScrollingMovementMethod() + binding.tvOutput.movementMethod = ScrollingMovementMethod() // open the device and port when the permission is granted by user. - viewModel.getGrantedDevice().observe(this, Observer { device -> + viewModel.getGrantedDevice().observe(this, { device -> viewModel.openDeviceAndPort(device) }) - viewModel.getLiveOutput().observe(this, Observer { + viewModel.getLiveOutput().observe(this, { val spannable = SpannableString(it.text) spannable.setSpan( it.getAppearance(this), 0, it.text.length, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE) - tvOutput.append(it.text) + binding.tvOutput.append(it.text) }) // send the command to device when the button is clicked. - btEnter.setOnClickListener { - val input = etInput.text.toString() + binding.btEnter.setOnClickListener { + val input = binding.etInput.text.toString() if (viewModel.serialWrite(input)) - etInput.setText("") // clear the terminal input. + binding.etInput.setText("") // clear the terminal input. else Log.e(TAG, "The message was not sent to Arduino") } } diff --git a/app/src/test/java/org/kabiri/android/usbterminal/ExampleUnitTest.kt b/app/src/test/java/org/kabiri/android/usbterminal/ExampleUnitTest.kt deleted file mode 100644 index 90abd693..00000000 --- a/app/src/test/java/org/kabiri/android/usbterminal/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package org.kabiri.android.usbterminal - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} diff --git a/app/src/test/java/org/kabiri/android/usbterminal/model/OutputTextTest.kt b/app/src/test/java/org/kabiri/android/usbterminal/model/OutputTextTest.kt new file mode 100644 index 00000000..de3a2e9a --- /dev/null +++ b/app/src/test/java/org/kabiri/android/usbterminal/model/OutputTextTest.kt @@ -0,0 +1,33 @@ +package org.kabiri.android.usbterminal.model + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class OutputTextTest { + + @Test + fun testFields() { + val dummyText = "does not matter" + + var sut = OutputText( + text = dummyText, + type = OutputText.OutputType.TYPE_NORMAL + ) + assertThat(sut.text).isEqualTo(dummyText) + assertThat(sut.type).isEqualTo(OutputText.OutputType.TYPE_NORMAL) + + sut = OutputText( + text = dummyText, + type = OutputText.OutputType.TYPE_INFO + ) + assertThat(sut.text).isEqualTo(dummyText) + assertThat(sut.type).isEqualTo(OutputText.OutputType.TYPE_INFO) + + sut = OutputText( + text = dummyText, + type = OutputText.OutputType.TYPE_ERROR + ) + assertThat(sut.text).isEqualTo(dummyText) + assertThat(sut.type).isEqualTo(OutputText.OutputType.TYPE_ERROR) + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index c7f904f8..0005aef7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,14 +1,14 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.72' - ext.koin_version = '2.1.5' + ext.kotlin_version = '1.4.21' + ext.koin_version = '2.2.0' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.6.3' + classpath 'com.android.tools.build:gradle:4.1.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.koin:koin-gradle-plugin:$koin_version" diff --git a/fastlane/Appfile b/fastlane/Appfile new file mode 100644 index 00000000..235d8913 --- /dev/null +++ b/fastlane/Appfile @@ -0,0 +1,2 @@ +json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one +package_name("org.kabiri.android.usbterminal") # e.g. com.krausefx.app diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 00000000..1e03a189 --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,38 @@ +# This file contains the fastlane.tools configuration +# You can find the documentation at https://docs.fastlane.tools +# +# For a list of all available actions, check out +# +# https://docs.fastlane.tools/actions +# +# For a list of all available plugins, check out +# +# https://docs.fastlane.tools/plugins/available-plugins +# + +# Uncomment the line if you want fastlane to automatically update itself +# update_fastlane + +default_platform(:android) + +platform :android do + desc "Runs all the tests" + lane :run_unit_tests do + gradle(task: "testDebugUnitTest --stacktrace") + end + + desc "Submit a new Beta Build to Crashlytics Beta" + lane :beta do + gradle(task: "clean assembleRelease") + crashlytics + + # sh "your_script.sh" + # You can also use other beta testing services here + end + + desc "Deploy a new version to the Google Play" + lane :deploy do + gradle(task: "clean assembleRelease") + upload_to_play_store + end +end diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8bb656d7..94d93d58 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Mar 26 09:16:25 CET 2020 +#Sun Jan 24 09:56:06 CET 2021 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip