From 1f1c853647121ce4fc4d5a43481cf06b82c4fd46 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Tue, 18 Jul 2023 17:46:33 +0200 Subject: [PATCH 001/126] chore: update fastlane --- Gemfile.lock | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 7a0078bc..382d34e6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,21 +3,21 @@ GEM specs: CFPropertyList (3.0.6) rexml - addressable (2.8.1) + addressable (2.8.4) public_suffix (>= 2.0.2, < 6.0) artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.725.0) - aws-sdk-core (3.170.0) + aws-partitions (1.763.0) + aws-sdk-core (3.172.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.63.0) + aws-sdk-kms (1.64.0) aws-sdk-core (~> 3, >= 3.165.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.119.1) + aws-sdk-s3 (1.122.0) aws-sdk-core (~> 3, >= 3.165.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) @@ -66,7 +66,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.2.6) - fastlane (2.212.1) + fastlane (2.212.2) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -107,7 +107,7 @@ GEM xcpretty-travis-formatter (>= 0.0.3) fastlane-plugin-firebase_app_distribution (0.5.0) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.35.0) + google-apis-androidpublisher_v3 (0.42.0) google-apis-core (>= 0.11.0, < 2.a) google-apis-core (0.11.0) addressable (~> 2.5, >= 2.5.1) @@ -138,7 +138,7 @@ GEM google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.3.0) + googleauth (1.5.2) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) memoist (~> 0.16) @@ -194,7 +194,6 @@ GEM 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) word_wrap (1.0.0) From fe91b0750cb5337162ad9f2ff209ced9667fed3d Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Tue, 18 Jul 2023 17:47:08 +0200 Subject: [PATCH 002/126] refactor: use kotlin flow instead of live data to update the terminal output --- .../org/kabiri/android/usbterminal/MainActivity.kt | 11 +++++++---- .../usbterminal/viewmodel/MainActivityViewModel.kt | 7 +++++-- 2 files changed, 12 insertions(+), 6 deletions(-) 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..c6faf140 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt @@ -12,6 +12,7 @@ import android.widget.EditText import android.widget.TextView import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint import org.kabiri.android.usbterminal.extensions.scrollToLastLine import org.kabiri.android.usbterminal.viewmodel.MainActivityViewModel @@ -51,10 +52,12 @@ class MainActivity : AppCompatActivity() { ) } - viewModel.output.observe(this) { - tvOutput.apply { - text = it - scrollToLastLine() + lifecycleScope.launchWhenResumed { + viewModel.output.collect { + tvOutput.apply { + text = it + scrollToLastLine() + } } } diff --git a/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt b/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt index 575ab12a..64a668e6 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt @@ -3,6 +3,8 @@ package org.kabiri.android.usbterminal.viewmodel import android.hardware.usb.UsbDevice import androidx.lifecycle.* import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import org.kabiri.android.usbterminal.arduino.ArduinoHelper import org.kabiri.android.usbterminal.model.OutputText @@ -17,8 +19,9 @@ class MainActivityViewModel private val arduinoHelper: ArduinoHelper, ): ViewModel() { - private val _outputLive = MutableLiveData("") - val output = _outputLive + private val _outputLive = MutableStateFlow("") + val output: StateFlow + get() = _outputLive fun askForConnectionPermission() = arduinoHelper.askForConnectionPermission() From 910575d9f380991c28f64f0c60abb4d675beb1c7 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Tue, 18 Jul 2023 18:44:44 +0200 Subject: [PATCH 003/126] refactor: replace live data with kotlin flows --- .../android/usbterminal/MainActivity.kt | 18 +-- .../usbterminal/arduino/ArduinoHelper.kt | 103 ++++++++---------- .../ArduinoPermissionBroadcastReceiver.kt | 26 +++-- .../arduino/ArduinoSerialReceiver.kt | 22 ++-- .../viewmodel/MainActivityViewModel.kt | 31 ++++-- 5 files changed, 100 insertions(+), 100 deletions(-) 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 c6faf140..44607fe1 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt @@ -42,14 +42,16 @@ class MainActivity : AppCompatActivity() { viewModel.openDeviceAndPort(device) } - viewModel.getLiveOutput().observe(this) { - val spannable = SpannableString(it.text) - spannable.setSpan( - it.getAppearance(this), - 0, - it.text.length, - SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE - ) + lifecycleScope.launchWhenResumed { + viewModel.getLiveOutput().collect { + val spannable = SpannableString(it.text) + spannable.setSpan( + it.getAppearance(this@MainActivity), + 0, + it.text.length, + SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } } lifecycleScope.launchWhenResumed { diff --git a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt index 27830246..b0649df4 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt @@ -8,11 +8,11 @@ import android.hardware.usb.UsbDevice import android.hardware.usb.UsbDeviceConnection import android.hardware.usb.UsbManager import android.util.Log -import androidx.lifecycle.LiveData -import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.MutableLiveData import com.felhr.usbserial.UsbSerialDevice import com.felhr.usbserial.UsbSerialInterface +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine import org.kabiri.android.usbterminal.Constants import org.kabiri.android.usbterminal.R import javax.inject.Inject @@ -37,9 +37,9 @@ class ArduinoHelper private const val TAG = "ArduinoHelper" } - private val _liveOutput = MutableLiveData() - private val _liveInfoOutput = MutableLiveData() - private val _liveErrorOutput = MutableLiveData() + private val _liveOutput = MutableStateFlow("") + private val _liveInfoOutput = MutableStateFlow("") + private val _liveErrorOutput = MutableStateFlow("") private var usbManager: UsbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager private lateinit var connection: UsbDeviceConnection @@ -50,41 +50,39 @@ class ArduinoHelper */ fun askForConnectionPermission() { val usbDevices = usbManager.deviceList - _liveInfoOutput.postValue(context.getString(R.string.helper_info_checking_attached_usb_devices)) + _liveInfoOutput.value = + context.getString(R.string.helper_info_checking_attached_usb_devices) if (usbDevices.isNotEmpty()) { for (device in usbDevices) { val deviceVID = device.value.vendorId if (deviceVID == 0x2341) { // Arduino vendor ID connect(device) } else { - _liveErrorOutput.postValue(context.getString(R.string.helper_error_device_not_found)) - _liveErrorOutput.postValue(context.getString(R.string.helper_error_connecting_anyway)) + _liveErrorOutput.value = + context.getString(R.string.helper_error_device_not_found) + _liveErrorOutput.value = context.getString(R.string.helper_error_connecting_anyway) connect(device) } } } else { - _liveErrorOutput.postValue(context.getString(R.string.helper_error_usb_devices_not_attached)) + _liveErrorOutput.value = context.getString(R.string.helper_error_usb_devices_not_attached) } } fun disconnect() { try { connection.close() - _liveOutput.postValue(context.getString(R.string.helper_info_serial_connection_closed)) + _liveOutput.value = context.getString(R.string.helper_info_serial_connection_closed) } catch (e: UninitializedPropertyAccessException) { - _liveErrorOutput.postValue( - context.getString( - R.string.helper_error_connection_not_ready_to_close - ) - ) - _liveErrorOutput.postValue("${e.localizedMessage}\n") + _liveErrorOutput.value = + context.getString(R.string.helper_error_connection_not_ready_to_close) + + _liveErrorOutput.value = "${e.localizedMessage}\n" } catch (e: Exception) { - _liveErrorOutput.postValue( - context.getString( + _liveErrorOutput.value = context.getString( R.string.helper_error_connection_failed_to_close ) - ) - _liveErrorOutput.postValue("${e.localizedMessage}\n") + _liveErrorOutput.value = "${e.localizedMessage}\n" } } @@ -99,7 +97,7 @@ class ArduinoHelper val filter = IntentFilter(Constants.ACTION_USB_PERMISSION) context.registerReceiver(arduinoPermReceiver, filter) // register the broadcast receiver usbManager.requestPermission(device.value, permissionIntent) - _liveInfoOutput.postValue(context.getString(R.string.helper_info_usb_permission_requested)) + _liveInfoOutput.value = context.getString(R.string.helper_info_usb_permission_requested) } /** @@ -118,21 +116,21 @@ class ArduinoHelper serialPort = UsbSerialDevice.createUsbSerialDevice(device, connection) } catch (e: IllegalStateException) { Log.e(TAG, "${e.message}") - _liveErrorOutput.postValue(context.getString(R.string.helper_error_connection_closed_unexpectedly)) + _liveErrorOutput.value = context.getString(R.string.helper_error_connection_closed_unexpectedly) } catch (e: NullPointerException) { Log.e(TAG, "${e.message}") - _liveErrorOutput.postValue(context.getString( - R.string.helper_error_connection_failed_to_open)) + _liveErrorOutput.value = context.getString( + R.string.helper_error_connection_failed_to_open) } catch (e: Exception) { Log.e(TAG, "${e.message}") - _liveErrorOutput.postValue(context.getString( - R.string.helper_error_connection_failed_to_open_unknown)) + _liveErrorOutput.value = + context.getString(R.string.helper_error_connection_failed_to_open_unknown) } if (::serialPort.isInitialized) prepareSerialPort(serialPort) else { - _liveInfoOutput.postValue(context.getString(R.string.helper_error_serial_port_is_null)) + _liveInfoOutput.value = context.getString(R.string.helper_error_serial_port_is_null) connection.close() } } @@ -151,9 +149,10 @@ class ArduinoHelper it.setParity(UsbSerialInterface.PARITY_NONE) it.setFlowControl(UsbSerialInterface.FLOW_CONTROL_OFF) it.read(arduinoSerialReceiver) // messages will be received from the receiver. - _liveInfoOutput.postValue(context.getString(R.string.helper_info_serial_connection_opened)) + _liveInfoOutput.value = context.getString(R.string.helper_info_serial_connection_opened) } else { // serial port not opened. - _liveErrorOutput.postValue(context.getString(R.string.helper_error_serial_connection_not_opened)) + _liveErrorOutput.value = + context.getString(R.string.helper_error_serial_connection_not_opened) } } } @@ -166,49 +165,39 @@ class ArduinoHelper if (::serialPort.isInitialized && command.isNotBlank()) { serialPort.write(command.toByteArray()) // go to next line because the answer might be sent in more than one part. - _liveOutput.postValue(context.getString(R.string.next_line)) + _liveOutput.value = context.getString(R.string.next_line) true } else { - _liveErrorOutput.postValue(context.getString(R.string.helper_error_serial_port_is_null)) + _liveErrorOutput.value = context.getString(R.string.helper_error_serial_port_is_null) false } } catch (e: Exception) { - _liveErrorOutput.postValue( - context.getString(R.string.helper_error_write_problem) + - " \n${e.localizedMessage}\n") + _liveErrorOutput.value = context.getString(R.string.helper_error_write_problem) + + " \n${e.localizedMessage}\n" Log.e(TAG, "$e") false } } - fun getLiveOutput(): LiveData { - - val liveDataMerger = MediatorLiveData() - liveDataMerger.addSource(_liveOutput) { liveDataMerger.value = it } - liveDataMerger.addSource(arduinoPermReceiver.liveOutput) { liveDataMerger.value = it } - liveDataMerger.addSource(arduinoSerialReceiver.liveOutput) { liveDataMerger.value = it } - - return liveDataMerger + fun getLiveOutput(): Flow { + + return _liveOutput + .combine(arduinoPermReceiver.liveOutput) { a, b -> a + b } + .combine(arduinoSerialReceiver.liveOutput) { a, b -> a + b } } - fun getLiveInfoOutput(): LiveData { - - val liveDataMerger = MediatorLiveData() - liveDataMerger.addSource(_liveInfoOutput) { liveDataMerger.value = it } - liveDataMerger.addSource(arduinoPermReceiver.liveInfoOutput) { liveDataMerger.value = it } - liveDataMerger.addSource(arduinoSerialReceiver.liveInfoOutput) { liveDataMerger.value = it } + fun getLiveInfoOutput(): Flow { - return liveDataMerger + return _liveInfoOutput + .combine(arduinoPermReceiver.liveInfoOutput) { a, b -> a + b } + .combine(arduinoSerialReceiver.liveInfoOutput) { a, b -> a + b } } - fun getLiveErrorOutput(): LiveData { - - val liveDataMerger = MediatorLiveData() - liveDataMerger.addSource(_liveErrorOutput) { liveDataMerger.value = it } - liveDataMerger.addSource(arduinoPermReceiver.liveErrorOutput) { liveDataMerger.value = it } - liveDataMerger.addSource(arduinoSerialReceiver.liveErrorOutput) { liveDataMerger.value = it } + fun getLiveErrorOutput(): Flow { - return liveDataMerger + return _liveErrorOutput + .combine(arduinoPermReceiver.liveErrorOutput) { a, b -> a + b } + .combine(arduinoSerialReceiver.liveErrorOutput) { a, b -> a + b } } } \ No newline at end of file diff --git a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoPermissionBroadcastReceiver.kt b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoPermissionBroadcastReceiver.kt index dda0f115..d70301ee 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoPermissionBroadcastReceiver.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoPermissionBroadcastReceiver.kt @@ -8,6 +8,8 @@ import android.hardware.usb.UsbManager import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import org.kabiri.android.usbterminal.Constants import org.kabiri.android.usbterminal.R @@ -22,15 +24,15 @@ class ArduinoPermissionBroadcastReceiver: BroadcastReceiver() { private const val TAG = "ArduinoPermReceiver" } - private val _liveOutput = MutableLiveData() - private val _liveInfoOutput = MutableLiveData() - private val _liveErrorOutput = MutableLiveData() + private val _liveOutput = MutableStateFlow("") + private val _liveInfoOutput = MutableStateFlow("") + private val _liveErrorOutput = MutableStateFlow("") - val liveOutput: LiveData + val liveOutput: StateFlow get() = _liveOutput - val liveInfoOutput: LiveData + val liveInfoOutput: StateFlow get() = _liveInfoOutput - val liveErrorOutput: LiveData + val liveErrorOutput: StateFlow get() = _liveErrorOutput private val _liveGrantedDevice = MutableLiveData() @@ -46,20 +48,20 @@ class ArduinoPermissionBroadcastReceiver: BroadcastReceiver() { .getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false) if (permissionGranted) { - _liveInfoOutput.postValue( + _liveInfoOutput.value = "${context?.getString(R.string.breceiver_info_usb_permission_granted)} ${device?.manufacturerName}" - ) Log.i(TAG, "USB permission granted by the user") device?.let { _liveGrantedDevice.postValue(it) } } else { Log.e(TAG, "USB permission was probably denied by the user") - _liveErrorOutput.postValue( + _liveErrorOutput.value = "${context?.getString(R.string.breceiver_error_usb_permission_denied)} ${device?.manufacturerName}" - ) } } - UsbManager.ACTION_USB_DEVICE_ATTACHED -> _liveOutput.postValue(context?.getString(R.string.breceiver_info_device_attached)) - UsbManager.ACTION_USB_DEVICE_DETACHED -> _liveOutput.postValue(context?.getString(R.string.breceiver_info_device_detached)) + UsbManager.ACTION_USB_DEVICE_ATTACHED -> _liveOutput.value = + context?.getString(R.string.breceiver_info_device_attached) ?: "" + UsbManager.ACTION_USB_DEVICE_DETACHED -> _liveOutput.value = + context?.getString(R.string.breceiver_info_device_detached) ?: "" } } } \ No newline at end of file diff --git a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoSerialReceiver.kt b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoSerialReceiver.kt index b990c443..cea3438b 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoSerialReceiver.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoSerialReceiver.kt @@ -1,9 +1,9 @@ package org.kabiri.android.usbterminal.arduino import android.util.Log -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import com.felhr.usbserial.UsbSerialInterface +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import java.io.UnsupportedEncodingException import java.nio.charset.Charset @@ -18,15 +18,15 @@ class ArduinoSerialReceiver: UsbSerialInterface.UsbReadCallback { private const val TAG = "ArduinoSerialReceiver" } - private val _liveOutput = MutableLiveData() - private val _liveInfoOutput = MutableLiveData() - private val _liveErrorOutput = MutableLiveData() + private val _liveOutput = MutableStateFlow("") + private val _liveInfoOutput = MutableStateFlow("") + private val _liveErrorOutput = MutableStateFlow("") - val liveOutput: LiveData + val liveOutput: StateFlow get() = _liveOutput - val liveInfoOutput: LiveData + val liveInfoOutput: StateFlow get() = _liveInfoOutput - val liveErrorOutput: LiveData + val liveErrorOutput: StateFlow get() = _liveErrorOutput override fun onReceivedData(message: ByteArray?) { @@ -34,14 +34,14 @@ class ArduinoSerialReceiver: UsbSerialInterface.UsbReadCallback { try { // reading the message from the arduino board. val encoded = String(message, Charset.defaultCharset()) Log.i(TAG, "message from arduino: $encoded") - _liveOutput.postValue(encoded) + _liveOutput.value = encoded } catch (e: UnsupportedEncodingException) { e.printStackTrace() Log.e(TAG, "Encoding problem occurred when reading the serial message: $e") - _liveErrorOutput.postValue("\n${e.localizedMessage}") + _liveErrorOutput.value = "\n${e.localizedMessage}" } catch (e: Exception) { Log.e(TAG, "Unknown error occurred when reading the serial message: $e") - _liveErrorOutput.postValue("\n${e.localizedMessage}") + _liveErrorOutput.value = "\n${e.localizedMessage}" } } ?: run { Log.e(TAG, "Message was null") diff --git a/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt b/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt index 64a668e6..222f3a48 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt @@ -3,8 +3,12 @@ package org.kabiri.android.usbterminal.viewmodel import android.hardware.usb.UsbDevice import androidx.lifecycle.* import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import org.kabiri.android.usbterminal.arduino.ArduinoHelper import org.kabiri.android.usbterminal.model.OutputText @@ -39,32 +43,35 @@ class MainActivityViewModel * Transforms the outputs from ArduinoHelper into spannable text * and merges them in one single live data. */ - fun getLiveOutput(): LiveData { + suspend fun getLiveOutput(): StateFlow { - val liveOutput = arduinoHelper.getLiveOutput() - val liveInfoOutput = arduinoHelper.getLiveInfoOutput() - val liveErrorOutput = arduinoHelper.getLiveErrorOutput() + val serialOutput = arduinoHelper.getLiveOutput() + val serialInfoOutput = arduinoHelper.getLiveInfoOutput() + val serialErrorOutput = arduinoHelper.getLiveErrorOutput() - val liveSpannedOutput: LiveData = Transformations.map(liveOutput) { + val liveSpannedOutput: Flow = serialOutput.map { _outputLive.value = _outputLive.value + it return@map OutputText(it, OutputText.OutputType.TYPE_NORMAL) } - val liveSpannedInfoOutput: LiveData = Transformations.map(liveInfoOutput) { + val liveSpannedInfoOutput: Flow = serialInfoOutput.map { _outputLive.value = _outputLive.value + it return@map OutputText(it, OutputText.OutputType.TYPE_INFO) } - val liveSpannedErrorOutput: LiveData = Transformations.map(liveErrorOutput) { + val liveSpannedErrorOutput: Flow = serialErrorOutput.map { _outputLive.value = _outputLive.value + it return@map OutputText(it, OutputText.OutputType.TYPE_ERROR) } - val liveDataMerger = MediatorLiveData() - liveDataMerger.addSource(liveSpannedOutput) { liveDataMerger.value = it } - liveDataMerger.addSource(liveSpannedInfoOutput) { liveDataMerger.value = it } - liveDataMerger.addSource(liveSpannedErrorOutput) { liveDataMerger.value = it } +// val liveDataMerger = MediatorLiveData() +// liveDataMerger.addSource(liveSpannedOutput) { liveDataMerger.value = it } +// liveDataMerger.addSource(liveSpannedInfoOutput) { liveDataMerger.value = it } +// liveDataMerger.addSource(liveSpannedErrorOutput) { liveDataMerger.value = it } - return liveDataMerger + return liveSpannedOutput + .combine(liveSpannedInfoOutput) { a, b -> b } + .combine(liveSpannedErrorOutput) { a, b -> b } + .stateIn(viewModelScope) } } \ No newline at end of file From 597c5af23f3441e484793bb90cc6225747ab902f Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Tue, 18 Jul 2023 22:03:12 +0200 Subject: [PATCH 004/126] refactor: format code - no changes --- .../android/usbterminal/arduino/ArduinoHelper.kt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt index b0649df4..cc2c4792 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt @@ -60,12 +60,14 @@ class ArduinoHelper } else { _liveErrorOutput.value = context.getString(R.string.helper_error_device_not_found) - _liveErrorOutput.value = context.getString(R.string.helper_error_connecting_anyway) + _liveErrorOutput.value = + context.getString(R.string.helper_error_connecting_anyway) connect(device) } } } else { - _liveErrorOutput.value = context.getString(R.string.helper_error_usb_devices_not_attached) + _liveErrorOutput.value = + context.getString(R.string.helper_error_usb_devices_not_attached) } } @@ -116,7 +118,8 @@ class ArduinoHelper serialPort = UsbSerialDevice.createUsbSerialDevice(device, connection) } catch (e: IllegalStateException) { Log.e(TAG, "${e.message}") - _liveErrorOutput.value = context.getString(R.string.helper_error_connection_closed_unexpectedly) + _liveErrorOutput.value = + context.getString(R.string.helper_error_connection_closed_unexpectedly) } catch (e: NullPointerException) { Log.e(TAG, "${e.message}") _liveErrorOutput.value = context.getString( @@ -149,7 +152,8 @@ class ArduinoHelper it.setParity(UsbSerialInterface.PARITY_NONE) it.setFlowControl(UsbSerialInterface.FLOW_CONTROL_OFF) it.read(arduinoSerialReceiver) // messages will be received from the receiver. - _liveInfoOutput.value = context.getString(R.string.helper_info_serial_connection_opened) + _liveInfoOutput.value = + context.getString(R.string.helper_info_serial_connection_opened) } else { // serial port not opened. _liveErrorOutput.value = context.getString(R.string.helper_error_serial_connection_not_opened) @@ -168,7 +172,8 @@ class ArduinoHelper _liveOutput.value = context.getString(R.string.next_line) true } else { - _liveErrorOutput.value = context.getString(R.string.helper_error_serial_port_is_null) + _liveErrorOutput.value = + context.getString(R.string.helper_error_serial_port_is_null) false } } catch (e: Exception) { From ca1ef487a0dac520893540e27488b60e0e56de05 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Tue, 18 Jul 2023 23:02:51 +0200 Subject: [PATCH 005/126] refactor: no changes --- .../usbterminal/arduino/ArduinoHelper.kt | 36 ++++++++----------- .../viewmodel/MainActivityViewModel.kt | 6 ++-- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt index cc2c4792..d7dba16c 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt @@ -38,8 +38,22 @@ class ArduinoHelper } private val _liveOutput = MutableStateFlow("") + val output: Flow + get() = _liveOutput + .combine(arduinoPermReceiver.liveOutput) { a, b -> a + b } + .combine(arduinoSerialReceiver.liveOutput) { a, b -> a + b } + private val _liveInfoOutput = MutableStateFlow("") + val infoOutput: Flow + get() = _liveInfoOutput + .combine(arduinoPermReceiver.liveInfoOutput) { a, b -> a + b } + .combine(arduinoSerialReceiver.liveInfoOutput) { a, b -> a + b } + private val _liveErrorOutput = MutableStateFlow("") + val errorOutput: Flow + get() = _liveErrorOutput + .combine(arduinoPermReceiver.liveErrorOutput) { a, b -> a + b } + .combine(arduinoSerialReceiver.liveErrorOutput) { a, b -> a + b } private var usbManager: UsbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager private lateinit var connection: UsbDeviceConnection @@ -183,26 +197,4 @@ class ArduinoHelper false } } - - fun getLiveOutput(): Flow { - - return _liveOutput - .combine(arduinoPermReceiver.liveOutput) { a, b -> a + b } - .combine(arduinoSerialReceiver.liveOutput) { a, b -> a + b } - } - - fun getLiveInfoOutput(): Flow { - - return _liveInfoOutput - .combine(arduinoPermReceiver.liveInfoOutput) { a, b -> a + b } - .combine(arduinoSerialReceiver.liveInfoOutput) { a, b -> a + b } - } - - fun getLiveErrorOutput(): Flow { - - return _liveErrorOutput - .combine(arduinoPermReceiver.liveErrorOutput) { a, b -> a + b } - .combine(arduinoSerialReceiver.liveErrorOutput) { a, b -> a + b } - } - } \ No newline at end of file diff --git a/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt b/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt index 222f3a48..f4ede830 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt @@ -45,9 +45,9 @@ class MainActivityViewModel */ suspend fun getLiveOutput(): StateFlow { - val serialOutput = arduinoHelper.getLiveOutput() - val serialInfoOutput = arduinoHelper.getLiveInfoOutput() - val serialErrorOutput = arduinoHelper.getLiveErrorOutput() + val serialOutput = arduinoHelper.output + val serialInfoOutput = arduinoHelper.infoOutput + val serialErrorOutput = arduinoHelper.errorOutput val liveSpannedOutput: Flow = serialOutput.map { _outputLive.value = _outputLive.value + it From 81c5a26611564ab59dd899f68e8a57ed59644db0 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Tue, 18 Jul 2023 23:13:23 +0200 Subject: [PATCH 006/126] fix: clicking connect button multiple times outputs error regardless of the last message --- .../java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt index d7dba16c..2e492e78 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt @@ -80,6 +80,7 @@ class ArduinoHelper } } } else { + _liveErrorOutput.value = "" _liveErrorOutput.value = context.getString(R.string.helper_error_usb_devices_not_attached) } From 550ba01703ec527d6ae1cd1168a0d21632fc6620 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Wed, 19 Jul 2023 05:12:12 +0200 Subject: [PATCH 007/126] chore: update dependencies and add jetpack compse --- app/build.gradle.kts | 29 ++++++++++++++++++++++++----- build.gradle | 6 +++--- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 253fb968..b271179a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -22,6 +22,14 @@ android { targetCompatibility = JavaVersion.VERSION_1_8 } + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = "1.4.4" + } + defaultConfig { applicationId = "org.kabiri.android.usbterminal" minSdk = 23 @@ -168,8 +176,8 @@ val firebase_bom_version: String by project val hilt_version: String by project dependencies { - implementation("androidx.appcompat:appcompat:1.6.0") - implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("androidx.core:core-ktx:1.10.1") implementation("androidx.constraintlayout:constraintlayout:2.1.4") // Firebase @@ -182,13 +190,24 @@ 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.6.1") + implementation("androidx.activity:activity-compose:1.7.2") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") 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") + // 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.7.1") + // hilt testing // more info: // https://developer.android.com/training/dependency-injection/hilt-testing diff --git a/build.gradle b/build.gradle index 09ce2b2e..1bf710b7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - kotlin_version = '1.7.10' + kotlin_version = '1.8.10' hilt_version = '2.44' firebase_bom_version = '30.5.0' jacoco_version = '0.8.8' @@ -8,13 +8,13 @@ buildscript { dependencies { classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath "com.google.gms:google-services:4.3.15" - classpath "com.google.firebase:firebase-crashlytics-gradle:2.9.4" + classpath "com.google.firebase:firebase-crashlytics-gradle:2.9.7" } } plugins { id("com.android.application") version "7.4.2" apply false - id("org.jetbrains.kotlin.android") version "1.7.0" apply false + id("org.jetbrains.kotlin.android") version "1.8.10" apply false id("org.sonarqube") version "3.5.0.2730" } From 52b6e1ace73b6debb975770793f16d1244febd3b Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Wed, 19 Jul 2023 05:14:11 +0200 Subject: [PATCH 008/126] feature: add a compose view to load a snapshot list of OutputText --- .../android/usbterminal/MainActivity.kt | 21 +++++++++++++++++++ .../viewmodel/MainActivityViewModel.kt | 20 +++++++++++------- app/src/main/res/layout/activity_main.xml | 11 +++++++++- app/src/main/res/menu/activity_main_menu.xml | 1 + app/src/main/res/values/strings.xml | 1 + 5 files changed, 45 insertions(+), 9 deletions(-) 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 44607fe1..2989a160 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt @@ -12,6 +12,12 @@ import android.widget.EditText import android.widget.TextView import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.BasicText +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint import org.kabiri.android.usbterminal.extensions.scrollToLastLine @@ -33,6 +39,21 @@ class MainActivity : AppCompatActivity() { val etInput = findViewById(R.id.etInput) val tvOutput = findViewById(R.id.tvOutput) val btEnter = findViewById