mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-04 11:37:22 +00:00
Added map in compose, removed MapFragment
This commit is contained in:
1 parent
acb851d4f6
commit
c592b8d3ce
23 files changed
+333
-737
No files matched your search
@@ -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 <T> 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<T>(key)?.let(onResult)
|
||||
backStackEntry.savedStateHandle.remove<T>(key)
|
||||
}
|
||||
}
|
||||
backStackEntry.lifecycle.addObserver(observer)
|
||||
viewLifecycleOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
|
||||
if (event == Lifecycle.Event.ON_DESTROY) backStackEntry.lifecycle.removeObserver(observer)
|
||||
})
|
||||
}
|
||||
|
||||
fun <T> 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
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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<Satellite, GeoPos>) {
|
||||
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<List<GeoPos>>) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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<Satellite, GeoPos>, 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<List<GeoPos>>, 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)
|
||||
}
|
||||
@@ -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<Map<Satellite, GeoPos>>()
|
||||
val positions: LiveData<Map<Satellite, GeoPos>> = _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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
+1
-2
@@ -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() }
|
||||
|
||||
+11
-12
@@ -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() }
|
||||
|
||||
+18
-20
@@ -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<Pair<Double, Double>>(R.id.nav_settings, "position") { position ->
|
||||
viewModel.setStationPosition(position.first, position.second)
|
||||
}
|
||||
getNavResult<String>(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<Pair<Double, Double>>(R.id.nav_settings, "position") { position ->
|
||||
// viewModel.setStationPosition(position.first, position.second)
|
||||
// }
|
||||
// getNavResult<String>(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<List<String>>(R.id.nav_settings, "sources") {
|
||||
viewModel.updateFromWeb()
|
||||
}
|
||||
// getNavResult<List<String>>(R.id.nav_settings, "sources") {
|
||||
// viewModel.updateFromWeb()
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user