diff --git a/app/build.gradle b/app/build.gradle index 8e092bca..49bb16de 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -43,7 +43,7 @@ android { } testOptions { animationsDisabled true - unitTests.includeAndroidResources = true + unitTests.includeAndroidResources true } compileOptions { sourceCompatibility JavaVersion.VERSION_11 @@ -56,7 +56,7 @@ android { dependencies { implementation project(":base") - implementation "androidx.core:core-splashscreen:1.0.0-beta01" + implementation "androidx.core:core-splashscreen:1.0.0-beta02" implementation "androidx.constraintlayout:constraintlayout:2.1.3" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 71c49a71..36643fee 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,11 +5,10 @@ - - + - Log.e("BT Error", "${error.message}") + rotationConnectionJob = reporterScope.launch { + try { + manager.adapter.getRemoteDevice(deviceId)?.let { device -> + device.createInsecureRfcommSocketToServiceRecord(sppid)?.let { socket -> + connecting = true + socket.connect() + outputStream = socket.outputStream + connected = true + connecting = false + Log.i(tag, "$tag: Connected!") + } + } + } catch (e: SecurityException) { + Log.e(tag, "$tag: ${e.message}") + } catch (e: Exception) { + Log.e(tag, "$tag: ${e.message}") } } } - } - - fun isBTConnected():Boolean - { - return connected + @Suppress("BlockingMethodInNonBlockingContext") + fun reportRotation(format: String, azimuth: Int, elevation: Int) { + if (connected) { + rotationReportingJob = reporterScope.launch { + try { + val azimuthString = intToStringWithLeadingZeroes(azimuth) + val elevationString = intToStringWithLeadingZeroes(elevation) + val crChar = '\r' + val nlChar = '\n' + val tbChar = '\t' + var buffer = format.replace("\$AZ", azimuthString) + buffer = buffer.replace("\$EL", elevationString) + buffer = buffer.replace("\\r", crChar.toString()) + buffer = buffer.replace("\\n", nlChar.toString()) + buffer = buffer.replace("\\t", tbChar.toString()) + Log.i(tag, "$tag: Sending $buffer") + if (connected) outputStream.write(buffer.toByteArray()) + } catch (e: Exception) { + Log.e(tag, "$tag: ${e.message}") + connected = false + } + } + } } - fun connectInProg():Boolean - { - return connectInProgress - } - - - fun reportRotationBT(dev: String, fmt: String, AZ: Int, EL: Int) { - - runCatching { - var azStr:String - var elStr:String - satVisible=(EL>1) - - //Need to add leading zeros to string to ensure always 3 digits. - //Ideally this could be done via the format string but this will do for now. - if(AZ<100){ - if(AZ<10){ - azStr="00" - } - else - { - azStr="0" - } - azStr=azStr.plus(AZ.toString()) - } - else - { - azStr=AZ.toString() - } - - if(EL<100){ - if(EL<10){ - elStr="00" - } - else - { - elStr="0" - } - if(satVisible) { - elStr = elStr.plus(EL.toString()) - } - else { - elStr="000" - } - - } - else - { - elStr=EL.toString() - } - - var buffer = fmt.replace("\$AZ",azStr) - buffer = buffer.replace("\$EL",elStr) - buffer = buffer.replace("\\r",CRchar.toString()) - buffer = buffer.replace("\\n",NLchar.toString()) - buffer = buffer.replace("\\t",TBchar.toString()) - Log.i("Output is", buffer) - if(connected) { - Log.i("Sending BT", buffer) - this.mmOutStream.write(buffer.toByteArray()) - Log.i("Sent", buffer) - } - }.onFailure { error: Throwable -> - Log.e("BT Error","${error.message}") - connected=false + private fun intToStringWithLeadingZeroes(value: Int): String { + return if (value > 0) { + if (value < 10) "00$value" else if (value < 100) "0$value" else "$value" + } else { + val absValue = abs(value) + if (value > -10) "-00$absValue" else if (value > -100) "-0$absValue" else "-$absValue" } } } - diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarViewModel.kt index 501d0d4e..df3ef8cf 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarViewModel.kt @@ -42,8 +42,8 @@ import javax.inject.Inject class RadarViewModel @Inject constructor( private val orientationManager: OrientationManager, private val reporter: DataReporter, - private val BTreporter: BTReporter, - private val satelliteManager: ISatelliteManager, + private val btReporter: BTReporter, + private val satManager: ISatelliteManager, private val repository: IDataRepository, private val settings: ISettingsManager ) : ViewModel(), OrientationManager.OrientationListener { @@ -57,7 +57,7 @@ class RadarViewModel @Inject constructor( val orientation: LiveData> = _orientation fun getPass(catNum: Int, aosTime: Long) = liveData { - satelliteManager.calculatedPasses.collect { passes -> + satManager.calculatedPasses.collect { passes -> val pass = passes.find { pass -> pass.catNum == catNum && pass.aosTime == aosTime } pass?.let { satPass -> emit(satPass) @@ -94,12 +94,11 @@ class RadarViewModel @Inject constructor( viewModelScope.launch { var satTrack: List = emptyList() if (!satPass.isDeepSpace) { - val startDate = satPass.aosTime - val endDate = satPass.losTime - satTrack = satelliteManager.getTrack(satPass.satellite, stationPos, startDate, endDate) + satTrack = satManager + .getTrack(satPass.satellite, stationPos, satPass.aosTime, satPass.losTime) } while (isActive) { - val satPos = satelliteManager.getPosition(satPass.satellite, stationPos, Date().time) + val satPos = satManager.getPosition(satPass.satellite, stationPos, Date().time) if (settings.getRotatorEnabled()) { val server = settings.getRotatorServer() val port = settings.getRotatorPort().toInt() @@ -116,21 +115,20 @@ class RadarViewModel @Inject constructor( private fun sendPassDataBT(satPass: SatPass) { viewModelScope.launch { while (isActive) { - val satPos = satelliteManager.getPosition(satPass.satellite, stationPos, Date().time) + val satPos = satManager.getPosition(satPass.satellite, stationPos, Date().time) if (settings.getBTEnabled()) { - val server = settings.getBTDeviceAddr() - if(BTreporter.isBTConnected()) { - val port = settings.getBTFormat() + val btDevice = settings.getBTDeviceAddr() + if (btReporter.isConnected()) { + val format = settings.getBTFormat() val azimuth = satPos.azimuth.toDegrees().round(0).toInt() val elevation = satPos.elevation.toDegrees().round(0).toInt() - BTreporter.reportRotationBT(server, port, azimuth, elevation) - } - else if(!BTreporter.connectInProg()) { - Log.i("look4satBT", "Attempting to connect...") - BTreporter.connectBTDevice(server) + btReporter.reportRotation(format, azimuth, elevation) + } else if (!btReporter.isConnecting()) { + Log.i("BTReporter", "BTReporter: Attempting to connect...") + btReporter.connectBTDevice(btDevice) } } - delay(2000) + delay(1000) } } } @@ -141,7 +139,7 @@ class RadarViewModel @Inject constructor( val transmitters = repository.getRadiosWithId(pass.catNum) while (isActive) { val time = System.currentTimeMillis() - val list = satelliteManager.processRadios(pass.satellite, stationPos, transmitters, time) + val list = satManager.processRadios(pass.satellite, stationPos, transmitters, time) _transmitters.postValue(list) delay(1000) } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsFragment.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsFragment.kt index 350e7ada..858c8890 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsFragment.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsFragment.kt @@ -17,15 +17,13 @@ */ package com.rtbishop.look4sat.presentation.settingsScreen -//import com.rtbishop.look4sat.BuildConfig import android.Manifest -import android.bluetooth.BluetoothAdapter import android.content.Intent import android.net.Uri +import android.os.Build import android.os.Bundle import android.text.method.LinkMovementMethod import android.view.View -import android.widget.ArrayAdapter import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.core.widget.NestedScrollView @@ -34,6 +32,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.asLiveData import androidx.navigation.fragment.findNavController +import com.rtbishop.look4sat.BuildConfig import com.rtbishop.look4sat.R import com.rtbishop.look4sat.databinding.FragmentSettingsBinding import com.rtbishop.look4sat.domain.model.DataState @@ -44,11 +43,21 @@ import com.rtbishop.look4sat.utility.isValidIPv4 import com.rtbishop.look4sat.utility.isValidPort import dagger.hilt.android.AndroidEntryPoint - @AndroidEntryPoint class SettingsFragment : Fragment(R.layout.fragment_settings) { private val viewModel: SettingsViewModel by viewModels() + private val bluetooth = when { + Build.VERSION.SDK_INT < Build.VERSION_CODES.S -> Manifest.permission.BLUETOOTH + else -> Manifest.permission.BLUETOOTH_CONNECT + } + private val bluetoothContract = ActivityResultContracts.RequestPermission() + private val bluetoothRequest = registerForActivityResult(bluetoothContract) { isGranted -> + if (!isGranted) { + showToast(getString(R.string.BTremote_perm_error)) + toggleBTstate(isGranted) + } + } private val locationFine = Manifest.permission.ACCESS_FINE_LOCATION private val locationCoarse = Manifest.permission.ACCESS_COARSE_LOCATION private val locationContract = ActivityResultContracts.RequestMultiplePermissions() @@ -73,7 +82,7 @@ class SettingsFragment : Fragment(R.layout.fragment_settings) { if (y > newY) settingsFab.hide() else settingsFab.show() }) settingsAbout.aboutVersion.text = - String.format(getString(R.string.app_version), 0) + String.format(getString(R.string.app_version), BuildConfig.VERSION_NAME) settingsBtnGithub.clickWithDebounce { gotoUrl("https://github.com/rt-bishop/Look4Sat/") } @@ -84,8 +93,6 @@ class SettingsFragment : Fragment(R.layout.fragment_settings) { gotoUrl("https://f-droid.org/en/packages/com.rtbishop.look4sat/") } } - - setupLocationCard() setupDataCard() setupRemoteCard() @@ -154,7 +161,6 @@ class SettingsFragment : Fragment(R.layout.fragment_settings) { settingsRemote.remotePort.isEnabled = isChecked settingsRemote.remotePortEdit.setText(viewModel.getRotatorPort()) setOnCheckedChangeListener { _, isChecked -> - viewModel.setRotatorEnabled(isChecked) settingsRemote.remoteIp.isEnabled = isChecked settingsRemote.remotePort.isEnabled = isChecked } @@ -169,31 +175,36 @@ class SettingsFragment : Fragment(R.layout.fragment_settings) { } private fun setupBTCard() { - binding.run { settingsBtremote.BTremoteSwitch.apply { isChecked = viewModel.getBTEnabled() settingsBtremote.BTremoteAddress.isEnabled = isChecked - settingsBtremote.BTremoteFormat.isEnabled = isChecked settingsBtremote.BTAddressEdit.setText(viewModel.getBTDeviceAddr()) + settingsBtremote.BTremoteFormat.isEnabled = isChecked settingsBtremote.BTFormatEdit.setText(viewModel.getBTFormat()) setOnCheckedChangeListener { _, isChecked -> - viewModel.setBTEnabled(isChecked) - settingsBtremote.BTremoteAddress.isEnabled = isChecked - settingsBtremote.BTremoteFormat.isEnabled = isChecked + toggleBTstate(isChecked) + bluetoothRequest.launch(bluetooth) } } - settingsBtremote.BTAddressEdit.doOnTextChanged { text, _, _, _ -> viewModel.setBTDeviceAddr(text.toString()) } - settingsBtremote.BTFormatEdit.doOnTextChanged { text, _, _, _ -> viewModel.setBTFormat(text.toString()) } } } + private fun toggleBTstate(value: Boolean) { + binding.run { + viewModel.setBTEnabled(value) + settingsBtremote.BTremoteSwitch.isChecked = value + settingsBtremote.BTremoteAddress.isEnabled = value + settingsBtremote.BTremoteFormat.isEnabled = value + } + } + private fun setupOtherCard() { binding.run { settingsOther.otherSwitchUtc.apply { diff --git a/app/src/main/res/layout/card_btremote.xml b/app/src/main/res/layout/card_btremote.xml index ca72fc19..02d646bb 100644 --- a/app/src/main/res/layout/card_btremote.xml +++ b/app/src/main/res/layout/card_btremote.xml @@ -1,7 +1,6 @@ + app:layout_constraintEnd_toEndOf="@+id/BTremote_switch" + app:layout_constraintStart_toStartOf="@+id/BTremote_switch" + app:layout_constraintTop_toBottomOf="@id/BTremote_switch"> - + + android:layout_marginTop="6dp" + android:layout_marginBottom="12dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="@+id/BTremote_address" + app:layout_constraintStart_toStartOf="@+id/BTremote_address" + app:layout_constraintTop_toBottomOf="@id/BTremote_address"> @@ -67,6 +69,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/remote_port_hint" + android:inputType="number" android:textColorHint="@color/textMain" /> diff --git a/app/src/main/res/layout/dialog_position.xml b/app/src/main/res/layout/dialog_position.xml index e64bbbdb..50cbe535 100644 --- a/app/src/main/res/layout/dialog_position.xml +++ b/app/src/main/res/layout/dialog_position.xml @@ -57,7 +57,8 @@ android:id="@+id/position_lat_edit" android:layout_width="match_parent" android:layout_height="match_parent" - android:inputType="textUri" /> + android:digits="0123456789." + android:inputType="number" /> @@ -94,7 +95,8 @@ android:id="@+id/position_lon_edit" android:layout_width="match_parent" android:layout_height="match_parent" - android:inputType="textUri" /> + android:digits="0123456789." + android:inputType="number" /> diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 0ba55032..4643906b 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -138,6 +138,12 @@ IP адрес Порт + Передача данных по Bluetooth + Включить передачу + Id устройства + Формат данных + Нет разрешения использовать bluetooth + Другие настройки Показывать время по UTC Показывать анимацию радара diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d20ef091..14851d68 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -139,10 +139,11 @@ IP address Port - Bluetooth Output - Enable Bluetooth - Device - Output format + Bluetooth data output + Enable output + Device id + Data format + Check your bluetooth permission Other preferences Show pass time in UTC diff --git a/base/build.gradle b/base/build.gradle index db00cc2c..dc1c7649 100644 --- a/base/build.gradle +++ b/base/build.gradle @@ -13,9 +13,9 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0" - implementation "org.json:json:20211205" + implementation "org.json:json:20220320" testImplementation "junit:junit:4.13.2" - testImplementation "org.mockito:mockito-core:4.3.1" + testImplementation "org.mockito:mockito-core:4.4.0" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0" } diff --git a/base/src/main/java/com/rtbishop/look4sat/utility/Extensions.kt b/base/src/main/java/com/rtbishop/look4sat/utility/Extensions.kt index 75f3928c..ff107572 100644 --- a/base/src/main/java/com/rtbishop/look4sat/utility/Extensions.kt +++ b/base/src/main/java/com/rtbishop/look4sat/utility/Extensions.kt @@ -67,7 +67,8 @@ fun String.isValidIPv4(): Boolean { } fun String.isValidPort(): Boolean { - return this.isNotEmpty() && this.toInt() in 1024..65535 + val port = "([1-9]|[1-9]\\d{1,3}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])" + return this.matches(port.toRegex()) && this.toInt() in 1024..65535 } fun ping(hostname: String, port: Int): Int { diff --git a/build.gradle b/build.gradle index 5e9a22d8..ac4163ee 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { dependencies { - classpath "com.google.dagger:hilt-android-gradle-plugin:2.40.5" + classpath "com.google.dagger:hilt-android-gradle-plugin:2.41" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.4.1" } }