mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-04 03:37:21 +00:00
Minor refactoring. Changes to the app name and the package.
This commit is contained in:
1 parent
1b741bc572
commit
0b93780d7c
28 files changed
+116
-178
No files matched your search
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* LookingSat. Amateur radio & weather satellite tracker and passes calculator.
|
||||
* Copyright (C) 2019 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 2 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat
|
||||
|
||||
import android.app.Application
|
||||
import com.rtbishop.look4sat.di.AppComponent
|
||||
import com.rtbishop.look4sat.di.DaggerAppComponent
|
||||
|
||||
class Look4SatApp : Application() {
|
||||
|
||||
val appComponent: AppComponent by lazy {
|
||||
DaggerAppComponent.factory().create(applicationContext)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* LookingSat. Amateur radio & weather satellite tracker and passes calculator.
|
||||
* Copyright (C) 2019 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 2 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.location.Location
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.amsacode.predict4java.GroundStationPosition
|
||||
import com.github.amsacode.predict4java.PassPredictor
|
||||
import com.github.amsacode.predict4java.SatNotFoundException
|
||||
import com.github.amsacode.predict4java.TLE
|
||||
import com.google.android.gms.location.FusedLocationProviderClient
|
||||
import com.rtbishop.look4sat.repo.Repository
|
||||
import com.rtbishop.look4sat.repo.SatPass
|
||||
import com.rtbishop.look4sat.repo.Transmitter
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.io.ObjectInputStream
|
||||
import java.io.ObjectOutputStream
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
private val defValueLoc = application.getString(R.string.def_gsp_loc)
|
||||
private val defValueHours = application.getString(R.string.def_hours_ahead)
|
||||
private val defValueMinEl = application.getString(R.string.def_min_el)
|
||||
private val defValueUpdateFreq = application.getString(R.string.def_update_freq)
|
||||
private val keyLat = application.getString(R.string.key_lat)
|
||||
private val keyLon = application.getString(R.string.key_lon)
|
||||
private val keyAlt = application.getString(R.string.key_alt)
|
||||
private val keyHours = application.getString(R.string.key_hours_ahead)
|
||||
private val keyMinEl = application.getString(R.string.key_min_el)
|
||||
private val keyUpdateFreq = application.getString(R.string.key_update_freq)
|
||||
private val tleMainListFileName = application.getString(R.string.tle_main_list_file_name)
|
||||
private val tleSelectionFileName = application.getString(R.string.tle_selection_file_name)
|
||||
private val app = application
|
||||
|
||||
@Inject
|
||||
lateinit var locationClient: FusedLocationProviderClient
|
||||
@Inject
|
||||
lateinit var preferences: SharedPreferences
|
||||
@Inject
|
||||
lateinit var repository: Repository
|
||||
|
||||
init {
|
||||
(app as Look4SatApp).appComponent.inject(this)
|
||||
}
|
||||
|
||||
private var passPrefs = SatPassPrefs(
|
||||
preferences.getString(keyHours, defValueHours)!!.toInt(),
|
||||
preferences.getString(keyMinEl, defValueMinEl)!!.toDouble()
|
||||
)
|
||||
|
||||
private val _debugMessage = MutableLiveData<String>()
|
||||
val debugMessage: LiveData<String> = _debugMessage
|
||||
|
||||
private val _gsp = MutableLiveData<GroundStationPosition>(
|
||||
GroundStationPosition(
|
||||
preferences.getString(keyLat, defValueLoc)!!.toDouble(),
|
||||
preferences.getString(keyLon, defValueLoc)!!.toDouble(),
|
||||
preferences.getString(keyAlt, defValueLoc)!!.toDouble()
|
||||
)
|
||||
)
|
||||
val gsp: LiveData<GroundStationPosition> = _gsp
|
||||
|
||||
var satPassList = emptyList<SatPass>()
|
||||
var tleMainList = loadTwoLineElementFile()
|
||||
var selectionList = loadSelectionList()
|
||||
val updateFreq: Long
|
||||
get() = preferences.getString(keyUpdateFreq, defValueUpdateFreq)!!.toLong()
|
||||
|
||||
fun updateGsp() {
|
||||
val lat = preferences.getString(keyLat, defValueLoc)!!.toDouble()
|
||||
val lon = preferences.getString(keyLon, defValueLoc)!!.toDouble()
|
||||
val alt = preferences.getString(keyAlt, defValueLoc)!!.toDouble()
|
||||
_gsp.postValue(GroundStationPosition(lat, lon, alt))
|
||||
}
|
||||
|
||||
fun getCurrentLocation() {
|
||||
locationClient.lastLocation.addOnSuccessListener { location: Location? ->
|
||||
val lat = location?.latitude ?: 0.0
|
||||
val lon = location?.longitude ?: 0.0
|
||||
val alt = location?.altitude ?: 0.0
|
||||
|
||||
preferences.edit {
|
||||
putString(keyLat, lat.toString())
|
||||
putString(keyLon, lon.toString())
|
||||
putString(keyAlt, alt.toString())
|
||||
apply()
|
||||
}
|
||||
_gsp.postValue(GroundStationPosition(lat, lon, alt))
|
||||
_debugMessage.postValue("Location was updated")
|
||||
}
|
||||
}
|
||||
|
||||
fun updateAndSaveTleFile() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val inputStream = repository.fetchTleStream()
|
||||
val tleList = TLE.importSat(inputStream).apply { sortBy { it.name } }
|
||||
val fileOutStream = app.openFileOutput(tleMainListFileName, Context.MODE_PRIVATE)
|
||||
ObjectOutputStream(fileOutStream).apply {
|
||||
writeObject(tleList)
|
||||
flush()
|
||||
close()
|
||||
}
|
||||
tleMainList = tleList
|
||||
selectionList = mutableListOf()
|
||||
_debugMessage.postValue("TLE file was updated")
|
||||
} catch (exception: IOException) {
|
||||
_debugMessage.postValue("Couldn't update TLE file")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateTransmittersDatabase() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
repository.updateTransmittersDatabase()
|
||||
_debugMessage.postValue("Transmitters were updated")
|
||||
} catch (exception: IOException) {
|
||||
_debugMessage.postValue("Couldn't update transmitters")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateAndSaveSelectionList(list: MutableList<Int>) {
|
||||
selectionList = list
|
||||
try {
|
||||
val fileOutputStream = app.openFileOutput(tleSelectionFileName, Context.MODE_PRIVATE)
|
||||
ObjectOutputStream(fileOutputStream).apply {
|
||||
writeObject(list)
|
||||
flush()
|
||||
close()
|
||||
}
|
||||
} catch (exception: IOException) {
|
||||
_debugMessage.postValue(exception.toString())
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getPasses() {
|
||||
val hoursAhead = preferences.getString(keyHours, defValueHours)!!.toInt()
|
||||
val minEl = preferences.getString(keyMinEl, defValueMinEl)!!.toDouble()
|
||||
val passList = mutableListOf<SatPass>()
|
||||
withContext(Dispatchers.Default) {
|
||||
selectionList.forEach { indexOfSelection ->
|
||||
val tle = tleMainList[indexOfSelection]
|
||||
try {
|
||||
val predictor = PassPredictor(tle, gsp.value)
|
||||
val passes = predictor.getPasses(Date(), hoursAhead, true)
|
||||
passes.forEach { passList.add(SatPass(tle, predictor, it)) }
|
||||
} catch (exception: IllegalArgumentException) {
|
||||
_debugMessage.postValue("There was a problem with ${tle.name}")
|
||||
} catch (exception: SatNotFoundException) {
|
||||
_debugMessage.postValue("${tle.name} shall not pass")
|
||||
}
|
||||
}
|
||||
passList.retainAll { it.pass.maxEl >= minEl }
|
||||
passList.sortBy { it.pass.startTime }
|
||||
}
|
||||
satPassList = passList
|
||||
}
|
||||
|
||||
suspend fun getTransmittersForSat(id: Int): List<Transmitter> {
|
||||
return repository.getTransmittersForSat(id)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun loadTwoLineElementFile(): List<TLE> {
|
||||
try {
|
||||
val fileInputStream = app.openFileInput(tleMainListFileName)
|
||||
val tleList = ObjectInputStream(fileInputStream).readObject()
|
||||
return tleList as List<TLE>
|
||||
} catch (exception: FileNotFoundException) {
|
||||
_debugMessage.postValue("TLE file wasn't found")
|
||||
} catch (exception: IOException) {
|
||||
_debugMessage.postValue(exception.toString())
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun loadSelectionList(): MutableList<Int> {
|
||||
try {
|
||||
val fileInputStream = app.openFileInput(tleSelectionFileName)
|
||||
val selectionList = ObjectInputStream(fileInputStream).readObject()
|
||||
return selectionList as MutableList<Int>
|
||||
} catch (exception: FileNotFoundException) {
|
||||
_debugMessage.postValue("Selection file wasn't found")
|
||||
} catch (exception: IOException) {
|
||||
_debugMessage.postValue(exception.toString())
|
||||
}
|
||||
return mutableListOf()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* LookingSat. Amateur radio & weather satellite tracker and passes calculator.
|
||||
* Copyright (C) 2019 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 2 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.di
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.gms.location.FusedLocationProviderClient
|
||||
import com.google.android.gms.location.LocationServices
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
class AndroidModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideSharedPreferences(context: Context): SharedPreferences {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideLocationClient(context: Context): FusedLocationProviderClient {
|
||||
return LocationServices.getFusedLocationProviderClient(context)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* LookingSat. Amateur radio & weather satellite tracker and passes calculator.
|
||||
* Copyright (C) 2019 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 2 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.di
|
||||
|
||||
import android.content.Context
|
||||
import com.rtbishop.look4sat.MainViewModel
|
||||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@Component(modules = [AndroidModule::class, NetworkModule::class, StorageModule::class])
|
||||
interface AppComponent {
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
fun create(@BindsInstance context: Context): AppComponent
|
||||
}
|
||||
|
||||
fun inject(viewModel: MainViewModel)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* LookingSat. Amateur radio & weather satellite tracker and passes calculator.
|
||||
* Copyright (C) 2019 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 2 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.di
|
||||
|
||||
import com.rtbishop.look4sat.network.TransmittersApi
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
class NetworkModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideTransmittersApi(): TransmittersApi {
|
||||
return Retrofit.Builder()
|
||||
.baseUrl("https://db.satnogs.org/api/")
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build()
|
||||
.create(TransmittersApi::class.java)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideOkHttpClient(): OkHttpClient {
|
||||
return OkHttpClient()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* LookingSat. Amateur radio & weather satellite tracker and passes calculator.
|
||||
* Copyright (C) 2019 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 2 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import com.rtbishop.look4sat.storage.TransmittersDao
|
||||
import com.rtbishop.look4sat.storage.TransmittersDb
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
class StorageModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideTransmittersDb(context: Context): TransmittersDb {
|
||||
return Room.databaseBuilder(context, TransmittersDb::class.java, "transmitters")
|
||||
.build()
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideTransmittersDao(db: TransmittersDb): TransmittersDao {
|
||||
return db.transmittersDao()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* LookingSat. Amateur radio & weather satellite tracker and passes calculator.
|
||||
* Copyright (C) 2019 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 2 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.network
|
||||
|
||||
import com.rtbishop.look4sat.repo.Transmitter
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import java.io.InputStream
|
||||
import java.io.SequenceInputStream
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class RemoteDataSource @Inject constructor(
|
||||
private val api: TransmittersApi,
|
||||
private val client: OkHttpClient
|
||||
) {
|
||||
|
||||
fun fetchTleStream(): InputStream {
|
||||
val requestAmateur =
|
||||
Request.Builder().url("https://celestrak.com/NORAD/elements/amateur.txt").build()
|
||||
val requestWeather =
|
||||
Request.Builder().url("https://celestrak.com/NORAD/elements/weather.txt").build()
|
||||
val streamAmateur = client.newCall(requestAmateur).execute().body()?.byteStream()
|
||||
val streamWeather = client.newCall(requestWeather).execute().body()?.byteStream()
|
||||
return SequenceInputStream(streamAmateur, streamWeather)
|
||||
}
|
||||
|
||||
suspend fun fetchTransmittersList(): List<Transmitter> {
|
||||
return api.fetchTransmittersList()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* LookingSat. Amateur radio & weather satellite tracker and passes calculator.
|
||||
* Copyright (C) 2019 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 2 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.network
|
||||
|
||||
import com.rtbishop.look4sat.repo.Transmitter
|
||||
import retrofit2.http.GET
|
||||
|
||||
interface TransmittersApi {
|
||||
|
||||
@GET("transmitters/")
|
||||
suspend fun fetchTransmittersList(): List<Transmitter>
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* LookingSat. Amateur radio & weather satellite tracker and passes calculator.
|
||||
* Copyright (C) 2019 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 2 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.repo
|
||||
|
||||
import com.rtbishop.look4sat.network.RemoteDataSource
|
||||
import com.rtbishop.look4sat.storage.LocalDataSource
|
||||
import java.io.InputStream
|
||||
import javax.inject.Inject
|
||||
|
||||
class Repository @Inject constructor(
|
||||
private val localSource: LocalDataSource,
|
||||
private val remoteSource: RemoteDataSource
|
||||
) {
|
||||
fun fetchTleStream(): InputStream {
|
||||
return remoteSource.fetchTleStream()
|
||||
}
|
||||
|
||||
suspend fun updateTransmittersDatabase() {
|
||||
localSource.insertTransmitters(remoteSource.fetchTransmittersList())
|
||||
}
|
||||
|
||||
suspend fun getTransmittersForSat(id: Int): List<Transmitter> {
|
||||
return localSource.getTransmittersForSat(id)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* LookingSat. Amateur radio & weather satellite tracker and passes calculator.
|
||||
* Copyright (C) 2019 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 2 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.repo
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.github.amsacode.predict4java.PassPredictor
|
||||
import com.github.amsacode.predict4java.SatPassTime
|
||||
import com.github.amsacode.predict4java.TLE
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.parcel.RawValue
|
||||
|
||||
@Parcelize
|
||||
data class SatPass(
|
||||
val tle: TLE,
|
||||
val predictor: @RawValue PassPredictor,
|
||||
val pass: @RawValue SatPassTime
|
||||
) : Parcelable
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* LookingSat. Amateur radio & weather satellite tracker and passes calculator.
|
||||
* Copyright (C) 2019 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 2 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.repo
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Entity(tableName = "transmitters")
|
||||
data class Transmitter(
|
||||
@PrimaryKey @SerializedName("uuid") val uuid: String,
|
||||
@SerializedName("description") val description: String,
|
||||
@SerializedName("alive") val isAlive: Boolean,
|
||||
@SerializedName("uplink_low") val uplinkLow: Long?,
|
||||
@SerializedName("uplink_high") val uplinkHigh: Long?,
|
||||
@SerializedName("downlink_low") val downlinkLow: Long?,
|
||||
@SerializedName("downlink_high") val downlinkHigh: Long?,
|
||||
@SerializedName("mode") val mode: String?,
|
||||
@SerializedName("invert") val isInverted: Boolean,
|
||||
@SerializedName("norad_cat_id") val noradCatId: Int
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* LookingSat. Amateur radio & weather satellite tracker and passes calculator.
|
||||
* Copyright (C) 2019 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 2 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.storage
|
||||
|
||||
import com.rtbishop.look4sat.repo.Transmitter
|
||||
import javax.inject.Inject
|
||||
|
||||
class LocalDataSource @Inject constructor(private val transmittersDao: TransmittersDao) {
|
||||
|
||||
suspend fun insertTransmitters(transmitters: List<Transmitter>) {
|
||||
transmittersDao.insertTransmitters(transmitters)
|
||||
}
|
||||
|
||||
suspend fun getTransmittersForSat(id: Int): List<Transmitter> {
|
||||
return transmittersDao.getTransmittersForSat(id)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* LookingSat. Amateur radio & weather satellite tracker and passes calculator.
|
||||
* Copyright (C) 2019 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 2 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.storage
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.rtbishop.look4sat.repo.Transmitter
|
||||
|
||||
@Dao
|
||||
interface TransmittersDao {
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertTransmitters(transmitters: List<Transmitter>)
|
||||
|
||||
@Query("SELECT * FROM transmitters WHERE isAlive = 1 and noradCatId = :id")
|
||||
suspend fun getTransmittersForSat(id: Int): List<Transmitter>
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* LookingSat. Amateur radio & weather satellite tracker and passes calculator.
|
||||
* Copyright (C) 2019 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 2 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.storage
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import com.rtbishop.look4sat.repo.Transmitter
|
||||
|
||||
@Database(entities = [Transmitter::class], version = 1, exportSchema = false)
|
||||
abstract class TransmittersDb : RoomDatabase() {
|
||||
|
||||
abstract fun transmittersDao(): TransmittersDao
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* LookingSat. Amateur radio & weather satellite tracker and passes calculator.
|
||||
* Copyright (C) 2019 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 2 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.rtbishop.look4sat.R
|
||||
|
||||
class CreditsFragment : Fragment() {
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.fragment_credits, container, false)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* LookingSat. Amateur radio & weather satellite tracker and passes calculator.
|
||||
* Copyright (C) 2019 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 2 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.ui
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.ImageButton
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.ui.AppBarConfiguration
|
||||
import androidx.navigation.ui.navigateUp
|
||||
import androidx.navigation.ui.setupActionBarWithNavController
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import com.rtbishop.look4sat.MainViewModel
|
||||
import com.rtbishop.look4sat.R
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private val permLocationCode = 101
|
||||
private val permLocation = Manifest.permission.ACCESS_FINE_LOCATION
|
||||
private val permGranted = PackageManager.PERMISSION_GRANTED
|
||||
private val githubUrl = "https://github.com/rt-bishop/LookingSat"
|
||||
|
||||
private lateinit var viewModel: MainViewModel
|
||||
private lateinit var timerLayout: ConstraintLayout
|
||||
private lateinit var drawerLayout: DrawerLayout
|
||||
private lateinit var drawerLat: TextView
|
||||
private lateinit var drawerLon: TextView
|
||||
private lateinit var drawerBtnLoc: ImageButton
|
||||
private lateinit var drawerBtnTle: ImageButton
|
||||
private lateinit var drawerBtnTrans: ImageButton
|
||||
private lateinit var drawerBtnGithub: ImageButton
|
||||
private lateinit var drawerBtnExit: ImageButton
|
||||
private lateinit var appBarConfig: AppBarConfiguration
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setTheme(R.style.AppTheme)
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
|
||||
setupComponents()
|
||||
setupObservers()
|
||||
setupDrawer()
|
||||
}
|
||||
|
||||
private fun setupComponents() {
|
||||
val navController = findNavController(R.id.nav_host)
|
||||
val navView: NavigationView = findViewById(R.id.nav_view)
|
||||
val header = navView.getHeaderView(0)
|
||||
val toolbar: Toolbar = findViewById(R.id.toolbar)
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
timerLayout = findViewById(R.id.toolbar_layout_timer)
|
||||
drawerLayout = findViewById(R.id.drawer_layout)
|
||||
drawerLat = header.findViewById(R.id.drawer_lat_value)
|
||||
drawerLon = header.findViewById(R.id.drawer_lon_value)
|
||||
drawerBtnLoc = header.findViewById(R.id.drawer_btn_loc)
|
||||
drawerBtnTle = header.findViewById(R.id.drawer_btn_tle)
|
||||
drawerBtnTrans = header.findViewById(R.id.drawer_btn_trans)
|
||||
drawerBtnGithub = header.findViewById(R.id.drawer_btn_github)
|
||||
drawerBtnExit = header.findViewById(R.id.drawer_btn_exit)
|
||||
|
||||
appBarConfig = AppBarConfiguration(
|
||||
setOf(R.id.nav_sky, R.id.nav_map, R.id.nav_credits, R.id.nav_about),
|
||||
drawerLayout
|
||||
)
|
||||
|
||||
setupActionBarWithNavController(navController, appBarConfig)
|
||||
navView.setupWithNavController(navController)
|
||||
|
||||
navController.addOnDestinationChangedListener { _, destination, _ ->
|
||||
when (destination.id) {
|
||||
R.id.nav_sky -> {
|
||||
this.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
toolbar.visibility = View.VISIBLE
|
||||
timerLayout.visibility = View.VISIBLE
|
||||
}
|
||||
R.id.nav_map -> {
|
||||
this.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
toolbar.visibility = View.GONE
|
||||
}
|
||||
else -> {
|
||||
this.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
toolbar.visibility = View.VISIBLE
|
||||
timerLayout.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupObservers() {
|
||||
viewModel.debugMessage.observe(this, Observer { debug_message ->
|
||||
Toast.makeText(this, debug_message, Toast.LENGTH_SHORT).show()
|
||||
})
|
||||
|
||||
viewModel.gsp.observe(this, Observer { gsp ->
|
||||
drawerLat.text = String.format(getString(R.string.pattern_lat), gsp.latitude)
|
||||
drawerLon.text = String.format(getString(R.string.pattern_lon), gsp.longitude)
|
||||
})
|
||||
}
|
||||
|
||||
private fun setupDrawer() {
|
||||
drawerBtnLoc.setOnClickListener { requestLocationUpdate() }
|
||||
drawerBtnTle.setOnClickListener { viewModel.updateAndSaveTleFile() }
|
||||
drawerBtnTrans.setOnClickListener { viewModel.updateTransmittersDatabase() }
|
||||
drawerBtnGithub.setOnClickListener {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(githubUrl)))
|
||||
}
|
||||
drawerBtnExit.setOnClickListener { finish() }
|
||||
}
|
||||
|
||||
private fun requestLocationUpdate() {
|
||||
if (ContextCompat.checkSelfPermission(this, permLocation) != permGranted) {
|
||||
if (ActivityCompat.shouldShowRequestPermissionRationale(this, permLocation)) {
|
||||
Toast.makeText(this, getString(R.string.no_permissions), Toast.LENGTH_LONG).show()
|
||||
} else {
|
||||
ActivityCompat.requestPermissions(this, arrayOf(permLocation), permLocationCode)
|
||||
}
|
||||
} else {
|
||||
viewModel.getCurrentLocation()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(reqCode: Int, perms: Array<out String>, res: IntArray) {
|
||||
when (reqCode) {
|
||||
permLocationCode -> {
|
||||
if (res.isNotEmpty() && res[0] == permGranted) {
|
||||
viewModel.getCurrentLocation()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
val navController = findNavController(R.id.nav_host)
|
||||
return navController.navigateUp(appBarConfig) || super.onSupportNavigateUp()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* LookingSat. Amateur radio & weather satellite tracker and passes calculator.
|
||||
* Copyright (C) 2019 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 2 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Path
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.amsacode.predict4java.SatPos
|
||||
import com.rtbishop.look4sat.MainViewModel
|
||||
import com.rtbishop.look4sat.R
|
||||
import com.rtbishop.look4sat.repo.SatPass
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
class RadarFragment : Fragment() {
|
||||
|
||||
private val service = Executors.newSingleThreadScheduledExecutor()
|
||||
|
||||
private lateinit var viewModel: MainViewModel
|
||||
private lateinit var satPass: SatPass
|
||||
private lateinit var radarView: RadarView
|
||||
private lateinit var radarSkyFrame: FrameLayout
|
||||
private lateinit var transRecycler: RecyclerView
|
||||
private lateinit var transAdapter: TransAdapter
|
||||
private lateinit var radarAzimuth: TextView
|
||||
private lateinit var radarElevation: TextView
|
||||
private lateinit var radarRange: TextView
|
||||
private lateinit var radarAltitude: TextView
|
||||
private lateinit var transNoFound: TextView
|
||||
private lateinit var mainActivity: MainActivity
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
mainActivity = activity as MainActivity
|
||||
viewModel = ViewModelProvider(mainActivity).get(MainViewModel::class.java)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.fragment_radar, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val delay = viewModel.updateFreq
|
||||
satPass = arguments?.get("satPass") as SatPass
|
||||
mainActivity.supportActionBar?.title = satPass.tle.name
|
||||
|
||||
radarSkyFrame = view.findViewById(R.id.radar_sky_frame)
|
||||
transRecycler = view.findViewById(R.id.radar_recycler)
|
||||
radarAzimuth = view.findViewById(R.id.radar_azimuth)
|
||||
radarElevation = view.findViewById(R.id.radar_elevation)
|
||||
radarRange = view.findViewById(R.id.radar_range)
|
||||
radarAltitude = view.findViewById(R.id.radar_altitude)
|
||||
transNoFound = view.findViewById(R.id.radar_no_trans)
|
||||
|
||||
radarView = RadarView(mainActivity, delay)
|
||||
radarSkyFrame.addView(radarView)
|
||||
service.scheduleAtFixedRate({ radarView.invalidate() }, delay, delay, TimeUnit.MILLISECONDS)
|
||||
|
||||
setupTransRecycler()
|
||||
}
|
||||
|
||||
private fun setupTransRecycler() {
|
||||
transAdapter = TransAdapter()
|
||||
transRecycler.apply {
|
||||
layoutManager = LinearLayoutManager(mainActivity)
|
||||
adapter = transAdapter
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
val transList = viewModel.getTransmittersForSat(satPass.tle.catnum)
|
||||
if (transList.isNotEmpty()) {
|
||||
transAdapter.setList(transList)
|
||||
transAdapter.notifyDataSetChanged()
|
||||
} else {
|
||||
transRecycler.visibility = View.INVISIBLE
|
||||
transNoFound.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class RadarView(context: Context, updateFreq: Long) : View(context) {
|
||||
|
||||
private val radarSize = resources.displayMetrics.widthPixels
|
||||
private val scale = resources.displayMetrics.density
|
||||
private val startTime = satPass.pass.startTime
|
||||
private val endTime = satPass.pass.endTime
|
||||
private val radius = radarSize * 0.45f
|
||||
private val piDiv2 = Math.PI / 2.0
|
||||
private val txtSize = scale * 15
|
||||
private val center = 0f
|
||||
private val delay = updateFreq
|
||||
|
||||
private val radarPaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
color = resources.getColor(R.color.lightOnDark, mainActivity.theme)
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = scale
|
||||
}
|
||||
private val txtPaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
color = resources.getColor(R.color.themeAccent, mainActivity.theme)
|
||||
textSize = txtSize
|
||||
}
|
||||
private val trackPaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
color = resources.getColor(R.color.satTrack, mainActivity.theme)
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = scale
|
||||
}
|
||||
private val satPaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
color = resources.getColor(R.color.themeAccent, mainActivity.theme)
|
||||
style = Paint.Style.FILL
|
||||
}
|
||||
private val path: Path = Path()
|
||||
|
||||
private lateinit var satPos: SatPos
|
||||
private var satPassX = 0f
|
||||
private var satPassY = 0f
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
canvas.translate(radarSize / 2f, radarSize / 2f)
|
||||
setPassText()
|
||||
drawRadarView(canvas)
|
||||
drawRadarText(canvas)
|
||||
drawPassTrajectory(canvas)
|
||||
drawSatellite(canvas)
|
||||
}
|
||||
|
||||
private fun setPassText() {
|
||||
satPos = satPass.predictor.getSatPos(Date())
|
||||
radarAzimuth.text = String.format("Azimuth: %.1f°", rad2Deg(satPos.azimuth))
|
||||
radarElevation.text = String.format("Elevation: %.1f°", rad2Deg(satPos.elevation))
|
||||
radarRange.text = String.format("Range: %.0f km", satPos.range)
|
||||
radarAltitude.text = String.format("Altitude: %.0f km", satPos.altitude)
|
||||
}
|
||||
|
||||
private fun drawRadarView(cvs: Canvas) {
|
||||
cvs.drawLine(center - radius, center, center + radius, center, radarPaint)
|
||||
cvs.drawLine(center, center - radius, center, center + radius, radarPaint)
|
||||
cvs.drawCircle(center, center, radius, radarPaint)
|
||||
cvs.drawCircle(center, center, (radius / 3) * 2, radarPaint)
|
||||
cvs.drawCircle(center, center, radius / 3, radarPaint)
|
||||
}
|
||||
|
||||
private fun drawRadarText(cvs: Canvas) {
|
||||
cvs.drawText("N", center - txtSize / 3, center - radius - scale * 2, txtPaint)
|
||||
cvs.drawText("E", center + radius + scale * 2, center + txtSize / 3, txtPaint)
|
||||
cvs.drawText("S", center - txtSize / 3, center + radius + txtSize, txtPaint)
|
||||
cvs.drawText("W", center - radius - txtSize, center + txtSize / 3, txtPaint)
|
||||
cvs.drawText("0°", center + scale, center - scale * 2, txtPaint)
|
||||
cvs.drawText("30°", center + scale, center - (radius / 3) - scale * 2, txtPaint)
|
||||
cvs.drawText("60°", center + scale, center - ((radius / 3) * 2) - scale * 2, txtPaint)
|
||||
}
|
||||
|
||||
private fun drawPassTrajectory(cvs: Canvas) {
|
||||
while (startTime.before(endTime)) {
|
||||
satPos = satPass.predictor.getSatPos(startTime)
|
||||
satPassX = center + sph2CartX(satPos.azimuth, satPos.elevation, radius.toDouble())
|
||||
satPassY = center - sph2CartY(satPos.azimuth, satPos.elevation, radius.toDouble())
|
||||
if (startTime.compareTo(satPass.pass.startTime) == 0) {
|
||||
path.moveTo(satPassX, satPassY)
|
||||
} else {
|
||||
path.lineTo(satPassX, satPassY)
|
||||
}
|
||||
startTime.time += delay
|
||||
}
|
||||
cvs.drawPath(path, trackPaint)
|
||||
}
|
||||
|
||||
private fun drawSatellite(cvs: Canvas) {
|
||||
satPos = satPass.predictor.getSatPos(Date())
|
||||
if (satPos.elevation > 0) {
|
||||
cvs.drawCircle(
|
||||
center + sph2CartX(satPos.azimuth, satPos.elevation, radius.toDouble()),
|
||||
center - sph2CartY(satPos.azimuth, satPos.elevation, radius.toDouble()),
|
||||
txtSize / 3, satPaint
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sph2CartX(azimuth: Double, elevation: Double, r: Double): Float {
|
||||
val radius = r * (piDiv2 - elevation) / piDiv2
|
||||
return (radius * cos(piDiv2 - azimuth)).toFloat()
|
||||
}
|
||||
|
||||
private fun sph2CartY(azimuth: Double, elevation: Double, r: Double): Float {
|
||||
val radius = r * (piDiv2 - elevation) / piDiv2
|
||||
return (radius * sin(piDiv2 - azimuth)).toFloat()
|
||||
}
|
||||
|
||||
private fun rad2Deg(value: Double): Double {
|
||||
return value * 180 / Math.PI
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* LookingSat. Amateur radio & weather satellite tracker and passes calculator.
|
||||
* Copyright (C) 2019 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 2 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.ui
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.rtbishop.look4sat.R
|
||||
import com.rtbishop.look4sat.repo.SatPass
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class SatPassAdapter : RecyclerView.Adapter<SatPassAdapter.PassHolder>() {
|
||||
|
||||
inner class PassHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
||||
|
||||
private var satPassList = emptyList<SatPass>()
|
||||
|
||||
fun setList(list: List<SatPass>) {
|
||||
satPassList = list
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return satPassList.size
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PassHolder {
|
||||
val itemView = LayoutInflater
|
||||
.from(parent.context)
|
||||
.inflate(R.layout.sat_pass_card, parent, false)
|
||||
return PassHolder(itemView)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: PassHolder, position: Int) {
|
||||
val satPass = satPassList[position]
|
||||
|
||||
val satName = holder.itemView.findViewById<TextView>(R.id.card_sat_name)
|
||||
val maxEl = holder.itemView.findViewById<TextView>(R.id.card_max_el)
|
||||
val passVector = holder.itemView.findViewById<TextView>(R.id.card_pass_vector)
|
||||
val passStart = holder.itemView.findViewById<TextView>(R.id.card_pass_start)
|
||||
val passEnd = holder.itemView.findViewById<TextView>(R.id.card_pass_end)
|
||||
|
||||
val aosTime =
|
||||
SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(satPass.pass.startTime)
|
||||
val losTime =
|
||||
SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(satPass.pass.endTime)
|
||||
satName.text = satPass.tle.name
|
||||
maxEl.text = String.format("%.1f°", satPass.pass.maxEl)
|
||||
passVector.text =
|
||||
String.format("Az: %2d° -> %2d°", satPass.pass.aosAzimuth, satPass.pass.losAzimuth)
|
||||
passStart.text = String.format("AOS - %s", aosTime)
|
||||
passEnd.text = String.format("LOS - %s", losTime)
|
||||
|
||||
holder.itemView.setOnClickListener {
|
||||
val bundle = bundleOf("satPass" to satPass)
|
||||
holder.itemView.findNavController().navigate(R.id.action_nav_sky_to_nav_radar, bundle)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* LookingSat. Amateur radio & weather satellite tracker and passes calculator.
|
||||
* Copyright (C) 2019 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 2 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.ui
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.rtbishop.look4sat.MainViewModel
|
||||
import com.rtbishop.look4sat.R
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompat(),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private lateinit var viewModel: MainViewModel
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preference, rootKey)
|
||||
|
||||
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
|
||||
|
||||
val provider = EditTextPreference.SimpleSummaryProvider.getInstance()
|
||||
val latPref = findPreference<EditTextPreference>(context!!.getText(R.string.key_lat))
|
||||
val lonPref = findPreference<EditTextPreference>(context!!.getText(R.string.key_lon))
|
||||
val altPref = findPreference<EditTextPreference>(context!!.getText(R.string.key_alt))
|
||||
|
||||
latPref?.summaryProvider = provider
|
||||
lonPref?.summaryProvider = provider
|
||||
altPref?.summaryProvider = provider
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||
if (key == context!!.getText(R.string.key_lat) ||
|
||||
key == context!!.getText(R.string.key_lon) ||
|
||||
key == context!!.getText(R.string.key_alt)
|
||||
) viewModel.updateGsp()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
preferenceManager.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* LookingSat. Amateur radio & weather satellite tracker and passes calculator.
|
||||
* Copyright (C) 2019 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 2 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.CountDownTimer
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.amsacode.predict4java.TLE
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.rtbishop.look4sat.MainViewModel
|
||||
import com.rtbishop.look4sat.R
|
||||
import com.rtbishop.look4sat.repo.SatPass
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class SkyFragment : Fragment() {
|
||||
|
||||
private lateinit var viewModel: MainViewModel
|
||||
private lateinit var recViewCurrent: RecyclerView
|
||||
private lateinit var recViewFuture: RecyclerView
|
||||
private lateinit var recAdapterCurrent: RecyclerView.Adapter<*>
|
||||
private lateinit var recAdapterFuture: SatPassAdapter
|
||||
private lateinit var timeToAos: TextView
|
||||
private lateinit var btnRefresh: ImageButton
|
||||
private lateinit var progressBar: ProgressBar
|
||||
private lateinit var fab: FloatingActionButton
|
||||
private lateinit var aosTimer: CountDownTimer
|
||||
private lateinit var satPassList: List<SatPass>
|
||||
private var isTimerSet: Boolean = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
viewModel = ViewModelProvider(activity as MainActivity).get(MainViewModel::class.java)
|
||||
recAdapterFuture = SatPassAdapter()
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.fragment_sky, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
setupViews(view)
|
||||
}
|
||||
|
||||
private fun setupViews(view: View) {
|
||||
timeToAos = (activity as MainActivity).findViewById(R.id.toolbar_time_to_aos)
|
||||
btnRefresh = (activity as MainActivity).findViewById(R.id.toolbar_btn_refresh)
|
||||
progressBar = view.findViewById(R.id.sky_progressbar)
|
||||
recViewFuture = view.findViewById(R.id.sky_recycler_future)
|
||||
fab = view.findViewById(R.id.sky_fab)
|
||||
|
||||
satPassList = viewModel.satPassList
|
||||
recViewFuture.apply {
|
||||
layoutManager = LinearLayoutManager(activity as MainActivity)
|
||||
adapter = recAdapterFuture
|
||||
}
|
||||
recAdapterFuture.setList(satPassList)
|
||||
recAdapterFuture.notifyDataSetChanged()
|
||||
if (satPassList.isEmpty()) {
|
||||
resetTimer()
|
||||
}
|
||||
|
||||
btnRefresh.setOnClickListener { calculatePasses() }
|
||||
fab.setOnClickListener {
|
||||
showSelectSatDialog(viewModel.tleMainList, viewModel.selectionList)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSelectSatDialog(tleMainList: List<TLE>, selectionList: MutableList<Int>) {
|
||||
val tleNameArray = arrayOfNulls<String>(tleMainList.size).apply {
|
||||
tleMainList.withIndex().forEach { (position, tle) -> this[position] = tle.name }
|
||||
}
|
||||
val tleCheckedArray = BooleanArray(tleMainList.size).apply {
|
||||
selectionList.forEach { this[it] = true }
|
||||
}
|
||||
val builder = AlertDialog.Builder(activity as MainActivity)
|
||||
builder.setTitle(getString(R.string.dialog_select_sat))
|
||||
.setMultiChoiceItems(tleNameArray, tleCheckedArray) { _, which, isChecked ->
|
||||
if (isChecked) selectionList.add(which)
|
||||
else if (selectionList.contains(which)) selectionList.remove(which)
|
||||
}
|
||||
.setPositiveButton(getString(R.string.btn_ok)) { _, _ ->
|
||||
viewModel.updateAndSaveSelectionList(selectionList)
|
||||
calculatePasses()
|
||||
}
|
||||
.setNegativeButton(getString(R.string.btn_cancel)) { dialog, _ ->
|
||||
dialog.cancel()
|
||||
}
|
||||
.setNeutralButton(getString(R.string.btn_clear)) { _, _ ->
|
||||
selectionList.clear()
|
||||
viewModel.updateAndSaveSelectionList(selectionList)
|
||||
calculatePasses()
|
||||
}
|
||||
.create()
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun calculatePasses() {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
recViewFuture.visibility = View.INVISIBLE
|
||||
progressBar.visibility = View.VISIBLE
|
||||
progressBar.isIndeterminate = true
|
||||
viewModel.getPasses()
|
||||
satPassList = viewModel.satPassList
|
||||
recAdapterFuture.setList(satPassList)
|
||||
recAdapterFuture.notifyDataSetChanged()
|
||||
progressBar.isIndeterminate = false
|
||||
progressBar.visibility = View.INVISIBLE
|
||||
recViewFuture.visibility = View.VISIBLE
|
||||
if (satPassList.isNotEmpty()) setTimer(satPassList.first().pass.startTime.time)
|
||||
else resetTimer()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setTimer(passTime: Long) {
|
||||
if (isTimerSet) resetTimer()
|
||||
val totalMillis = passTime.minus(System.currentTimeMillis())
|
||||
aosTimer = object : CountDownTimer(totalMillis, 1000) {
|
||||
override fun onFinish() {
|
||||
Toast.makeText(activity, "Time is up!", Toast.LENGTH_SHORT).show()
|
||||
this.cancel()
|
||||
}
|
||||
|
||||
override fun onTick(millisUntilFinished: Long) {
|
||||
timeToAos.text = String.format(
|
||||
getString(R.string.pattern_aos),
|
||||
TimeUnit.MILLISECONDS.toHours(millisUntilFinished) % 60,
|
||||
TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished) % 60,
|
||||
TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished) % 60
|
||||
)
|
||||
}
|
||||
}
|
||||
aosTimer.start()
|
||||
isTimerSet = true
|
||||
}
|
||||
|
||||
private fun resetTimer() {
|
||||
if (isTimerSet) {
|
||||
aosTimer.cancel()
|
||||
isTimerSet = false
|
||||
}
|
||||
timeToAos.text = String.format(getString(R.string.pattern_aos), 0, 0, 0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* LookingSat. Amateur radio & weather satellite tracker and passes calculator.
|
||||
* Copyright (C) 2019 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 2 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.ui
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.rtbishop.look4sat.R
|
||||
import com.rtbishop.look4sat.repo.Transmitter
|
||||
|
||||
class TransAdapter : RecyclerView.Adapter<TransAdapter.TransHolder>() {
|
||||
|
||||
inner class TransHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
||||
|
||||
private var transList = emptyList<Transmitter>()
|
||||
|
||||
fun setList(list: List<Transmitter>) {
|
||||
transList = list
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return transList.size
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransHolder {
|
||||
val itemView = LayoutInflater
|
||||
.from(parent.context)
|
||||
.inflate(R.layout.card_transmitter, parent, false)
|
||||
return TransHolder(itemView)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: TransHolder, position: Int) {
|
||||
val context = holder.itemView.context
|
||||
val freqDivider = 1000000f
|
||||
val transmitter = transList[position]
|
||||
val description = holder.itemView.findViewById<TextView>(R.id.trans_description)
|
||||
val downlink = holder.itemView.findViewById<TextView>(R.id.trans_downlink)
|
||||
val uplink = holder.itemView.findViewById<TextView>(R.id.trans_uplink)
|
||||
val mode = holder.itemView.findViewById<TextView>(R.id.trans_mode)
|
||||
val inverted = holder.itemView.findViewById<TextView>(R.id.trans_inverted)
|
||||
|
||||
description.text = transmitter.description
|
||||
|
||||
if (transmitter.downlinkLow != null && transmitter.downlinkHigh == null) {
|
||||
downlink.text = String.format(
|
||||
context.getString(R.string.pattern_trans_downlink_low),
|
||||
transmitter.downlinkLow / freqDivider
|
||||
)
|
||||
} else if (transmitter.downlinkLow != null && transmitter.downlinkHigh != null) {
|
||||
downlink.text = String.format(
|
||||
context.getString(R.string.pattern_trans_downlink_lowHigh),
|
||||
transmitter.downlinkLow / freqDivider,
|
||||
transmitter.downlinkHigh / freqDivider
|
||||
)
|
||||
} else {
|
||||
downlink.text = context.getString(R.string.no_downlink)
|
||||
}
|
||||
|
||||
if (transmitter.uplinkLow != null && transmitter.uplinkHigh == null) {
|
||||
uplink.text = String.format(
|
||||
context.getString(R.string.pattern_trans_uplink_low),
|
||||
transmitter.uplinkLow / freqDivider
|
||||
)
|
||||
} else if (transmitter.uplinkLow != null && transmitter.uplinkHigh != null) {
|
||||
uplink.text = String.format(
|
||||
context.getString(R.string.pattern_trans_uplink_lowHigh),
|
||||
transmitter.uplinkLow / freqDivider,
|
||||
transmitter.uplinkHigh / freqDivider
|
||||
)
|
||||
} else {
|
||||
uplink.text = context.getString(R.string.no_uplink)
|
||||
}
|
||||
|
||||
if (transmitter.mode != null) {
|
||||
mode.text =
|
||||
String.format(context.getString(R.string.pattern_trans_mode), transmitter.mode)
|
||||
} else {
|
||||
mode.text = context.getString(R.string.no_mode)
|
||||
}
|
||||
if (transmitter.isInverted) {
|
||||
inverted.text = String.format(
|
||||
context.getString(R.string.pattern_trans_inverted),
|
||||
context.getString(R.string.btn_yes)
|
||||
)
|
||||
} else {
|
||||
inverted.text = String.format(
|
||||
context.getString(R.string.pattern_trans_inverted),
|
||||
context.getString(R.string.btn_no)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* LookingSat. Amateur radio & weather satellite tracker and passes calculator.
|
||||
* Copyright (C) 2019 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 2 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, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.github.amsacode.predict4java.PassPredictor
|
||||
import com.github.amsacode.predict4java.Position
|
||||
import com.github.amsacode.predict4java.SatPos
|
||||
import com.github.amsacode.predict4java.TLE
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.rtbishop.look4sat.MainViewModel
|
||||
import com.rtbishop.look4sat.R
|
||||
import java.util.*
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.abs
|
||||
|
||||
class WorldMapFragment : Fragment() {
|
||||
|
||||
private val service = Executors.newSingleThreadScheduledExecutor()
|
||||
|
||||
private lateinit var viewModel: MainViewModel
|
||||
private lateinit var trackView: TrackView
|
||||
private lateinit var mapFrame: FrameLayout
|
||||
private lateinit var fab: FloatingActionButton
|
||||
private lateinit var predictor: PassPredictor
|
||||
private lateinit var mainActivity: MainActivity
|
||||
private lateinit var selectedSat: TLE
|
||||
private lateinit var tleMainList: List<TLE>
|
||||
private lateinit var selectionList: MutableList<Int>
|
||||
private var checkedItem = 0
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
mainActivity = activity as MainActivity
|
||||
viewModel = ViewModelProvider(mainActivity).get(MainViewModel::class.java)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.fragment_worldmap, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val delay = viewModel.updateFreq
|
||||
tleMainList = viewModel.tleMainList
|
||||
selectionList = viewModel.selectionList
|
||||
mapFrame = view.findViewById(R.id.worldmap_frame)
|
||||
fab = view.findViewById(R.id.worldmap_fab)
|
||||
|
||||
if (tleMainList.isNotEmpty() && selectionList.isNotEmpty()) {
|
||||
fab.setOnClickListener { showSelectSatDialog(tleMainList, selectionList) }
|
||||
selectedSat = tleMainList[selectionList[0]]
|
||||
predictor = PassPredictor(selectedSat, viewModel.gsp.value)
|
||||
trackView = TrackView(mainActivity)
|
||||
mapFrame.addView(trackView)
|
||||
service.scheduleAtFixedRate(
|
||||
{ trackView.invalidate() },
|
||||
delay,
|
||||
delay,
|
||||
TimeUnit.MILLISECONDS
|
||||
)
|
||||
} else {
|
||||
fab.setOnClickListener {
|
||||
Toast.makeText(
|
||||
mainActivity,
|
||||
getString(R.string.no_selected_sat),
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSelectSatDialog(tleMainList: List<TLE>, selectionList: MutableList<Int>) {
|
||||
val tleNameArray = arrayOfNulls<String>(selectionList.size).apply {
|
||||
selectionList.withIndex().forEach { (index, selection) ->
|
||||
this[index] = tleMainList[selection].name
|
||||
}
|
||||
}
|
||||
|
||||
val builder = AlertDialog.Builder(mainActivity)
|
||||
builder.setTitle(getString(R.string.dialog_show_track))
|
||||
.setSingleChoiceItems(tleNameArray, checkedItem) { dialog, which ->
|
||||
checkedItem = which
|
||||
selectedSat = tleMainList[selectionList[which]]
|
||||
predictor = PassPredictor(selectedSat, viewModel.gsp.value)
|
||||
trackView.invalidate()
|
||||
dialog.dismiss()
|
||||
}
|
||||
.create()
|
||||
.show()
|
||||
}
|
||||
|
||||
inner class TrackView(context: Context) : View(context) {
|
||||
private val gsp = viewModel.gsp.value!!
|
||||
private val scale = resources.displayMetrics.density
|
||||
private val groundTrackPaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
color = resources.getColor(R.color.satTrack, mainActivity.theme)
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = scale
|
||||
}
|
||||
private val footprintPaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
color = resources.getColor(R.color.satFootprint, mainActivity.theme)
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = scale
|
||||
}
|
||||
private val txtPaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
color = resources.getColor(R.color.satFootprint, mainActivity.theme)
|
||||
style = Paint.Style.FILL
|
||||
strokeWidth = scale
|
||||
textSize = 16f
|
||||
setShadowLayer(2f, 2f, 2f, Color.BLACK)
|
||||
}
|
||||
private val outlinePaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
color = Color.BLACK
|
||||
style = Paint.Style.FILL_AND_STROKE
|
||||
strokeWidth = scale
|
||||
textSize = 16f
|
||||
}
|
||||
private val rect = Rect()
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
canvas.translate(width / 2f, height / 2f)
|
||||
val degLon = width / 360f
|
||||
val degLat = height / 180f
|
||||
drawHomeLoc(canvas, degLon, degLat)
|
||||
|
||||
val currentTime = getDateFor(System.currentTimeMillis())
|
||||
val orbitalPeriod = (24 * 60 / selectedSat.meanmo).toInt()
|
||||
val satPosList = predictor.getPositions(currentTime, 60, 0, orbitalPeriod * 3)
|
||||
drawGroundTrack(canvas, degLon, degLat, satPosList)
|
||||
|
||||
selectionList.forEach {
|
||||
drawSat(it, currentTime, canvas, degLon, degLat)
|
||||
}
|
||||
}
|
||||
|
||||
private fun drawHomeLoc(cvs: Canvas, degLon: Float, degLat: Float) {
|
||||
val lon = gsp.longitude.toFloat()
|
||||
val lat = gsp.latitude.toFloat() * -1
|
||||
val cx = lon * degLon
|
||||
val cy = lat * degLat
|
||||
cvs.drawCircle(cx, cy, scale * 2, txtPaint)
|
||||
cvs.drawText(
|
||||
context.getString(R.string.map_gsp),
|
||||
cx - txtPaint.textSize,
|
||||
cy - txtPaint.textSize,
|
||||
txtPaint
|
||||
)
|
||||
}
|
||||
|
||||
private fun drawGroundTrack(cvs: Canvas, degLon: Float, degLat: Float, list: List<SatPos>) {
|
||||
val path = Path()
|
||||
var lon: Float
|
||||
var lat: Float
|
||||
var lastLon = 181f
|
||||
|
||||
list.withIndex().forEach { (index, satPos) ->
|
||||
lon = rad2Deg(satPos.longitude).toFloat()
|
||||
lat = rad2Deg(satPos.latitude).toFloat() * -1
|
||||
|
||||
if (lon > 180f) lon -= 360f
|
||||
|
||||
lon *= degLon
|
||||
lat *= degLat
|
||||
|
||||
if (index == 0 || abs(lon - lastLon) > 180) path.moveTo(lon, lat)
|
||||
else path.lineTo(lon, lat)
|
||||
|
||||
lastLon = lon
|
||||
}
|
||||
cvs.drawPath(path, groundTrackPaint)
|
||||
}
|
||||
|
||||
private fun drawSat(index: Int, date: Date, canvas: Canvas, degLon: Float, degLat: Float) {
|
||||
val tle = tleMainList[index]
|
||||
val predictor = PassPredictor(tle, gsp)
|
||||
val satPosNow = predictor.getSatPos(date)
|
||||
val footprintPosList = satPosNow.rangeCircle
|
||||
drawFootprint(canvas, degLon, degLat, footprintPosList)
|
||||
drawName(canvas, degLon, degLat, satPosNow, tle.name)
|
||||
}
|
||||
|
||||
private fun drawFootprint(cvs: Canvas, degLon: Float, degLat: Float, list: List<Position>) {
|
||||
val path = Path()
|
||||
var lon: Float
|
||||
var lat: Float
|
||||
var lastLon = 181f
|
||||
|
||||
list.withIndex().forEach { (index, position) ->
|
||||
lon = position.lon.toFloat()
|
||||
lat = position.lat.toFloat() * -1
|
||||
|
||||
if (lon > 180f) lon -= 360f
|
||||
|
||||
lon *= degLon
|
||||
lat *= degLat
|
||||
|
||||
if (index == 0 || abs(lon - lastLon) > 180) path.moveTo(lon, lat)
|
||||
else path.lineTo(lon, lat)
|
||||
|
||||
lastLon = lon
|
||||
}
|
||||
cvs.drawPath(path, footprintPaint)
|
||||
}
|
||||
|
||||
private fun drawName(cvs: Canvas, degLon: Float, degLat: Float, pos: SatPos, name: String) {
|
||||
var lon = rad2Deg(pos.longitude).toFloat()
|
||||
val lat = rad2Deg(pos.latitude).toFloat() * -1
|
||||
if (lon > 180f) lon -= 360f
|
||||
val cx = lon * degLon
|
||||
val cy = lat * degLat
|
||||
cvs.drawCircle(cx, cy, scale * 2, txtPaint)
|
||||
txtPaint.getTextBounds(name, 0, name.length, rect)
|
||||
cvs.drawText(name, cx - rect.width() / 2, cy - txtPaint.textSize, outlinePaint)
|
||||
cvs.drawText(name, cx - rect.width() / 2, cy - txtPaint.textSize, txtPaint)
|
||||
}
|
||||
|
||||
private fun getDateFor(value: Long): Date {
|
||||
return Date(value)
|
||||
}
|
||||
|
||||
private fun rad2Deg(value: Double): Double {
|
||||
return value * 180 / Math.PI
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user