diff --git a/app/build.gradle b/app/build.gradle index 9a276b18..b3e2fd0f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,7 +3,6 @@ plugins { id "org.jetbrains.kotlin.android" id "org.jetbrains.kotlin.kapt" id "dagger.hilt.android.plugin" - id "androidx.navigation.safeargs.kotlin" } android { diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/MainActivity.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/MainActivity.kt index 6c453f79..16c8efbd 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/MainActivity.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/MainActivity.kt @@ -26,12 +26,7 @@ import android.os.SystemClock import android.view.View import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.annotation.IdRes import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import androidx.fragment.app.Fragment -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver -import androidx.navigation.fragment.findNavController import com.rtbishop.look4sat.presentation.bottomNav.MainScreenView import dagger.hilt.android.AndroidEntryPoint @@ -54,24 +49,6 @@ class MainActivity : ComponentActivity() { } } -fun Fragment.getNavResult(@IdRes id: Int, key: String, onResult: (result: T) -> Unit) { - val backStackEntry = findNavController().getBackStackEntry(id) - val observer = LifecycleEventObserver { _, event -> - if (event == Lifecycle.Event.ON_RESUME && backStackEntry.savedStateHandle.contains(key)) { - backStackEntry.savedStateHandle.get(key)?.let(onResult) - backStackEntry.savedStateHandle.remove(key) - } - } - backStackEntry.lifecycle.addObserver(observer) - viewLifecycleOwner.lifecycle.addObserver(LifecycleEventObserver { _, event -> - if (event == Lifecycle.Event.ON_DESTROY) backStackEntry.lifecycle.removeObserver(observer) - }) -} - -fun Fragment.setNavResult(key: String, value: T) { - findNavController().previousBackStackEntry?.savedStateHandle?.set(key, value) -} - fun View.clickWithDebounce(debounceTime: Long = 875L, action: () -> Unit) { this.setOnClickListener(object : View.OnClickListener { private var lastClickTime: Long = 0 diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/bottomNav/BottomNavBar.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/bottomNav/BottomNavBar.kt index 1507e9cf..b17369ed 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/bottomNav/BottomNavBar.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/bottomNav/BottomNavBar.kt @@ -20,6 +20,7 @@ import androidx.navigation.compose.rememberNavController import com.rtbishop.look4sat.presentation.MainTheme import com.rtbishop.look4sat.presentation.aboutScreen.AboutScreen import com.rtbishop.look4sat.presentation.entriesScreen.EntriesScreen +import com.rtbishop.look4sat.presentation.mapScreen.MapScreen import com.rtbishop.look4sat.presentation.passesScreen.PassesScreen import com.rtbishop.look4sat.presentation.passesScreen.PassesViewModel @@ -58,7 +59,7 @@ fun NavigationGraph(navController: NavHostController) { EntriesScreen(navToPasses, passesViewModel = passesViewModel) } composable(BottomNavItem.Passes.screen_route) { PassesScreen(viewModel = passesViewModel) } - composable(BottomNavItem.WorldMap.screen_route) {} + composable(BottomNavItem.WorldMap.screen_route) { MapScreen() } composable(BottomNavItem.Settings.screen_route) {} composable(BottomNavItem.About.screen_route) { AboutScreen() } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/entriesScreen/ModesDialog.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/entriesScreen/ModesDialog.kt index 432f66ff..69045c6e 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/entriesScreen/ModesDialog.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/entriesScreen/ModesDialog.kt @@ -28,7 +28,6 @@ import androidx.recyclerview.widget.SimpleItemAnimator import com.rtbishop.look4sat.R import com.rtbishop.look4sat.databinding.DialogModesBinding import com.rtbishop.look4sat.domain.ISettingsManager -import com.rtbishop.look4sat.presentation.setNavResult import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -74,7 +73,7 @@ class ModesDialog : AppCompatDialogFragment(), ModesAdapter.ModesClickListener { } modesBtnNeg.setOnClickListener { dismiss() } modesBtnPos.setOnClickListener { - setNavResult("modes", selectedModes.toList()) +// setNavResult("modes", selectedModes.toList()) dismiss() } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/mapScreen/MapFragment.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/mapScreen/MapFragment.kt deleted file mode 100644 index 0f89287e..00000000 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/mapScreen/MapFragment.kt +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Look4Sat. Amateur radio satellite tracker and pass predictor. - * Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.rtbishop.look4sat.presentation.mapScreen - -import android.content.Context -import android.graphics.* -import android.graphics.drawable.BitmapDrawable -import android.graphics.drawable.Drawable -import android.os.Bundle -import android.view.View -import androidx.core.content.ContextCompat -import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels -import androidx.navigation.fragment.findNavController -import androidx.navigation.fragment.navArgs -import com.rtbishop.look4sat.R -import com.rtbishop.look4sat.databinding.FragmentMapBinding -import com.rtbishop.look4sat.domain.predict.GeoPos -import com.rtbishop.look4sat.domain.predict.SatPos -import com.rtbishop.look4sat.domain.predict.Satellite -import com.rtbishop.look4sat.presentation.clickWithDebounce -import dagger.hilt.android.AndroidEntryPoint -import org.osmdroid.config.Configuration -import org.osmdroid.tileprovider.tilesource.TileSourceFactory -import org.osmdroid.util.GeoPoint -import org.osmdroid.views.CustomZoomButtonsController -import org.osmdroid.views.MapView -import org.osmdroid.views.overlay.FolderOverlay -import org.osmdroid.views.overlay.Marker -import org.osmdroid.views.overlay.Polyline - -@AndroidEntryPoint -class MapFragment : Fragment(R.layout.fragment_map) { - - private val viewModel: MapViewModel by viewModels() - private val navArgs: MapFragmentArgs by navArgs() - private val minLat = MapView.getTileSystem().minLatitude - private val maxLat = MapView.getTileSystem().maxLatitude - private val trackPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - strokeWidth = 3f - style = Paint.Style.STROKE - color = Color.RED - strokeCap = Paint.Cap.ROUND - strokeJoin = Paint.Join.ROUND - } - private val footprintPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - strokeWidth = 3f - style = Paint.Style.FILL_AND_STROKE - color = Color.parseColor("#FFE082") - } - private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - textSize = 36f - style = Paint.Style.FILL - color = Color.parseColor("#CCFFFFFF") - setShadowLayer(3f, 3f, 3f, Color.BLACK) - } - private val labelRect = Rect() - private lateinit var binding: FragmentMapBinding - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding = FragmentMapBinding.bind(view) - setupComponents() - setupObservers() - } - - private fun setupComponents() { - val context = requireContext() - val preferences = context.getSharedPreferences("default", Context.MODE_PRIVATE) - Configuration.getInstance().load(context, preferences) - binding.run { - mapView.apply { - setMultiTouchControls(true) - setTileSource(TileSourceFactory.WIKIMEDIA) - minZoomLevel = getMinZoom(resources.displayMetrics.heightPixels) + 0.25 - maxZoomLevel = 5.75 - controller.setZoom(minZoomLevel + 0.25) - zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER) - overlayManager.tilesOverlay.loadingBackgroundColor = Color.TRANSPARENT - overlayManager.tilesOverlay.loadingLineColor = Color.TRANSPARENT - overlayManager.tilesOverlay.setColorFilter(getColorFilter()) - setScrollableAreaLimitLatitude(maxLat, minLat, 0) - // add overlays: 0 - GSP, 1 - SatTrack, 2 - SatFootprint, 3 - SatIcons - overlays.addAll(Array(4) { FolderOverlay() }) - } - mapBtnBack.clickWithDebounce { findNavController().navigateUp() } - mapBtnPrev.setOnClickListener { viewModel.scrollSelection(true) } - mapBtnNext.setOnClickListener { viewModel.scrollSelection(false) } - } - } - - private fun setupObservers() { - viewModel.selectDefaultSatellite(navArgs.catNum) - viewModel.stationPos.observe(viewLifecycleOwner) { handleStationPos(it) } - viewModel.positions.observe(viewLifecycleOwner) { handlePositions(it) } - viewModel.track.observe(viewLifecycleOwner) { handleTrack(it) } - viewModel.footprint.observe(viewLifecycleOwner) { handleFootprint(it) } - viewModel.mapData.observe(viewLifecycleOwner) { handleMapData(it) } - } - - private fun handleStationPos(stationPos: GeoPos) { - binding.apply { - Marker(mapView).apply { - setInfoWindow(null) - setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM) - icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_position) - position = GeoPoint(stationPos.lat, stationPos.lon) - mapView.overlays[0] = this - mapView.invalidate() - } - } - } - - private fun handlePositions(posMap: Map) { - binding.apply { - val markers = FolderOverlay() - posMap.entries.forEach { - Marker(mapView).apply { - setInfoWindow(null) - setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER) - icon = getCustomTextIcon(it.key.data.name) - try { - position = GeoPoint(it.value.lat, it.value.lon) - } catch (exception: IllegalArgumentException) { - println(exception.stackTraceToString()) - } - setOnMarkerClickListener { _, _ -> - viewModel.selectSatellite(it.key) - return@setOnMarkerClickListener true - } - markers.add(this) - } - } - mapView.overlays[3] = markers - mapView.invalidate() - } - } - - private fun getCustomTextIcon(textLabel: String): Drawable { - textPaint.getTextBounds(textLabel, 0, textLabel.length, labelRect) - val iconSize = 8f - val width = labelRect.width() + iconSize * 2f - val height = textPaint.textSize * 3f + iconSize * 2f - val bitmap = Bitmap.createBitmap(width.toInt(), height.toInt(), Bitmap.Config.ARGB_8888) - Canvas(bitmap).run { - drawCircle(width / 2f, height / 2f, iconSize, textPaint) - drawText(textLabel, iconSize / 2f, height - iconSize, textPaint) - } - return BitmapDrawable(binding.mapView.context.resources, bitmap) - } - - private fun handleTrack(satTrack: List>) { - val center = GeoPoint(satTrack[0][0].lat, satTrack[0][0].lon) - val trackOverlay = FolderOverlay() - satTrack.forEach { track -> - val trackPoints = track.map { GeoPoint(it.lat, it.lon) } - Polyline().apply { - try { - setPoints(trackPoints) - outlinePaint.set(trackPaint) - trackOverlay.add(this) - } catch (exception: IllegalArgumentException) { - println(exception.stackTraceToString()) - } - } - } - binding.mapView.overlays[1] = trackOverlay - binding.mapView.controller.animateTo(center) - } - - private fun handleFootprint(satPos: SatPos) { - val footprintPoints = satPos.getRangeCircle().map { GeoPoint(it.lat, it.lon) } - val footprintOverlay = Polyline().apply { - outlinePaint.set(footprintPaint) - try { - setPoints(footprintPoints) - } catch (exception: IllegalArgumentException) { - println(exception.stackTraceToString()) - } - } - binding.mapView.overlays[2] = footprintOverlay - } - - private fun handleMapData(mapData: MapData) { - binding.apply { - mapTimer.text = mapData.aosTime - mapDataPeriod.text = String.format(getString(R.string.map_period, mapData.period)) - mapDataPhase.text = String.format(getString(R.string.map_phase), mapData.phase) - mapAzimuth.text = String.format(getString(R.string.map_azimuth), mapData.azimuth) - mapElevation.text = String.format(getString(R.string.map_elevation), mapData.elevation) - mapDataAlt.text = String.format(getString(R.string.map_altitude), mapData.altitude) - mapDataDst.text = String.format(getString(R.string.map_distance), mapData.range) - mapDataLat.text = - String.format(getString(R.string.map_latitude), mapData.osmPos.lat) - mapDataLon.text = - String.format(getString(R.string.map_longitude), mapData.osmPos.lon) - mapDataQth.text = String.format(getString(R.string.map_qth), mapData.qthLoc) - mapDataVel.text = String.format(getString(R.string.map_velocity), mapData.velocity) - if (mapData.eclipsed) { - mapDataVisibility.text = getString(R.string.map_eclipsed) - } else { - mapDataVisibility.text = getString(R.string.map_visible) - } - } - binding.mapView.invalidate() - } - - private fun getColorFilter(): ColorMatrixColorFilter { - val grayScaleMatrix = ColorMatrix().apply { setSaturation(0f) } - val negativeMatrix = ColorMatrix( - floatArrayOf( - -1f, 0f, 0f, 0f, 255f, - 0f, -1f, 0f, 0f, 255f, - 0f, 0f, -1f, 0f, 255f, - 0f, 0f, 0f, 1f, 0f - ) - ) - negativeMatrix.preConcat(grayScaleMatrix) - return ColorMatrixColorFilter(negativeMatrix) - } - - private fun getMinZoom(screenHeight: Int): Double { - return MapView.getTileSystem().getLatitudeZoom(maxLat, minLat, screenHeight) - } -} diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/mapScreen/MapScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/mapScreen/MapScreen.kt new file mode 100644 index 00000000..58d40993 --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/mapScreen/MapScreen.kt @@ -0,0 +1,247 @@ +package com.rtbishop.look4sat.presentation.mapScreen + +import android.content.Context +import android.graphics.* +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ElevatedCard +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.content.ContextCompat +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import com.rtbishop.look4sat.R +import com.rtbishop.look4sat.domain.predict.GeoPos +import com.rtbishop.look4sat.domain.predict.SatPos +import com.rtbishop.look4sat.domain.predict.Satellite +import org.osmdroid.config.Configuration +import org.osmdroid.tileprovider.tilesource.TileSourceFactory +import org.osmdroid.util.GeoPoint +import org.osmdroid.views.CustomZoomButtonsController +import org.osmdroid.views.MapView +import org.osmdroid.views.overlay.FolderOverlay +import org.osmdroid.views.overlay.Marker +import org.osmdroid.views.overlay.Polyline +import timber.log.Timber + +private val minLat = MapView.getTileSystem().minLatitude +private val maxLat = MapView.getTileSystem().maxLatitude +private val trackPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + strokeWidth = 3f + style = Paint.Style.STROKE + color = Color.RED + strokeCap = Paint.Cap.ROUND + strokeJoin = Paint.Join.ROUND +} +private val footprintPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + strokeWidth = 3f + style = Paint.Style.FILL_AND_STROKE + color = Color.parseColor("#FFE082") +} +private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + textSize = 36f + style = Paint.Style.FILL + color = Color.parseColor("#CCFFFFFF") + setShadowLayer(3f, 3f, 3f, Color.BLACK) +} +private val labelRect = Rect() + +@Composable +fun MapScreen(viewModel: MapViewModel = hiltViewModel()) { + val context = LocalContext.current + Configuration.getInstance().load(context, viewModel.getPreferences()) + viewModel.selectDefaultSatellite(-1) + val stationPos = viewModel.stationPos.observeAsState() + val positions = viewModel.positions.observeAsState() + val satTrack = viewModel.track.observeAsState() + val footprint = viewModel.footprint.observeAsState() + val positionClick = { satellite: Satellite -> viewModel.selectSatellite(satellite) } + Timber.d("MapScreen recomposition") + Column(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ElevatedCard(modifier = Modifier.fillMaxSize()) { + MapView(modifier = Modifier.fillMaxSize()) { mapView -> + Timber.d("MapView recomposition") + stationPos.value?.let { setStationPosition(it, mapView) } + positions.value?.let { setPositions(it, mapView, positionClick) } + satTrack.value?.let { setSatelliteTrack(it, mapView) } + footprint.value?.let { setFootprint(it, mapView) } + } + } + } +} + +private fun setStationPosition(stationPos: GeoPos, mapView: MapView) { + Marker(mapView).apply { + setInfoWindow(null) + setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM) + icon = ContextCompat.getDrawable(mapView.context, R.drawable.ic_position) + position = GeoPoint(stationPos.lat, stationPos.lon) + mapView.overlays[0] = this + mapView.invalidate() + } +} + +private fun setPositions( + posMap: Map, mapView: MapView, action: (Satellite) -> Unit +) { + val markers = FolderOverlay() + posMap.entries.forEach { + Marker(mapView).apply { + setInfoWindow(null) + setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER) + icon = getCustomTextIcon(it.key.data.name, mapView) + try { + position = GeoPoint(it.value.lat, it.value.lon) + } catch (exception: IllegalArgumentException) { + println(exception.stackTraceToString()) + } + setOnMarkerClickListener { _, _ -> + action(it.key) + return@setOnMarkerClickListener true + } + markers.add(this) + } + } + mapView.overlays[3] = markers + mapView.invalidate() +} + +private fun getCustomTextIcon(textLabel: String, mapView: MapView): Drawable { + textPaint.getTextBounds(textLabel, 0, textLabel.length, labelRect) + val iconSize = 8f + val width = labelRect.width() + iconSize * 2f + val height = textPaint.textSize * 3f + iconSize * 2f + val bitmap = Bitmap.createBitmap(width.toInt(), height.toInt(), Bitmap.Config.ARGB_8888) + Canvas(bitmap).run { + drawCircle(width / 2f, height / 2f, iconSize, textPaint) + drawText(textLabel, iconSize / 2f, height - iconSize, textPaint) + } + return BitmapDrawable(mapView.context.resources, bitmap) +} + +private fun setSatelliteTrack(satTrack: List>, mapView: MapView) { +// val center = GeoPoint(satTrack[0][0].lat, satTrack[0][0].lon) + val trackOverlay = FolderOverlay() + satTrack.forEach { track -> + val trackPoints = track.map { GeoPoint(it.lat, it.lon) } + Polyline().apply { + try { + setPoints(trackPoints) + outlinePaint.set(trackPaint) + trackOverlay.add(this) + } catch (exception: IllegalArgumentException) { + println(exception.stackTraceToString()) + } + } + } + mapView.overlays[1] = trackOverlay +// mapView.controller.animateTo(center) +} + +private fun setFootprint(satPos: SatPos, mapView: MapView) { + val footprintPoints = satPos.getRangeCircle().map { GeoPoint(it.lat, it.lon) } + val footprintOverlay = Polyline().apply { + outlinePaint.set(footprintPaint) + try { + setPoints(footprintPoints) + } catch (exception: IllegalArgumentException) { + println(exception.stackTraceToString()) + } + } + mapView.overlays[2] = footprintOverlay +} + +//private fun handleMapData(mapData: MapData) { +// binding.apply { +// mapTimer.text = mapData.aosTime +// mapDataPeriod.text = String.format(getString(R.string.map_period, mapData.period)) +// mapDataPhase.text = String.format(getString(R.string.map_phase), mapData.phase) +// mapAzimuth.text = String.format(getString(R.string.map_azimuth), mapData.azimuth) +// mapElevation.text = String.format(getString(R.string.map_elevation), mapData.elevation) +// mapDataAlt.text = String.format(getString(R.string.map_altitude), mapData.altitude) +// mapDataDst.text = String.format(getString(R.string.map_distance), mapData.range) +// mapDataLat.text = +// String.format(getString(R.string.map_latitude), mapData.osmPos.lat) +// mapDataLon.text = +// String.format(getString(R.string.map_longitude), mapData.osmPos.lon) +// mapDataQth.text = String.format(getString(R.string.map_qth), mapData.qthLoc) +// mapDataVel.text = String.format(getString(R.string.map_velocity), mapData.velocity) +// if (mapData.eclipsed) { +// mapDataVisibility.text = getString(R.string.map_eclipsed) +// } else { +// mapDataVisibility.text = getString(R.string.map_visible) +// } +// } +// binding.mapView.invalidate() +//} + +@Composable +private fun MapView(modifier: Modifier = Modifier, update: ((map: MapView) -> Unit)? = null) { + val mapView = rememberMapViewWithLifecycle() + AndroidView({ mapView }, modifier) { update?.invoke(it) } +} + +@Composable +private fun rememberMapViewWithLifecycle(context: Context = LocalContext.current): MapView { + // add clipToOutline = true if needed + val mapView = remember { MapView(context).apply { id = R.id.osm_map_view } }.apply { + setMultiTouchControls(true) + setTileSource(TileSourceFactory.WIKIMEDIA) + minZoomLevel = getMinZoom(resources.displayMetrics.heightPixels) + 0.25 + maxZoomLevel = 5.75 + controller.setZoom(minZoomLevel + 0.25) + zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER) + overlayManager.tilesOverlay.loadingBackgroundColor = Color.TRANSPARENT + overlayManager.tilesOverlay.loadingLineColor = Color.TRANSPARENT + overlayManager.tilesOverlay.setColorFilter(getColorFilter()) + setScrollableAreaLimitLatitude(maxLat, minLat, 0) + // add overlays: 0 - GSP, 1 - SatTrack, 2 - SatFootprint, 3 - SatIcons + overlays.addAll(Array(4) { FolderOverlay() }) + } + // Makes MapView follow the lifecycle of this composable + val lifecycleObserver = rememberMapViewLifecycleObserver(mapView) + val lifecycle = LocalLifecycleOwner.current.lifecycle + DisposableEffect(lifecycle) { + lifecycle.addObserver(lifecycleObserver) + onDispose { lifecycle.removeObserver(lifecycleObserver) } + } + return mapView +} + +@Composable +private fun rememberMapViewLifecycleObserver(mapView: MapView) = remember(mapView) { + LifecycleEventObserver { _, event -> + when (event) { + Lifecycle.Event.ON_RESUME -> mapView.onResume() + Lifecycle.Event.ON_PAUSE -> mapView.onPause() + else -> {} + } + } +} + +private fun getColorFilter(): ColorMatrixColorFilter { + val grayScaleMatrix = ColorMatrix().apply { setSaturation(0f) } + val negativeMatrix = ColorMatrix( + floatArrayOf( + -1f, 0f, 0f, 0f, 255f, 0f, -1f, 0f, 0f, 255f, 0f, 0f, -1f, 0f, 255f, 0f, 0f, 0f, 1f, 0f + ) + ) + negativeMatrix.preConcat(grayScaleMatrix) + return ColorMatrixColorFilter(negativeMatrix) +} + +private fun getMinZoom(screenHeight: Int): Double { + return MapView.getTileSystem().getLatitudeZoom(maxLat, minLat, screenHeight) +} diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/mapScreen/MapViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/mapScreen/MapViewModel.kt index cdfe718a..d549de04 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/mapScreen/MapViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/mapScreen/MapViewModel.kt @@ -17,6 +17,7 @@ */ package com.rtbishop.look4sat.presentation.mapScreen +import android.content.SharedPreferences import androidx.lifecycle.* import com.rtbishop.look4sat.domain.IDataRepository import com.rtbishop.look4sat.domain.ISatelliteManager @@ -36,9 +37,10 @@ import kotlin.math.min @HiltViewModel class MapViewModel @Inject constructor( - private val satelliteManager: ISatelliteManager, + private val preferences: SharedPreferences, private val repository: IDataRepository, - private val settings: ISettingsManager, + private val satelliteManager: ISatelliteManager, + private val settings: ISettingsManager ) : ViewModel() { private val stationPosition = settings.loadStationPosition() @@ -66,6 +68,8 @@ class MapViewModel @Inject constructor( private val _positions = MutableLiveData>() val positions: LiveData> = _positions + fun getPreferences() = preferences + fun scrollSelection(decrement: Boolean) { if (allSatellites.isNotEmpty()) { val index = allSatellites.indexOf(selectedSatellite) @@ -184,8 +188,19 @@ class MapViewModel @Inject constructor( val phase = satPos.phase.toDegrees() val visibility = satPos.eclipsed val satData = MapData( - sat.data.catnum, sat.data.name, aosTime, azimuth, elevation, satPos.distance, - satPos.altitude, velocity, qthLoc, osmPos, sat.data.orbitalPeriod, phase, visibility + sat.data.catnum, + sat.data.name, + aosTime, + azimuth, + elevation, + satPos.distance, + satPos.altitude, + velocity, + qthLoc, + osmPos, + sat.data.orbitalPeriod, + phase, + visibility ) _mapData.postValue(satData) } 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 e4e15ec0..2c054691 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 @@ -17,14 +17,10 @@ */ package com.rtbishop.look4sat.presentation.radarScreen -import android.content.Intent import android.os.Bundle -import android.provider.CalendarContract import android.view.View import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels -import androidx.navigation.fragment.findNavController -import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.SimpleItemAnimator @@ -32,7 +28,6 @@ import com.rtbishop.look4sat.R import com.rtbishop.look4sat.databinding.FragmentRadarBinding import com.rtbishop.look4sat.domain.predict.SatPass import com.rtbishop.look4sat.domain.predict.SatPos -import com.rtbishop.look4sat.presentation.clickWithDebounce import com.rtbishop.look4sat.utility.toDegrees import com.rtbishop.look4sat.utility.toTimerString import dagger.hilt.android.AndroidEntryPoint @@ -41,7 +36,6 @@ import dagger.hilt.android.AndroidEntryPoint class RadarFragment : Fragment(R.layout.fragment_radar) { private val viewModel: RadarViewModel by viewModels() - private val navArgs: RadarFragmentArgs by navArgs() private val radioAdapter = RadioAdapter() private var binding: FragmentRadarBinding? = null private var radarView: RadarView? = null @@ -78,7 +72,7 @@ class RadarFragment : Fragment(R.layout.fragment_radar) { } private fun setupObservers() { - viewModel.getPass(navArgs.catNum, navArgs.aosTime).observe(viewLifecycleOwner) { pass -> + viewModel.getPass(45000, System.currentTimeMillis()).observe(viewLifecycleOwner) { pass -> binding?.run { radarView = RadarView(requireContext()).apply { setShowAim(viewModel.getUseCompass()) @@ -103,24 +97,16 @@ class RadarFragment : Fragment(R.layout.fragment_radar) { viewModel.orientation.observe(viewLifecycleOwner) { value -> radarView?.setOrientation(value.first, value.second, value.third) } - radarBtnBack.clickWithDebounce { findNavController().navigateUp() } - radarBtnMap.clickWithDebounce { - val direction = RadarFragmentDirections.globalToMap(pass.catNum) - findNavController().navigate(direction) - } - radarBtnNotify.clickWithDebounce { - val intent = Intent(Intent.ACTION_INSERT) - .setData(CalendarContract.Events.CONTENT_URI) - .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, pass.aosTime) - .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, pass.losTime) - .putExtra(CalendarContract.Events.TITLE, pass.name) - .putExtra(CalendarContract.Events.DESCRIPTION, "Look4Sat") - startActivity(intent) - } - radarBtnSettings.clickWithDebounce { - val direction = RadarFragmentDirections.globalToSettings() - findNavController().navigate(direction) - } +// radarBtnBack.clickWithDebounce { findNavController().navigateUp() } +// radarBtnMap.clickWithDebounce { +// val direction = RadarFragmentDirections.globalToMap(pass.catNum) +// findNavController().navigate(direction) +// } +// radarBtnNotify.isEnabled = false +// radarBtnSettings.clickWithDebounce { +// val direction = RadarFragmentDirections.globalToSettings() +// findNavController().navigate(direction) +// } } } } @@ -150,7 +136,7 @@ class RadarFragment : Fragment(R.layout.fragment_radar) { radarTimer.text = millisBeforeEnd.toTimerString() if (timeNow > satPass.losTime) { radarTimer.text = 0L.toTimerString() - findNavController().navigateUp() +// findNavController().navigateUp() } } } else radarTimer.text = 0L.toTimerString() diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/LocatorDialog.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/LocatorDialog.kt index 7c98c377..1f8cd427 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/LocatorDialog.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/LocatorDialog.kt @@ -26,7 +26,6 @@ import androidx.appcompat.app.AppCompatDialogFragment import com.rtbishop.look4sat.R import com.rtbishop.look4sat.databinding.DialogLocatorBinding import com.rtbishop.look4sat.domain.ISettingsManager -import com.rtbishop.look4sat.presentation.setNavResult import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -50,7 +49,7 @@ class LocatorDialog : AppCompatDialogFragment() { ) locatorEdit.setText(preferences.loadStationLocator()) locatorBtnPos.setOnClickListener { - setNavResult("locator", locatorEdit.text.toString()) +// setNavResult("locator", locatorEdit.text.toString()) dismiss() } locatorBtnNeg.setOnClickListener { dismiss() } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/PositionDialog.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/PositionDialog.kt index aec4b3a9..4833034a 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/PositionDialog.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/PositionDialog.kt @@ -26,7 +26,6 @@ import androidx.appcompat.app.AppCompatDialogFragment import com.rtbishop.look4sat.R import com.rtbishop.look4sat.databinding.DialogPositionBinding import com.rtbishop.look4sat.domain.ISettingsManager -import com.rtbishop.look4sat.presentation.setNavResult import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -52,17 +51,17 @@ class PositionDialog : AppCompatDialogFragment() { positionLatEdit.setText(location.lat.toString()) positionLonEdit.setText(location.lon.toString()) positionBtnPos.setOnClickListener { - val latitude = try { - positionLatEdit.text.toString().toDouble() - } catch (exception: Exception) { - 180.0 - } - val longitude = try { - positionLonEdit.text.toString().toDouble() - } catch (exception: Exception) { - 400.0 - } - setNavResult("position", Pair(latitude, longitude)) +// val latitude = try { +// positionLatEdit.text.toString().toDouble() +// } catch (exception: Exception) { +// 180.0 +// } +// val longitude = try { +// positionLonEdit.text.toString().toDouble() +// } catch (exception: Exception) { +// 400.0 +// } +// setNavResult("position", Pair(latitude, longitude)) dismiss() } positionBtnNeg.setOnClickListener { dismiss() } 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 1f673f0b..b94ee1bb 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 @@ -31,14 +31,12 @@ import androidx.core.widget.doOnTextChanged 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 import com.rtbishop.look4sat.domain.predict.GeoPos import com.rtbishop.look4sat.presentation.clickWithDebounce -import com.rtbishop.look4sat.presentation.getNavResult import com.rtbishop.look4sat.utility.isValidIPv4 import com.rtbishop.look4sat.utility.isValidPort import dagger.hilt.android.AndroidEntryPoint @@ -79,7 +77,7 @@ class SettingsFragment : Fragment(R.layout.fragment_settings) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentSettingsBinding.bind(view).apply { - settingsBtnBack.clickWithDebounce { findNavController().navigateUp() } +// settingsBtnBack.clickWithDebounce { findNavController().navigateUp() } settingsScroll.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, y, _, newY -> if (y > newY) settingsFab.hide() else settingsFab.show() }) @@ -115,20 +113,20 @@ class SettingsFragment : Fragment(R.layout.fragment_settings) { settingsLocation.locationBtnGps.clickWithDebounce { locationRequest.launch(arrayOf(locationFine, locationCoarse)) } - settingsLocation.locationBtnManual.clickWithDebounce { - val action = SettingsFragmentDirections.globalToPosition() - findNavController().navigate(action) - } - settingsLocation.locationBtnQth.clickWithDebounce { - val action = SettingsFragmentDirections.globalToLocator() - findNavController().navigate(action) - } - getNavResult>(R.id.nav_settings, "position") { position -> - viewModel.setStationPosition(position.first, position.second) - } - getNavResult(R.id.nav_settings, "locator") { locator -> - viewModel.setPositionFromQth(locator) - } +// settingsLocation.locationBtnManual.clickWithDebounce { +// val action = SettingsFragmentDirections.globalToPosition() +// findNavController().navigate(action) +// } +// settingsLocation.locationBtnQth.clickWithDebounce { +// val action = SettingsFragmentDirections.globalToLocator() +// findNavController().navigate(action) +// } +// getNavResult>(R.id.nav_settings, "position") { position -> +// viewModel.setStationPosition(position.first, position.second) +// } +// getNavResult(R.id.nav_settings, "locator") { locator -> +// viewModel.setPositionFromQth(locator) +// } } } @@ -146,9 +144,9 @@ class SettingsFragment : Fragment(R.layout.fragment_settings) { val radiosFormat = getString(R.string.data_radios) settingsData.dataRadios.text = String.format(radiosFormat, number) } - getNavResult>(R.id.nav_settings, "sources") { - viewModel.updateFromWeb() - } +// getNavResult>(R.id.nav_settings, "sources") { +// viewModel.updateFromWeb() +// } } } diff --git a/app/src/main/res/drawable/ic_checkbox_off.xml b/app/src/main/res/drawable/ic_checkbox_off.xml index 1547143b..323c8421 100644 --- a/app/src/main/res/drawable/ic_checkbox_off.xml +++ b/app/src/main/res/drawable/ic_checkbox_off.xml @@ -1,7 +1,8 @@ - + + diff --git a/app/src/main/res/drawable/ic_filter.xml b/app/src/main/res/drawable/ic_filter.xml index 92074d26..17848623 100644 --- a/app/src/main/res/drawable/ic_filter.xml +++ b/app/src/main/res/drawable/ic_filter.xml @@ -1,7 +1,8 @@ - - diff --git a/app/src/main/res/drawable/ic_radar_def.xml b/app/src/main/res/drawable/ic_radar_def.xml deleted file mode 100644 index 8771c662..00000000 --- a/app/src/main/res/drawable/ic_radar_def.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_radar_pos.xml b/app/src/main/res/drawable/ic_radar_pos.xml index 94dc8f5b..d3e6123d 100644 --- a/app/src/main/res/drawable/ic_radar_pos.xml +++ b/app/src/main/res/drawable/ic_radar_pos.xml @@ -1,6 +1,7 @@ diff --git a/app/src/main/res/drawable/ic_radar_tech.xml b/app/src/main/res/drawable/ic_radar_tech.xml deleted file mode 100644 index 7f2a47ef..00000000 --- a/app/src/main/res/drawable/ic_radar_tech.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/layout/fragment_map.xml b/app/src/main/res/layout/fragment_map.xml deleted file mode 100644 index 81040354..00000000 --- a/app/src/main/res/layout/fragment_map.xml +++ /dev/null @@ -1,242 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -