diff --git a/app/src/main/java/com/rtbishop/look4sat/repository/SatelliteRepo.kt b/app/src/main/java/com/rtbishop/look4sat/repository/SatelliteRepo.kt index 793a5fc7..fd596f88 100644 --- a/app/src/main/java/com/rtbishop/look4sat/repository/SatelliteRepo.kt +++ b/app/src/main/java/com/rtbishop/look4sat/repository/SatelliteRepo.kt @@ -21,36 +21,31 @@ import android.content.ContentResolver import android.net.Uri import com.github.amsacode.predict4java.Satellite import com.github.amsacode.predict4java.TLE -import com.rtbishop.look4sat.data.* -import com.rtbishop.look4sat.di.ExternalScope +import com.rtbishop.look4sat.data.SatEntry +import com.rtbishop.look4sat.data.SatTrans +import com.rtbishop.look4sat.data.TleSource import com.rtbishop.look4sat.di.IoDispatcher import com.rtbishop.look4sat.repository.localData.SatelliteDao import com.rtbishop.look4sat.repository.remoteData.SatelliteService -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.collect -import timber.log.Timber +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.io.InputStream import java.util.zip.ZipInputStream import javax.inject.Inject import javax.inject.Singleton -import kotlin.system.measureTimeMillis @Singleton class SatelliteRepo @Inject constructor( private val resolver: ContentResolver, private val satelliteDao: SatelliteDao, private val satelliteService: SatelliteService, - @ExternalScope private val externalScope: CoroutineScope, @IoDispatcher private val ioDispatcher: CoroutineDispatcher ) { - private val _satData = MutableStateFlow>>(Result.InProgress) - val satData: Flow>> = _satData - init { - loadEntriesFromDb() - } + val satDataFlow = satelliteDao.getSatItems() fun getTransmittersForSat(catNum: Int): Flow> { return satelliteDao.getTransmittersForSat(catNum) @@ -65,49 +60,32 @@ class SatelliteRepo @Inject constructor( } suspend fun updateSatDataFromFile(uri: Uri) { - _satData.value = Result.InProgress - externalScope.launch { - runCatching { - resolver.openInputStream(uri)?.use { stream -> - val entries = importEntriesFromStreams(listOf(stream)) - insertEntriesAndRestoreSelection(entries) - } - }.onFailure { throwable: Throwable -> - Timber.d("$throwable") - _satData.value = Result.Error(throwable) + runCatching { + resolver.openInputStream(uri)?.use { stream -> + val entries = importEntriesFromStreams(listOf(stream)) + insertEntriesAndRestoreSelection(entries) } - loadEntriesFromDb() + }.onFailure { throwable: Throwable -> + throw throwable } } suspend fun updateSatDataFromWeb(sources: List) { - _satData.value = Result.InProgress - externalScope.launch { - val updateTimeMillis = measureTimeMillis { - runCatching { - val streams = externalScope.async { getStreamsForSources(sources) } - val transmitters = externalScope.async { satelliteService.fetchTransmitters() } - val entries = importEntriesFromStreams(streams.await()) - satelliteDao.insertTransmitters(transmitters.await()) - insertEntriesAndRestoreSelection(entries) - }.onFailure { throwable: Throwable -> - Timber.d("$throwable") - _satData.value = Result.Error(throwable) - } - loadEntriesFromDb() - } - Timber.d("Update from Web took $updateTimeMillis ms") + coroutineScope { + launch { updateEntriesFromSources(sources) } + launch { updateTransmitters() } } } - private fun loadEntriesFromDb() { - _satData.value = Result.InProgress - externalScope.launch { - satelliteDao.getSatItems().collect { satItems -> - delay(250) - _satData.value = Result.Success(satItems) - } - } + private suspend fun updateEntriesFromSources(sources: List) { + val streams = getStreamsForSources(sources) + val entries = importEntriesFromStreams(streams) + insertEntriesAndRestoreSelection(entries) + } + + private suspend fun updateTransmitters() { + val transmitters = satelliteService.fetchTransmitters() + satelliteDao.insertTransmitters(transmitters) } private suspend fun getStreamsForSources(sources: List): List { @@ -115,10 +93,7 @@ class SatelliteRepo @Inject constructor( sources.forEach { tleSource -> satelliteService.fetchFile(tleSource.url).body()?.byteStream()?.let { inputStream -> if (tleSource.url.contains(".zip", true)) { - // Handle zip stream - val zipInputStream = ZipInputStream(inputStream) - val zipEntry = zipInputStream.nextEntry - if (zipEntry != null && !zipEntry.isDirectory) streams.add(zipInputStream) + streams.add(getZipStream(inputStream)) } else { streams.add(inputStream) } @@ -127,6 +102,17 @@ class SatelliteRepo @Inject constructor( return streams } + private suspend fun getZipStream(inputStream: InputStream): InputStream { + var stream: InputStream + withContext(ioDispatcher) { + ZipInputStream(inputStream).apply { + nextEntry + stream = this + } + } + return stream + } + private suspend fun importEntriesFromStreams(streams: List): List { val importedEntries = mutableListOf() withContext(ioDispatcher) { diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/SharedViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/ui/SharedViewModel.kt index 86904f8e..fa3c076a 100644 --- a/app/src/main/java/com/rtbishop/look4sat/ui/SharedViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/ui/SharedViewModel.kt @@ -31,9 +31,13 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import timber.log.Timber import java.util.* import javax.inject.Inject +import kotlin.system.measureTimeMillis @HiltViewModel class SharedViewModel @Inject constructor( @@ -41,19 +45,57 @@ class SharedViewModel @Inject constructor( private val satelliteRepo: SatelliteRepo, ) : ViewModel() { + private val _satData = MutableStateFlow>>(Result.InProgress) private val _passes = MutableStateFlow>>(Result.InProgress) val passes: LiveData>> = _passes.asLiveData() - - val satData = satelliteRepo.satData.asLiveData() + val satData = _satData.asLiveData(viewModelScope.coroutineContext) init { if (prefsRepo.isFirstLaunch()) { - updateDefaultSourcesAndEntries() + updateSatDataFromSources() prefsRepo.setFirstLaunchDone() } + loadSatData() calculatePasses() } + private fun loadSatData() { + viewModelScope.launch { + delay(8) + satelliteRepo.satDataFlow.map { Result.Success(it) }.collect { + _satData.value = it + } + } + } + + fun updateSatDataFromFile(uri: Uri) { + _satData.value = Result.InProgress + viewModelScope.launch { + try { + satelliteRepo.updateSatDataFromFile(uri) + } catch (exception: Exception) { + _satData.value = Result.Error(exception) + } + loadSatData() + } + } + + fun updateSatDataFromSources(sources: List = prefsRepo.loadTleSources()) { + _satData.value = Result.InProgress + viewModelScope.launch { + val updateMillis = measureTimeMillis { + try { + prefsRepo.saveTleSources(sources) + satelliteRepo.updateSatDataFromWeb(sources) + } catch (exception: Exception) { + _satData.value = Result.Error(exception) + } + loadSatData() + } + Timber.d("Update from WEB took $updateMillis ms") + } + } + fun getAppTimer() = liveData { while (true) { emit(System.currentTimeMillis()) @@ -76,19 +118,6 @@ class SharedViewModel @Inject constructor( } } - fun updateEntriesFromFile(uri: Uri) { - viewModelScope.launch { - satelliteRepo.updateSatDataFromFile(uri) - } - } - - fun updateEntriesFromSources(sources: List = prefsRepo.loadTleSources()) { - viewModelScope.launch { - prefsRepo.saveTleSources(sources) - satelliteRepo.updateSatDataFromWeb(sources) - } - } - fun updateItemsSelection(items: List) { _passes.value = Result.InProgress viewModelScope.launch { @@ -117,8 +146,4 @@ class SharedViewModel @Inject constructor( passes.sortBy { it.pass.startTime } return passes } - - private fun updateDefaultSourcesAndEntries() { - updateEntriesFromSources() - } } diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/entriesScreen/EntriesFragment.kt b/app/src/main/java/com/rtbishop/look4sat/ui/entriesScreen/EntriesFragment.kt index b043b289..89cdae8b 100644 --- a/app/src/main/java/com/rtbishop/look4sat/ui/entriesScreen/EntriesFragment.kt +++ b/app/src/main/java/com/rtbishop/look4sat/ui/entriesScreen/EntriesFragment.kt @@ -33,6 +33,7 @@ import com.rtbishop.look4sat.ui.SharedViewModel import com.rtbishop.look4sat.utility.RecyclerDivider import com.rtbishop.look4sat.utility.navigateSafe import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.ExperimentalCoroutinesApi @AndroidEntryPoint class EntriesFragment : Fragment(R.layout.fragment_entries) { @@ -40,11 +41,12 @@ class EntriesFragment : Fragment(R.layout.fragment_entries) { private val viewModel: SharedViewModel by activityViewModels() private val filePicker = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> - uri?.let { viewModel.updateEntriesFromFile(uri) } + uri?.let { viewModel.updateSatDataFromFile(uri) } } private var binding: FragmentEntriesBinding? = null private var entriesAdapter: EntriesAdapter? = null + @ExperimentalCoroutinesApi override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentEntriesBinding.bind(view) @@ -70,6 +72,7 @@ class EntriesFragment : Fragment(R.layout.fragment_entries) { } } + @ExperimentalCoroutinesApi private fun setupObservers() { viewModel.satData.observe(viewLifecycleOwner, { result -> when (result) { diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/sourcesDialog/SourcesDialog.kt b/app/src/main/java/com/rtbishop/look4sat/ui/sourcesDialog/SourcesDialog.kt index 95630081..f87c39c0 100644 --- a/app/src/main/java/com/rtbishop/look4sat/ui/sourcesDialog/SourcesDialog.kt +++ b/app/src/main/java/com/rtbishop/look4sat/ui/sourcesDialog/SourcesDialog.kt @@ -55,7 +55,7 @@ class SourcesDialog : AppCompatDialogFragment() { sourcesAdapter.addSource() } tleSourcesBtnPos.setOnClickListener { - viewModel.updateEntriesFromSources(sourcesAdapter.getSources()) + viewModel.updateSatDataFromSources(sourcesAdapter.getSources()) dismiss() } tleSourcesBtnNeg.setOnClickListener { dismiss() }