diff --git a/app/src/main/java/com/rtbishop/look4sat/framework/PreferencesSource.kt b/app/src/main/java/com/rtbishop/look4sat/framework/PreferencesSource.kt index d08c44a2..0df044bd 100644 --- a/app/src/main/java/com/rtbishop/look4sat/framework/PreferencesSource.kt +++ b/app/src/main/java/com/rtbishop/look4sat/framework/PreferencesSource.kt @@ -22,8 +22,8 @@ import android.hardware.GeomagneticField import android.location.LocationManager import androidx.core.content.edit import com.rtbishop.look4sat.BuildConfig -import com.rtbishop.look4sat.domain.predict.GeoPos import com.rtbishop.look4sat.domain.QthConverter +import com.rtbishop.look4sat.domain.predict.GeoPos import com.rtbishop.look4sat.presentation.round import javax.inject.Inject import javax.inject.Singleton @@ -109,19 +109,31 @@ class PreferencesSource @Inject constructor( return preferences.getBoolean(keyTextLabels, false) } - fun shouldUseUTC(): Boolean { + fun getUseUTC(): Boolean { return preferences.getBoolean(keyTimeUTC, false) } - fun shouldUseCompass(): Boolean { + fun setUseUTC(value: Boolean) { + preferences.edit { putBoolean(keyTimeUTC, value) } + } + + fun getUseCompass(): Boolean { return preferences.getBoolean(keyCompass, true) } - fun shouldShowSweep(): Boolean { + fun setUseCompass(value: Boolean) { + preferences.edit { putBoolean(keyCompass, value) } + } + + fun getShowSweep(): Boolean { return preferences.getBoolean(keyRadarSweep, true) } - fun isSetupDone(): Boolean { + fun setShowSweep(value: Boolean) { + preferences.edit { putBoolean(keyRadarSweep, value) } + } + + fun getSetupDone(): Boolean { return preferences.getBoolean(keyInitialSetup, false) } @@ -143,13 +155,33 @@ class PreferencesSource @Inject constructor( return emptyList() } - fun isRotatorEnabled(): Boolean { + fun getRotatorEnabled(): Boolean { return preferences.getBoolean(keyRotator, false) } + fun setRotatorEnabled(value: Boolean) { + preferences.edit { putBoolean(keyRotator, value) } + } + fun getRotatorServer(): Pair { val address = preferences.getString(keyRotatorAddress, null) ?: "127.0.0.1" val port = preferences.getString(keyRotatorPort, null) ?: "4533" return Pair(address, port.toInt()) } + + fun getRotatorIp(): String { + return preferences.getString(keyRotatorAddress, null) ?: "127.0.0.1" + } + + fun setRotatorIp(value: String) { + preferences.edit { putString(keyRotatorAddress, value) } + } + + fun getRotatorPort(): String { + return preferences.getString(keyRotatorPort, null) ?: "4533" + } + + fun setRotatorPort(value: String) { + preferences.edit { putString(keyRotatorPort, value) } + } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/Extensions.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/Extensions.kt index 8a9d2fbb..06b2c170 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/Extensions.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/Extensions.kt @@ -19,9 +19,13 @@ package com.rtbishop.look4sat.presentation import android.content.Context import android.content.SharedPreferences +import android.content.res.Resources import android.os.Bundle +import android.widget.EditText +import android.widget.FrameLayout import android.widget.Toast import androidx.annotation.IdRes +import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver @@ -101,8 +105,38 @@ fun String.getHash(type: String = "SHA-256"): String { return result.toString() } -fun String.isEmailValid(): Boolean { +fun String.isValidEmail(): Boolean { val expression = "^[\\w.-]+@([\\w\\-]+\\.)+[A-Z]{2,8}$" val pattern = Pattern.compile(expression, Pattern.CASE_INSENSITIVE) return pattern.matcher(this).matches() } + +fun String.isValidIPv4(): Boolean { + val ip4 = "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\\.(?!\$)|\$)){4}\$" + return this.matches(ip4.toRegex()) +} + +fun String.isValidPort(): Boolean { + return this.isNotEmpty() && this.toInt() in 1024..65535 +} + +fun Float.toPx(): Int = (this * Resources.getSystem().displayMetrics.density).toInt() + +fun AlertDialog.Builder.setEditText(editText: EditText): AlertDialog.Builder { + val container = FrameLayout(context) + container.addView(editText) + val containerParams = FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.WRAP_CONTENT + ) + val marginHorizontal = 32F + val marginTop = 24F + containerParams.topMargin = (marginTop / 2).toPx() + containerParams.leftMargin = marginHorizontal.toInt() + containerParams.rightMargin = marginHorizontal.toInt() + container.layoutParams = containerParams + val superContainer = FrameLayout(context) + superContainer.addView(container) + setView(superContainer) + return this +} diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesViewModel.kt index 23990253..a369d9e2 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesViewModel.kt @@ -28,7 +28,6 @@ import com.rtbishop.look4sat.domain.DataRepository import com.rtbishop.look4sat.framework.PreferencesSource import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.* -import kotlinx.coroutines.flow.collect import java.util.* import javax.inject.Inject @@ -46,7 +45,7 @@ class PassesViewModel @Inject constructor( val isFirstLaunchDone: LiveData = _isFirstLaunchDone init { - if (preferences.isSetupDone()) { + if (preferences.getSetupDone()) { viewModelScope.launch { _passes.postValue(DataState.Loading) val timeNow = System.currentTimeMillis() @@ -97,7 +96,7 @@ class PassesViewModel @Inject constructor( } fun shouldUseUTC(): Boolean { - return preferences.shouldUseUTC() + return preferences.getUseUTC() } private suspend fun tickPasses(passes: List) = withContext(Dispatchers.Default) { diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/prefsScreen/PrefsFragment.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/prefsScreen/PrefsFragment.kt index 6e568d64..dd380ae6 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/prefsScreen/PrefsFragment.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/prefsScreen/PrefsFragment.kt @@ -24,8 +24,11 @@ import android.net.Uri import android.os.Bundle import android.text.method.LinkMovementMethod import android.view.View +import android.widget.EditText import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat +import androidx.core.widget.doOnTextChanged import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController @@ -34,8 +37,7 @@ import com.rtbishop.look4sat.BuildConfig import com.rtbishop.look4sat.R import com.rtbishop.look4sat.databinding.FragmentPrefsBinding import com.rtbishop.look4sat.framework.PreferencesSource -import com.rtbishop.look4sat.presentation.getNavResult -import com.rtbishop.look4sat.presentation.navigateSafe +import com.rtbishop.look4sat.presentation.* import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -44,6 +46,7 @@ class PrefsFragment : Fragment(R.layout.fragment_prefs) { @Inject lateinit var preferences: PreferencesSource + private val viewModel: PrefsViewModel by viewModels() private val locPermFine = Manifest.permission.ACCESS_FINE_LOCATION private val locPermCoarse = Manifest.permission.ACCESS_COARSE_LOCATION @@ -64,13 +67,15 @@ class PrefsFragment : Fragment(R.layout.fragment_prefs) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) _binding = FragmentPrefsBinding.bind(view) - setupInfoCard() + setupAboutCard() setupDataCard() setupLocationCard() setupWarrantyCard() + setupOtherCard() + setupTrackingCard() } - private fun setupInfoCard() { + private fun setupAboutCard() { _binding?.prefsInfo?.let { binding -> binding.aboutVersion.text = String.format(getString(R.string.about_version), BuildConfig.VERSION_NAME) @@ -92,21 +97,77 @@ class PrefsFragment : Fragment(R.layout.fragment_prefs) { findNavController().navigateSafe(R.id.action_prefs_to_sources) } binding.updateBtnFile.setOnClickListener { filePicker.launch("*/*") } - } - getNavResult>(R.id.nav_passes, "sources") { sources -> - viewModel.updateDataFromWeb(sources) + getNavResult>(R.id.nav_prefs, "sources") { sources -> + viewModel.updateDataFromWeb(sources) + } } } private fun setupLocationCard() { + _binding?.prefsLocation?.let { binding -> + binding.locationBtnGps.setOnClickListener { + updatePositionFromGPS() + } + binding.locationBtnQth.setOnClickListener { + val editText = EditText(requireActivity()) + AlertDialog.Builder(requireContext()) + .setTitle("Title") + .setEditText(editText) + .setPositiveButton("OK") { _, _ -> + val editTextInput = editText.text.toString() + updatePositionFromQth(editTextInput) + } + .setNeutralButton("Cancel", null) + .create() + .show() + } + } + } + private fun setupTrackingCard() { + _binding?.prefsTracking?.let { binding -> + binding.trackingSwitch.apply { + isChecked = preferences.getRotatorEnabled() + binding.trackingIp.isEnabled = isChecked + binding.trackingIpEdit.setText(preferences.getRotatorIp()) + binding.trackingPort.isEnabled = isChecked + binding.trackingPortEdit.setText(preferences.getRotatorPort()) + setOnCheckedChangeListener { _, isChecked -> + preferences.setRotatorEnabled(isChecked) + binding.trackingIp.isEnabled = isChecked + binding.trackingPort.isEnabled = isChecked + } + } + binding.trackingIpEdit.doOnTextChanged { text, _, _, _ -> + if (text.toString().isValidIPv4()) preferences.setRotatorIp(text.toString()) + } + binding.trackingPortEdit.doOnTextChanged { text, _, _, _ -> + if (text.toString().isValidPort()) preferences.setRotatorPort(text.toString()) + } + } + } + + private fun setupOtherCard() { + _binding?.prefsOther?.let { binding -> + binding.otherSwitchUtc.apply { + isChecked = preferences.getUseUTC() + setOnCheckedChangeListener { _, isChecked -> preferences.setUseUTC(isChecked) } + } + binding.otherSwitchSweep.apply { + isChecked = preferences.getShowSweep() + setOnCheckedChangeListener { _, isChecked -> preferences.setShowSweep(isChecked) } + } + binding.otherSwitchSensors.apply { + isChecked = preferences.getUseCompass() + setOnCheckedChangeListener { _, isChecked -> preferences.setUseCompass(isChecked) } + } + } } private fun setupWarrantyCard() { _binding?.prefsWarranty?.let { binding -> - val moveMethod = LinkMovementMethod.getInstance() - binding.warrantyThanks.movementMethod = moveMethod - binding.warrantyLicense.movementMethod = moveMethod + binding.warrantyThanks.movementMethod = LinkMovementMethod.getInstance() + binding.warrantyLicense.movementMethod = LinkMovementMethod.getInstance() } } @@ -122,56 +183,14 @@ class PrefsFragment : Fragment(R.layout.fragment_prefs) { } else locPermReq.launch(arrayOf(locPermFine, locPermCoarse)) } - private fun updatePositionFromQth(qthString: String): Boolean { - return if (preferences.updatePositionFromQTH(qthString)) { + private fun updatePositionFromQth(qthString: String) { + if (preferences.updatePositionFromQTH(qthString)) { showSnack(getString(R.string.pref_pos_success)) - true } else { showSnack(getString(R.string.pref_pos_qth_error)) - false } } - private fun setupLater() { -// findPreference(PreferencesSource.keyPositionGPS)?.apply { -// setOnPreferenceClickListener { -// updatePositionFromGPS() -// return@setOnPreferenceClickListener true -// } -// } -// -// findPreference(PreferencesSource.keyPositionQTH)?.apply { -// setOnPreferenceChangeListener { _, newValue -> -// updatePositionFromQth(newValue.toString()) -// } -// } -// -// findPreference(PreferencesSource.keyRotatorAddress)?.apply { -// setOnPreferenceChangeListener { _, newValue -> -// val ip4 = "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\\.(?!\$)|\$)){4}\$" -// if (newValue.toString().matches(ip4.toRegex())) { -// return@setOnPreferenceChangeListener true -// } else { -// showSnack(getString(R.string.tracking_ip_invalid)) -// return@setOnPreferenceChangeListener false -// } -// } -// } -// -// findPreference(PreferencesSource.keyRotatorPort)?.apply { -// setOnBindEditTextListener { it.inputType = InputType.TYPE_CLASS_NUMBER } -// setOnPreferenceChangeListener { _, newValue -> -// val portValue = newValue.toString() -// if (portValue.isNotEmpty() && portValue.toInt() in 1024..65535) { -// return@setOnPreferenceChangeListener true -// } else { -// showSnack(getString(R.string.tracking_port_invalid)) -// return@setOnPreferenceChangeListener false -// } -// } -// } - } - private fun showSnack(message: String) { Snackbar.make(requireView(), message, Snackbar.LENGTH_SHORT).show() } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarFragment.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarFragment.kt index 757faaa5..1b571911 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarFragment.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarFragment.kt @@ -78,8 +78,8 @@ class RadarFragment : Fragment(R.layout.fragment_radar) { val aosTime = requireArguments().getLong("aosTime") viewModel.getPass(catNum, aosTime).observe(viewLifecycleOwner) { pass -> radarView = RadarView(requireContext()).apply { - setShowAim(preferences.shouldUseCompass()) - setScanning(preferences.shouldShowSweep()) + setShowAim(preferences.getUseCompass()) + setScanning(preferences.getShowSweep()) } binding.radarFrame.addView(radarView) viewModel.radarData.observe(viewLifecycleOwner, { passData -> 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 6d5032da..aa8d37eb 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 @@ -29,7 +29,6 @@ import com.rtbishop.look4sat.framework.PreferencesSource import com.rtbishop.look4sat.presentation.round import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import java.util.* @@ -64,11 +63,11 @@ class RadarViewModel @Inject constructor( } fun enableSensor() { - if (preferences.shouldUseCompass()) orientationSource.startListening(this) + if (preferences.getUseCompass()) orientationSource.startListening(this) } fun disableSensor() { - if (preferences.shouldUseCompass()) orientationSource.stopListening() + if (preferences.getUseCompass()) orientationSource.stopListening() } override fun onOrientationChanged(azimuth: Float, pitch: Float, roll: Float) { @@ -85,7 +84,7 @@ class RadarViewModel @Inject constructor( } while (isActive) { val satPos = predictor.getSatPos(satPass.satellite, stationPos, Date().time) - if (preferences.isRotatorEnabled()) { + if (preferences.getRotatorEnabled()) { val server = preferences.getRotatorServer().first val port = preferences.getRotatorServer().second val azimuth = Math.toDegrees(satPos.azimuth).round(1) diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index d1d1cbb0..45563536 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -11,4 +11,5 @@ #1e1e1e #99000000 @color/greyLight + @color/bgDark