diff --git a/.aiexclude b/.aiexclude
new file mode 100644
index 00000000..844da302
--- /dev/null
+++ b/.aiexclude
@@ -0,0 +1,89 @@
+# Built application files
+*.apk
+*.ap_
+*.aab
+
+# Files for the ART/Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+out/
+# Uncomment the following line in case you need and you don't have the release build type files in your app
+# release/
+
+# Gradle files
+.gradle/
+build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+# Android Studio Navigation editor temp files
+.navigation/
+
+# Android Studio captures folder
+captures/
+
+# IntelliJ
+*.iml
+.idea/*
+
+# Keystore files
+# Uncomment the following lines if you do not want to check your keystore files in.
+#*.jks
+#*.keystore
+
+# External native build folder generated in Android Studio 2.2 and later
+.externalNativeBuild
+
+# Google Services (e.g. APIs or Firebase)
+# google-services.json
+
+# Freeline
+freeline.py
+freeline/
+freeline_project_description.json
+
+# fastlane
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output
+fastlane/readme.md
+
+# Version control
+vcs.xml
+
+# lint
+lint/intermediates/
+lint/generated/
+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
+.idea/misc.xml
+.idea/misc.xml
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 29704536..5aca1c2e 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -25,10 +25,10 @@ commands:
executors:
android-machine:
machine:
- image: android:2022.12.1
+ image: android:2024.01.1
android-docker:
docker:
- - image: cimg/android:2023.02
+ - image: cimg/android:2024.01
jobs:
android-test:
@@ -64,15 +64,35 @@ jobs:
name: Prepare Fastlane
command: sudo bundle update
- run:
- name: Distribute to Firebase AppTester
+ name: Distribute to Firebase AppTester Dev
command: bundle exec fastlane distDev
- store_artifacts:
path: /home/circleci/project/app/build/outputs/apk/debug/app-debug.apk
destination: fastlane-output-debug
+ distribute-internal-testing:
+ executor: android-docker
+ resource_class: large
+ steps:
+ - checkout
+ - restore_gradle_cache
+ - run:
+ name: Prepare Fastlane
+ command: sudo bundle update
+ - run:
+ name: Distribute to Google PlayStore Internal Testing
+ command: bundle exec fastlane deploy
+ - run:
+ name: Distribute to Firebase AppTester Prod
+ command: bundle exec fastlane distProd
+ - store_artifacts:
+ path: /home/circleci/project/app/build/outputs/bundle/release/app-release.aab
+ destination: fastlane-output-release
+
workflows:
workflow:
jobs:
- android-test:
context: SonarCloud
- distribute-dev
+ - distribute-internal-testing
diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index 6a96ad37..e03a03bd 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -1,61 +1,61 @@
name: Android CI
-on: [push]
+on: [ push ]
jobs:
unit-test:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
- - uses: ruby/setup-ruby@v1
- with:
- ruby-version: '3.0'
- bundler-cache: true
- - name: set up JDK 11
- uses: actions/setup-java@v1
- with:
- distribution: 'zulu'
- java-version: '11'
- - name: Cache Gradle wrapper
- uses: actions/cache@v3
- with:
- path: ~/.gradle/wrapper
- key: ${{ runner.OS }}-gradle-wrapper-cache-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
- restore-keys: |
- ${{ runner.OS }}-gradle-wrapper-cache-
- - name: Cache Gradle caches
- uses: actions/cache@v3
- with:
- path: ~/.gradle/caches
- key: ${{ runner.OS }}-gradle-caches-cache-${{ hashFiles('build.gradle') }}
- restore-keys: |
- ${{ runner.OS }}-gradle-caches-cache-
- - name: generate ksProp file
- run: ./gradlew generateKsPropFile
- - name: generate google-services.json file
- run: ./gradlew generateGoogleServicesJson
- env:
- GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }}
- - 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
+ - uses: actions/checkout@v3
+ - uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '3.1'
+ bundler-cache: true
+ - name: set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'zulu'
+ java-version: '17'
+ - name: Cache Gradle wrapper
+ uses: actions/cache@v3
+ with:
+ path: ~/.gradle/wrapper
+ key: ${{ runner.OS }}-gradle-wrapper-cache-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
+ restore-keys: |
+ ${{ runner.OS }}-gradle-wrapper-cache-
+ - name: Cache Gradle caches
+ uses: actions/cache@v3
+ with:
+ path: ~/.gradle/caches
+ key: ${{ runner.OS }}-gradle-caches-cache-${{ hashFiles('build.gradle') }}
+ restore-keys: |
+ ${{ runner.OS }}-gradle-caches-cache-
+ - name: generate ksProp file
+ run: ./gradlew generateKsPropFile
+ - name: generate google-services.json file
+ run: ./gradlew generateGoogleServicesJson
+ env:
+ GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }}
+ - 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@v4
+ 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 11
+ - name: set up JDK 17
uses: actions/setup-java@v1
with:
distribution: 'zulu'
- java-version: '11'
+ java-version: '17'
- name: generate ksProp file
run: ./gradlew generateKsPropFile
- name: generate google-services.json file
@@ -65,27 +65,32 @@ jobs:
- name: Lint
run: bash ./gradlew lintDebug
- name: Lint results
- uses: actions/upload-artifact@v1
+ uses: actions/upload-artifact@v4
with:
name: app
path: app/build/reports/lint-results-debug.html
ui-test:
- runs-on: macos-latest
+ runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2
- - name: set up JDK 11
- uses: actions/setup-java@v1
+ - name: set up JDK 17
+ uses: actions/setup-java@v4
with:
distribution: 'zulu'
- java-version: '11'
+ java-version: '17'
- name: generate ksProp file
run: ./gradlew generateKsPropFile
- name: generate google-services.json file
run: ./gradlew generateGoogleServicesJson
env:
GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }}
+ - name: Enable KVM group perms
+ run: |
+ echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
+ sudo udevadm control --reload-rules
+ sudo udevadm trigger --name-match=kvm
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
diff --git a/.gitignore b/.gitignore
index 7769739e..844da302 100644
--- a/.gitignore
+++ b/.gitignore
@@ -86,3 +86,4 @@ app/build.gradle
.idea/.name
.idea/codeStyles/Project.xml
.idea/misc.xml
+.idea/misc.xml
diff --git a/.idea/androidTestResultsUserPreferences.xml b/.idea/androidTestResultsUserPreferences.xml
index c14ef4bb..87ee7a8a 100644
--- a/.idea/androidTestResultsUserPreferences.xml
+++ b/.idea/androidTestResultsUserPreferences.xml
@@ -3,6 +3,99 @@
diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
new file mode 100644
index 00000000..0ff848d8
--- /dev/null
+++ b/.idea/deploymentTargetDropDown.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
new file mode 100644
index 00000000..0fc31131
--- /dev/null
+++ b/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 669e0711..49bf8ca1 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,3 @@
-
@@ -6,7 +5,7 @@
-
+
diff --git a/Gemfile.lock b/Gemfile.lock
index 7a0078bc..d5b4e4ee 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,43 +1,47 @@
GEM
remote: https://rubygems.org/
specs:
- CFPropertyList (3.0.6)
+ CFPropertyList (3.0.7)
+ base64
+ nkf
rexml
- addressable (2.8.1)
- public_suffix (>= 2.0.2, < 6.0)
- artifactory (3.0.15)
+ addressable (2.8.7)
+ public_suffix (>= 2.0.2, < 7.0)
+ artifactory (3.0.17)
atomos (0.1.3)
- aws-eventstream (1.2.0)
- aws-partitions (1.725.0)
- aws-sdk-core (3.170.0)
- aws-eventstream (~> 1, >= 1.0.2)
- aws-partitions (~> 1, >= 1.651.0)
- aws-sigv4 (~> 1.5)
+ aws-eventstream (1.3.2)
+ aws-partitions (1.1095.0)
+ aws-sdk-core (3.222.3)
+ aws-eventstream (~> 1, >= 1.3.0)
+ aws-partitions (~> 1, >= 1.992.0)
+ aws-sigv4 (~> 1.9)
+ base64
jmespath (~> 1, >= 1.6.1)
- aws-sdk-kms (1.63.0)
- aws-sdk-core (~> 3, >= 3.165.0)
- aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.119.1)
- aws-sdk-core (~> 3, >= 3.165.0)
+ logger
+ aws-sdk-kms (1.99.0)
+ aws-sdk-core (~> 3, >= 3.216.0)
+ aws-sigv4 (~> 1.5)
+ aws-sdk-s3 (1.184.0)
+ aws-sdk-core (~> 3, >= 3.216.0)
aws-sdk-kms (~> 1)
- aws-sigv4 (~> 1.4)
- aws-sigv4 (1.5.2)
+ aws-sigv4 (~> 1.5)
+ aws-sigv4 (1.11.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
+ base64 (0.2.0)
claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
declarative (0.0.20)
- digest-crc (0.6.4)
+ digest-crc (0.7.0)
rake (>= 12.0.0, < 14.0.0)
- domain_name (0.5.20190701)
- unf (>= 0.0.5, < 1.0.0)
+ domain_name (0.6.20240107)
dotenv (2.8.1)
emoji_regex (3.2.3)
- excon (0.99.0)
- faraday (1.10.3)
+ excon (0.112.0)
+ faraday (1.10.4)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
@@ -56,17 +60,17 @@ GEM
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
- faraday-multipart (1.0.4)
- multipart-post (~> 2)
- faraday-net_http (1.0.1)
+ faraday-multipart (1.1.0)
+ multipart-post (~> 2.0)
+ faraday-net_http (1.0.2)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
- faraday_middleware (1.2.0)
+ faraday_middleware (1.2.1)
faraday (~> 1.0)
- fastimage (2.2.6)
- fastlane (2.212.1)
+ fastimage (2.4.0)
+ fastlane (2.218.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@@ -87,94 +91,108 @@ GEM
google-apis-playcustomapp_v1 (~> 0.1)
google-cloud-storage (~> 1.31)
highline (~> 2.0)
+ http-cookie (~> 1.0.5)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
- multipart-post (~> 2.0.0)
+ multipart-post (>= 2.0.0, < 3.0.0)
naturally (~> 2.2)
- optparse (~> 0.1.1)
+ optparse (>= 0.1.1)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
terminal-notifier (>= 2.0.0, < 3.0.0)
- terminal-table (>= 1.4.5, < 2.0.0)
+ terminal-table (~> 3)
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)
- fastlane-plugin-firebase_app_distribution (0.5.0)
+ fastlane-plugin-firebase_app_distribution (0.9.1)
+ google-apis-firebaseappdistribution_v1 (~> 0.3.0)
+ google-apis-firebaseappdistribution_v1alpha (~> 0.2.0)
gh_inspector (1.1.3)
- google-apis-androidpublisher_v3 (0.35.0)
- google-apis-core (>= 0.11.0, < 2.a)
- google-apis-core (0.11.0)
+ google-apis-androidpublisher_v3 (0.78.0)
+ google-apis-core (>= 0.15.0, < 2.a)
+ google-apis-core (0.17.0)
addressable (~> 2.5, >= 2.5.1)
- googleauth (>= 0.16.2, < 2.a)
- httpclient (>= 2.8.1, < 3.a)
+ googleauth (~> 1.9)
+ httpclient (>= 2.8.3, < 3.a)
mini_mime (~> 1.0)
+ mutex_m
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
- rexml
- webrick
- google-apis-iamcredentials_v1 (0.17.0)
+ google-apis-firebaseappdistribution_v1 (0.3.0)
google-apis-core (>= 0.11.0, < 2.a)
- google-apis-playcustomapp_v1 (0.13.0)
+ google-apis-firebaseappdistribution_v1alpha (0.2.0)
google-apis-core (>= 0.11.0, < 2.a)
- google-apis-storage_v1 (0.19.0)
- google-apis-core (>= 0.9.0, < 2.a)
- google-cloud-core (1.6.0)
- google-cloud-env (~> 1.0)
+ google-apis-iamcredentials_v1 (0.23.0)
+ google-apis-core (>= 0.15.0, < 2.a)
+ google-apis-playcustomapp_v1 (0.16.0)
+ google-apis-core (>= 0.15.0, < 2.a)
+ google-apis-storage_v1 (0.50.0)
+ google-apis-core (>= 0.15.0, < 2.a)
+ google-cloud-core (1.8.0)
+ google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0)
- google-cloud-env (1.6.0)
- faraday (>= 0.17.3, < 3.0)
- google-cloud-errors (1.3.1)
- google-cloud-storage (1.44.0)
+ google-cloud-env (2.3.0)
+ base64 (~> 0.2)
+ faraday (>= 1.0, < 3.a)
+ google-cloud-errors (1.5.0)
+ google-cloud-storage (1.56.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
- google-apis-iamcredentials_v1 (~> 0.1)
- google-apis-storage_v1 (~> 0.19.0)
+ google-apis-core (~> 0.13)
+ google-apis-iamcredentials_v1 (~> 0.18)
+ google-apis-storage_v1 (>= 0.42)
google-cloud-core (~> 1.6)
- googleauth (>= 0.16.2, < 2.a)
+ googleauth (~> 1.9)
mini_mime (~> 1.0)
- googleauth (1.3.0)
- faraday (>= 0.17.3, < 3.a)
+ google-logging-utils (0.2.0)
+ googleauth (1.14.0)
+ faraday (>= 1.0, < 3.a)
+ google-cloud-env (~> 2.2)
+ google-logging-utils (~> 0.1)
jwt (>= 1.4, < 3.0)
- memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
- http-cookie (1.0.5)
+ http-cookie (1.0.8)
domain_name (~> 0.5)
- httpclient (2.8.3)
+ httpclient (2.9.0)
+ mutex_m
jmespath (1.6.2)
- json (2.6.3)
- jwt (2.7.0)
- memoist (0.16.2)
- mini_magick (4.12.0)
- mini_mime (1.1.2)
+ json (2.11.3)
+ jwt (2.10.1)
+ base64
+ logger (1.7.0)
+ mini_magick (4.13.2)
+ mini_mime (1.1.5)
multi_json (1.15.0)
- multipart-post (2.0.0)
- nanaimo (0.3.0)
+ multipart-post (2.4.1)
+ mutex_m (0.3.0)
+ nanaimo (0.4.0)
naturally (2.2.1)
- optparse (0.1.1)
+ nkf (0.2.0)
+ optparse (0.6.0)
os (1.1.4)
- plist (3.7.0)
- public_suffix (5.0.1)
- rake (13.0.6)
+ plist (3.7.2)
+ public_suffix (6.0.2)
+ rake (13.2.1)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
- rexml (3.2.5)
+ rexml (3.4.1)
rouge (2.0.7)
ruby2_keywords (0.0.5)
- rubyzip (2.3.2)
+ rubyzip (2.4.1)
security (0.1.3)
- signet (0.17.0)
+ signet (0.20.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
@@ -183,28 +201,23 @@ GEM
CFPropertyList
naturally
terminal-notifier (2.0.0)
- terminal-table (1.8.0)
- unicode-display_width (~> 1.1, >= 1.1.1)
+ terminal-table (3.0.2)
+ unicode-display_width (>= 1.1.1, < 3)
trailblazer-option (0.1.2)
tty-cursor (0.7.1)
- tty-screen (0.8.1)
+ tty-screen (0.8.2)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
- unf (0.1.4)
- unf_ext
- unf_ext (0.0.8.2)
- unf_ext (0.0.8.2-x64-mingw32)
- unicode-display_width (1.8.0)
- webrick (1.8.1)
+ unicode-display_width (2.6.0)
word_wrap (1.0.0)
- xcodeproj (1.22.0)
+ xcodeproj (1.27.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
- nanaimo (~> 0.3.0)
- rexml (~> 3.2.4)
+ nanaimo (~> 0.4.0)
+ rexml (>= 3.3.6, < 4.0)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1)
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 253fb968..5ebd0cf9 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -6,6 +6,7 @@ plugins {
id("com.google.gms.google-services")
id("com.google.firebase.crashlytics")
id("org.jetbrains.kotlin.android")
+ id("org.jetbrains.kotlin.plugin.compose")
id("kotlin-kapt")
id("dagger.hilt.android.plugin")
id("jacoco")
@@ -15,19 +16,28 @@ repositories {
}
android {
- compileSdk = 33
compileOptions {
- sourceCompatibility = JavaVersion.VERSION_1_8
- targetCompatibility = JavaVersion.VERSION_1_8
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
}
+ kotlin {
+ jvmToolchain(17)
+ }
+
+ buildFeatures {
+ compose = true
+ buildConfig = true
+ }
+
+ compileSdk = 35
defaultConfig {
applicationId = "org.kabiri.android.usbterminal"
- minSdk = 23
- targetSdk = 33
- versionCode = System.getenv("CIRCLE_BUILD_NUM")?.toIntOrNull() ?: 13
- versionName = "0.9.12${System.getenv("CIRCLE_BUILD_NUM") ?: ""}"
+ minSdk = 24
+ targetSdk = 35
+ versionCode = System.getenv("CIRCLE_BUILD_NUM")?.toIntOrNull() ?: 14
+ versionName = "0.9.84${System.getenv("CIRCLE_BUILD_NUM") ?: ""}"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@@ -48,7 +58,7 @@ android {
buildTypes {
named("release") {
signingConfig = signingConfigs.findByName("release")
- isMinifyEnabled = false
+ isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
named("debug") {
@@ -71,6 +81,13 @@ android {
}
}
+ packaging {
+ resources {
+ excludes += "META-INF/LICENSE.md"
+ excludes += "META-INF/LICENSE-notice.md"
+ }
+ }
+
namespace = "org.kabiri.android.usbterminal"
}
@@ -93,12 +110,12 @@ tasks.register("jacocoTestReport") {
}
val fileFilter = listOf("**/R.class", "**/R$*.class", "**/BuildConfig.*", "**/Manifest*.*", "**/*Test*.*", "android/**/*.*")
- val debugTree = fileTree("${buildDir}/tmp/kotlin-classes/debug") { exclude(fileFilter) }
- val mainSrc = "${project.projectDir}/src/main/kotlin"
+ val debugTree = fileTree("${layout.buildDirectory}/tmp/kotlin-classes/debug") { exclude(fileFilter) }
+ val mainSrc = "${layout.projectDirectory}/src/main/kotlin"
sourceDirectories.from(files(setOf(mainSrc)))
classDirectories.from(files(setOf(debugTree)))
- executionData.from(fileTree(buildDir) { include(setOf(
+ executionData.from(fileTree(layout.buildDirectory) { include(setOf(
"outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec",
"outputs/managed_device_code_coverage/pixel2api30/coverage.ec"
))})
@@ -123,6 +140,19 @@ tasks.register("generateGoogleServicesJson") {
}
}
+tasks.register("generateKsFile") {
+ doLast {
+ val jsonFileName = "bad.json"
+ val encodedFileContent = System.getenv("KS_USB_TERMINAL_PLAY_STORE_RAW")
+ val decodedBytes = Base64.getDecoder().decode(encodedFileContent)
+ File(projectDir, jsonFileName).apply {
+ createNewFile()
+ writeBytes(decodedBytes)
+ println("generated ${this.path}")
+ }
+ }
+}
+
tasks.register("generateKsPropFile") {
doLast {
val configFileName = "keystore.properties"
@@ -130,10 +160,10 @@ tasks.register("generateKsPropFile") {
createNewFile()
writeText("""
# Gradle signing properties for app module
- release.file=${System.getenv("KS_PATH")}
- release.storePassword=${System.getenv("KS_PASSWORD")}
- release.keyAlias=${System.getenv("KS_KEY_ALIAS")}
- release.keyPassword=${System.getenv("KS_KEY_PASSWORD")}
+ release.file=${System.getenv("USB_TERMINAL_KS_PATH")}
+ release.storePassword=${System.getenv("USB_TERMINAL_KS_PASSWORD")}
+ release.keyAlias=${System.getenv("USB_TERMINAL_KS_KEY_ALIAS")}
+ release.keyPassword=${System.getenv("USB_TERMINAL_KS_KEY_PASSWORD")}
""".trimIndent())
println("generated ${this.path}")
}
@@ -152,6 +182,18 @@ tasks.register("generateAppDistKey") {
}
}
+tasks.register("generateInternalReleaseKey") {
+ doLast {
+ val jsonFileName = "internal-release-key.json"
+ val fileContent = System.getenv("SERVICE_ACCOUNT_USB_TERMINAL_PLAY_STORE_RAW")
+ File(rootDir, jsonFileName).apply {
+ createNewFile()
+ writeText(fileContent)
+ println("generated ${this.path}")
+ }
+ }
+}
+
fun loadKeyStore(name: String): Properties? {
val ksProp = Properties()
val ksPropFile = file(name)
@@ -166,11 +208,14 @@ fun loadKeyStore(name: String): Properties? {
val firebase_bom_version: String by project
val hilt_version: String by project
+val coroutines_version: String by project
+val material_version: String by project
+val mockk_version: String by project
dependencies {
- implementation("androidx.appcompat:appcompat:1.6.0")
- implementation("androidx.core:core-ktx:1.9.0")
- implementation("androidx.constraintlayout:constraintlayout:2.1.4")
+ implementation("androidx.appcompat:appcompat:1.7.0")
+ implementation("androidx.core:core-ktx:1.16.0")
+ implementation("androidx.constraintlayout:constraintlayout:2.2.1")
// Firebase
implementation(platform("com.google.firebase:firebase-bom:$firebase_bom_version"))
@@ -182,37 +227,61 @@ dependencies {
kapt("com.google.dagger:hilt-compiler:$hilt_version")
// Coroutines
- implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.1")
- implementation("androidx.activity:activity-compose:1.6.1")
- implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1")
+ implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
+ implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7")
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")
-
- // hilt testing
- // more info:
- // https://developer.android.com/training/dependency-injection/hilt-testing
- androidTestImplementation("com.google.dagger:hilt-android-testing:$hilt_version")
- kaptAndroidTest("com.google.dagger:hilt-android-compiler:$hilt_version")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
+
+ // Compose Bom
+ val composeBom = platform("androidx.compose:compose-bom:2023.06.01")
+ implementation(composeBom)
+ androidTestImplementation(composeBom)
+ implementation("androidx.compose.foundation:foundation")
+ implementation("androidx.compose.material3:material3")
+ // Compose - Android Studio Preview support
+ implementation("androidx.compose.ui:ui-tooling-preview")
+ debugImplementation("androidx.compose.ui:ui-tooling")
+ implementation("androidx.activity:activity-compose:1.10.1")
+
+ // Other UI Libraries
+ implementation("com.google.android.material:material:$material_version")
+
+ // data
+ implementation("androidx.datastore:datastore-preferences:1.1.4")
// unit test libs
testImplementation("junit:junit:4.13.2")
- testImplementation("com.google.truth:truth:1.1.3")
// instrumented test libs
- androidTestImplementation("androidx.test:core:1.5.0")
- androidTestImplementation("androidx.test.ext:junit:1.1.5")
- androidTestImplementation("androidx.test.ext:junit-ktx:1.1.5")
- // Espresso
- androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+ androidTestImplementation("androidx.test:core:1.6.1")
+ androidTestImplementation("androidx.test.ext:junit:1.2.1")
+ androidTestImplementation("androidx.test.ext:junit-ktx:1.2.1")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
+
// Hamcrest for view matching
androidTestImplementation("org.hamcrest:hamcrest-library:2.2")
- androidTestImplementation("androidx.test:runner:1.5.2")
- androidTestImplementation("androidx.test:rules:1.5.0")
-
- /**
- * This library helps to automate some parts of the USB serial connection.
- * For more information, visit: https://github.com/felHR85/UsbSerial
- */
- implementation("com.github.felHR85:UsbSerial:6.1.0")
+ androidTestImplementation("androidx.test:runner:1.6.2")
+ androidTestImplementation("androidx.test:rules:1.6.1")
+
+ // coroutine testing
+ testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version")
+ androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version")
+
+ // google truth for assertions
+ testImplementation("com.google.truth:truth:1.1.3")
+ androidTestImplementation("androidx.test.ext:truth:1.6.0")
+
+ // mockk
+ testImplementation("io.mockk:mockk-android:$mockk_version")
+ testImplementation("io.mockk:mockk-agent:$mockk_version")
+ androidTestImplementation("io.mockk:mockk-android:$mockk_version")
+ androidTestImplementation("io.mockk:mockk-agent:$mockk_version")
+
+ // hilt testing - https://developer.android.com/training/dependency-injection/hilt-testing
+ androidTestImplementation("com.google.dagger:hilt-android-testing:$hilt_version")
+ kaptAndroidTest("com.google.dagger:hilt-android-compiler:$hilt_version")
+
+ // Android Serial Controller
+ implementation("com.github.superus8r:UsbSerial:6.1.1")
}
\ No newline at end of file
diff --git a/app/src/androidTest/java/org/kabiri/android/usbterminal/common/FrequentMocks.kt b/app/src/androidTest/java/org/kabiri/android/usbterminal/common/FrequentMocks.kt
new file mode 100644
index 00000000..141f6b8e
--- /dev/null
+++ b/app/src/androidTest/java/org/kabiri/android/usbterminal/common/FrequentMocks.kt
@@ -0,0 +1,27 @@
+package org.kabiri.android.usbterminal.common
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import org.kabiri.android.usbterminal.data.repository.IUserSettingRepository
+import org.kabiri.android.usbterminal.model.UserSettingPreferences
+
+internal fun getFakeUserSettingRepository(
+ onSetBaudRate: () -> Unit = {},
+ fakeUserSetting: UserSettingPreferences = UserSettingPreferences(baudRate = 123)
+): IUserSettingRepository {
+ return object: IUserSettingRepository {
+ override val preferenceFlow: Flow
+ get() = flowOf(fakeUserSetting)
+
+ override suspend fun setBaudRate(baudRate: Int) {
+ onSetBaudRate()
+ }
+
+ override suspend fun clear() {}
+
+ override suspend fun fetchInitialPreferences(): UserSettingPreferences {
+ return fakeUserSetting
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/org/kabiri/android/usbterminal/data/repository/UserSettingRepositoryAndroidTest.kt b/app/src/androidTest/java/org/kabiri/android/usbterminal/data/repository/UserSettingRepositoryAndroidTest.kt
new file mode 100644
index 00000000..de19d3ae
--- /dev/null
+++ b/app/src/androidTest/java/org/kabiri/android/usbterminal/data/repository/UserSettingRepositoryAndroidTest.kt
@@ -0,0 +1,91 @@
+package org.kabiri.android.usbterminal.data.repository
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.PreferenceDataStoreFactory
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.preferencesDataStoreFile
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.kabiri.android.usbterminal.model.UserSettingPreferences
+
+private const val TEST_DATA_STORE_NAME = "test_data_store"
+@RunWith(AndroidJUnit4::class)
+class UserSettingRepositoryAndroidTest {
+
+ private val testCoroutineDispatcher: TestDispatcher = StandardTestDispatcher()
+ private val testCoroutineScope = TestScope(testCoroutineDispatcher + Job())
+ private val testContext = ApplicationProvider.getApplicationContext()
+
+ private val testDataStore: DataStore =
+ PreferenceDataStoreFactory.create(
+ scope = testCoroutineScope,
+ produceFile = { testContext.preferencesDataStoreFile(TEST_DATA_STORE_NAME) }
+ )
+
+ private val repository: UserSettingRepository = UserSettingRepository(testDataStore)
+
+ @Test
+ fun testFetchInitialPreferences() {
+
+ // arrange
+ val expected = UserSettingPreferences()
+ var actual: UserSettingPreferences? = null
+
+ // act
+ testCoroutineScope.runTest {
+ actual = repository.fetchInitialPreferences()
+ }
+
+ // assert
+ assertThat(actual).isEqualTo(expected)
+ }
+
+ @Test
+ fun testWriteBaudRate() {
+
+ // arrange
+ val customBaudRate = 123
+ val expected = UserSettingPreferences(
+ baudRate = customBaudRate
+ )
+ var actual: UserSettingPreferences? = null
+
+ // act
+ testCoroutineScope.runTest {
+ repository.setBaudRate(customBaudRate)
+ actual = repository.preferenceFlow.first()
+ }
+
+ // assert
+ assertThat(actual).isEqualTo(expected)
+ }
+
+ @Test
+ fun testClearResetsValuesToDefaults() {
+
+ // arrange
+ val customBaudRate = 123
+ val expected = UserSettingPreferences()
+ var actual: UserSettingPreferences? = null
+
+ // act
+ testCoroutineScope.runTest {
+ repository.setBaudRate(customBaudRate)
+ repository.clear()
+ actual = repository.preferenceFlow.first()
+ }
+
+ // assert
+ assertThat(actual).isEqualTo(expected)
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/org/kabiri/android/usbterminal/domain/GetCustomBaudRateUseCaseAndroidTest.kt b/app/src/androidTest/java/org/kabiri/android/usbterminal/domain/GetCustomBaudRateUseCaseAndroidTest.kt
new file mode 100644
index 00000000..82a33ac1
--- /dev/null
+++ b/app/src/androidTest/java/org/kabiri/android/usbterminal/domain/GetCustomBaudRateUseCaseAndroidTest.kt
@@ -0,0 +1,30 @@
+package org.kabiri.android.usbterminal.domain
+
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.kabiri.android.usbterminal.common.getFakeUserSettingRepository
+import org.kabiri.android.usbterminal.model.UserSettingPreferences
+
+internal class GetCustomBaudRateUseCaseAndroidTest {
+
+ @Test
+ fun testGetCustomBaudRateUseCaseAndroidTestReturns() = runTest {
+
+ // arrange
+ val expectedBaudRate = 1234
+ val fakeUserSettings = UserSettingPreferences(baudRate = expectedBaudRate)
+ val fakeUserSettingRepository =
+ getFakeUserSettingRepository(fakeUserSetting = fakeUserSettings)
+ val sut = GetCustomBaudRateUseCase(
+ userSettingRepository = fakeUserSettingRepository
+ )
+
+ // act
+ val actualBaudRate = sut().first()
+
+ // assert
+ assertThat(actualBaudRate).isEqualTo(expectedBaudRate)
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/org/kabiri/android/usbterminal/domain/SetCustomBaudRateUseCaseAndroidTest.kt b/app/src/androidTest/java/org/kabiri/android/usbterminal/domain/SetCustomBaudRateUseCaseAndroidTest.kt
new file mode 100644
index 00000000..6bfb8143
--- /dev/null
+++ b/app/src/androidTest/java/org/kabiri/android/usbterminal/domain/SetCustomBaudRateUseCaseAndroidTest.kt
@@ -0,0 +1,33 @@
+package org.kabiri.android.usbterminal.domain
+
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.kabiri.android.usbterminal.common.getFakeUserSettingRepository
+
+@OptIn(ExperimentalCoroutinesApi::class)
+internal class SetCustomBaudRateUseCaseAndroidTest {
+
+ @Test
+ fun testSetCustomBaudRateUseCaseAndroidTestCallsSetBaudRateOnRepository() = runTest {
+
+ // arrange
+ var isCalledSetBaudRate = false
+ val fakeUserSettingRepository = getFakeUserSettingRepository(onSetBaudRate = {
+ isCalledSetBaudRate = true
+ })
+ val sut = SetCustomBaudRateUseCase(
+ userSettingRepository = fakeUserSettingRepository,
+ scope = this,
+ )
+
+ // act
+ sut.invoke(123)
+ advanceUntilIdle()
+
+ // assert
+ assertThat(isCalledSetBaudRate).isTrue()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a9a2fe6d..07074fa9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -22,20 +22,15 @@
-
+
-
-
-
-
-
diff --git a/app/src/main/java/org/kabiri/android/usbterminal/Constants.kt b/app/src/main/java/org/kabiri/android/usbterminal/Constants.kt
index b4357479..a9d918de 100644
--- a/app/src/main/java/org/kabiri/android/usbterminal/Constants.kt
+++ b/app/src/main/java/org/kabiri/android/usbterminal/Constants.kt
@@ -5,6 +5,6 @@ package org.kabiri.android.usbterminal
*/
class Constants {
companion object {
- const val ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"
+ const val ACTION_USB_PERMISSION = "org.kabiri.android.usbterminal.USB_PERMISSION"
}
}
\ No newline at end of file
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 a3111b18..a0d5902f 100644
--- a/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt
+++ b/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt
@@ -1,19 +1,27 @@
package org.kabiri.android.usbterminal
import android.os.Bundle
-import android.text.SpannableString
import android.text.method.ScrollingMovementMethod
import android.util.Log
+import android.view.KeyEvent
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
+import android.view.View
+import android.view.inputmethod.EditorInfo
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
-import org.kabiri.android.usbterminal.extensions.scrollToLastLine
+import kotlinx.coroutines.launch
+import org.kabiri.android.usbterminal.util.scrollToLastLine
+import org.kabiri.android.usbterminal.ui.setting.SettingModalBottomSheet
+import org.kabiri.android.usbterminal.ui.setting.SettingViewModel
import org.kabiri.android.usbterminal.viewmodel.MainActivityViewModel
@AndroidEntryPoint
@@ -24,11 +32,21 @@ class MainActivity : AppCompatActivity() {
}
private val viewModel by viewModels()
+ private val settingViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
+ // avoid system navbar or soft keyboard overlapping the content.
+ val rootView = findViewById(R.id.root_view)
+ ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, insets ->
+ val systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
+ val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime())
+ view.setPadding(0, 0, 0, maxOf(systemBarsInsets.bottom, imeInsets.bottom))
+ insets
+ }
+
val etInput = findViewById(R.id.etInput)
val tvOutput = findViewById(R.id.tvOutput)
val btEnter = findViewById