Replaced Google Hilt with manual DI

This commit is contained in:
Arty Bishop committed 2025-12-27 15:01:33 +00:00
1 parent 622d4e3632
commit 4c13cc98a1
17 files changed
+234 -151

No files matched your search

+8 -23
View File
@@ -1,12 +1,11 @@
plugins { plugins {
id "com.android.application" id "com.android.application"
id "com.google.devtools.ksp"
id "org.jetbrains.kotlin.android" id "org.jetbrains.kotlin.android"
id "org.jetbrains.kotlin.kapt"
id "dagger.hilt.android.plugin"
} }
android { android {
namespace 'com.rtbishop.look4sat' namespace "com.rtbishop.look4sat"
compileSdk 33 compileSdk 33
defaultConfig { defaultConfig {
@@ -14,20 +13,9 @@ android {
minSdk 21 minSdk 21
targetSdk 33 targetSdk 33
versionCode 312 versionCode 312
versionName '3.1.2' versionName "3.1.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
kapt {
arguments {
arg("room.schemaLocation", "$projectDir/schemas")
arg("room.incremental", "true")
arg("room.expandProjection", "true")
}
correctErrorTypes true
}
} }
buildFeatures { buildFeatures {
compose true compose true
@@ -40,14 +28,14 @@ android {
} }
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_11 sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_17
} }
composeOptions { composeOptions {
kotlinCompilerExtensionVersion compose_compiler_version kotlinCompilerExtensionVersion compose_compiler_version
} }
kotlinOptions { kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString() jvmTarget = JavaVersion.VERSION_17.toString()
} }
packagingOptions { packagingOptions {
resources { resources {
@@ -61,7 +49,7 @@ android {
} }
kotlin { kotlin {
jvmToolchain(11) jvmToolchain(17)
} }
dependencies { dependencies {
@@ -70,9 +58,7 @@ dependencies {
implementation "androidx.core:core-splashscreen:$splashscreen_version" implementation "androidx.core:core-splashscreen:$splashscreen_version"
implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version" implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version" ksp "androidx.room:room-compiler:$room_version"
implementation "com.google.dagger:hilt-android:$hilt_android_version"
kapt "com.google.dagger:hilt-compiler:$hilt_android_version"
// Compose // Compose
implementation "androidx.activity:activity-compose:$activity_compose_version" implementation "androidx.activity:activity-compose:$activity_compose_version"
implementation "androidx.compose.animation:animation:$compose_version" implementation "androidx.compose.animation:animation:$compose_version"
@@ -81,7 +67,6 @@ dependencies {
implementation "androidx.compose.runtime:runtime:$compose_version" implementation "androidx.compose.runtime:runtime:$compose_version"
implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation "androidx.hilt:hilt-navigation-compose:$hilt_compose_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"
implementation "androidx.navigation:navigation-compose:$navigation_version" implementation "androidx.navigation:navigation-compose:$navigation_version"
// Utility // Utility
@@ -25,9 +25,7 @@ import androidx.activity.compose.setContent
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.rtbishop.look4sat.presentation.MainScreen import com.rtbishop.look4sat.presentation.MainScreen
import com.rtbishop.look4sat.presentation.MainTheme import com.rtbishop.look4sat.presentation.MainTheme
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -18,25 +18,18 @@
package com.rtbishop.look4sat package com.rtbishop.look4sat
import android.app.Application import android.app.Application
import com.rtbishop.look4sat.domain.IDataRepository
import com.rtbishop.look4sat.domain.ISettingsRepository
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber import timber.log.Timber
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.Date
import javax.inject.Inject import java.util.Locale
@HiltAndroidApp
class MainApplication : Application() { class MainApplication : Application() {
@Inject lateinit var container: MainContainer
lateinit var dataRepository: IDataRepository
@Inject
lateinit var settingsRepository: ISettingsRepository
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
container = MainContainer(this)
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree()) Timber.plant(Timber.DebugTree())
} }
@@ -44,12 +37,13 @@ class MainApplication : Application() {
} }
private fun checkAutoUpdate() { private fun checkAutoUpdate() {
val settingsRepository = container.settingsRepository
if (settingsRepository.isUpdateEnabled()) { if (settingsRepository.isUpdateEnabled()) {
val timeDelta = System.currentTimeMillis() - settingsRepository.getLastUpdateTime() val timeDelta = System.currentTimeMillis() - settingsRepository.getLastUpdateTime()
if (timeDelta > 172800000) { // 48 hours in ms if (timeDelta > 172800000) { // 48 hours in ms
val sdf = SimpleDateFormat("d MMM yyyy - HH:mm:ss", Locale.getDefault()) val sdf = SimpleDateFormat("d MMM yyyy - HH:mm:ss", Locale.getDefault())
Timber.d("Started periodic data update on ${sdf.format(Date())}") Timber.d("Started periodic data update on ${sdf.format(Date())}")
dataRepository.updateFromWeb() container.dataRepository.updateFromWeb()
} }
} }
} }
@@ -1,20 +1,3 @@
/*
* 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 package com.rtbishop.look4sat
import android.bluetooth.BluetoothManager import android.bluetooth.BluetoothManager
@@ -26,45 +9,43 @@ import androidx.room.Room
import com.rtbishop.look4sat.data.DataParser import com.rtbishop.look4sat.data.DataParser
import com.rtbishop.look4sat.data.DataRepository import com.rtbishop.look4sat.data.DataRepository
import com.rtbishop.look4sat.data.SatelliteRepository import com.rtbishop.look4sat.data.SatelliteRepository
import com.rtbishop.look4sat.domain.* import com.rtbishop.look4sat.domain.IDataRepository
import com.rtbishop.look4sat.framework.* import com.rtbishop.look4sat.domain.ISatelliteRepository
import com.rtbishop.look4sat.framework.data.* import com.rtbishop.look4sat.domain.ISensorRepository
import com.rtbishop.look4sat.domain.ISettingsRepository
import com.rtbishop.look4sat.framework.BluetoothReporter
import com.rtbishop.look4sat.framework.NetworkReporter
import com.rtbishop.look4sat.framework.SensorRepository
import com.rtbishop.look4sat.framework.SettingsRepository
import com.rtbishop.look4sat.framework.data.MIGRATION_1_2
import com.rtbishop.look4sat.framework.data.MainDatabase
import com.rtbishop.look4sat.framework.data.local.EntriesStorage import com.rtbishop.look4sat.framework.data.local.EntriesStorage
import com.rtbishop.look4sat.framework.data.local.RadiosStorage import com.rtbishop.look4sat.framework.data.local.RadiosStorage
import com.rtbishop.look4sat.framework.data.remote.FileSource import com.rtbishop.look4sat.framework.data.remote.FileSource
import com.rtbishop.look4sat.framework.data.remote.NetworkSource import com.rtbishop.look4sat.framework.data.remote.NetworkSource
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import javax.inject.Singleton
@Module class MainContainer(private val context: Context) {
@InstallIn(SingletonComponent::class)
object MainModule {
@Provides val bluetoothReporter = provideBluetoothReporter()
@Singleton val networkReporter = provideNetworkReporter()
fun provideBluetoothReporter(@ApplicationContext context: Context): BluetoothReporter { val settingsRepository = provideSettingsRepository()
val dataRepository = provideDataRepository(settingsRepository)
val sensorRepository = provideSensorRepository()
val satelliteRepository = provideSatelliteRepository()
private fun provideBluetoothReporter(): BluetoothReporter {
val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
return BluetoothReporter(manager, CoroutineScope(Dispatchers.IO)) return BluetoothReporter(manager, CoroutineScope(Dispatchers.IO))
} }
@Provides private fun provideNetworkReporter(): NetworkReporter {
@Singleton
fun provideNetworkReporter(): NetworkReporter {
return NetworkReporter(CoroutineScope(Dispatchers.IO)) return NetworkReporter(CoroutineScope(Dispatchers.IO))
} }
@Provides private fun provideDataRepository(settings: ISettingsRepository): IDataRepository {
@Singleton
fun provideDataRepository(
@ApplicationContext context: Context, settings: ISettingsRepository
): IDataRepository {
val db = Room.databaseBuilder(context, MainDatabase::class.java, "Look4SatDb") val db = Room.databaseBuilder(context, MainDatabase::class.java, "Look4SatDb")
.addMigrations(MIGRATION_1_2).fallbackToDestructiveMigration().build() .addMigrations(MIGRATION_1_2).fallbackToDestructiveMigration().build()
val parser = DataParser(Dispatchers.Default) val parser = DataParser(Dispatchers.Default)
@@ -76,23 +57,17 @@ object MainModule {
return DataRepository(parser, fileSource, entries, radios, remoteSource, scope, settings) return DataRepository(parser, fileSource, entries, radios, remoteSource, scope, settings)
} }
@Provides private fun provideSensorRepository(): ISensorRepository {
@Singleton
fun provideSensorRepository(@ApplicationContext context: Context): ISensorRepository {
val manager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager val manager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensor = manager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR) val sensor = manager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
return SensorRepository(manager, sensor) return SensorRepository(manager, sensor)
} }
@Provides private fun provideSatelliteRepository(): ISatelliteRepository {
@Singleton
fun provideSatelliteRepository(): ISatelliteRepository {
return SatelliteRepository(Dispatchers.Default) return SatelliteRepository(Dispatchers.Default)
} }
@Provides private fun provideSettingsRepository(): ISettingsRepository {
@Singleton
fun provideSettingsRepository(@ApplicationContext context: Context): ISettingsRepository {
val manager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager val manager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val preferences = context.getSharedPreferences("default", Context.MODE_PRIVATE) val preferences = context.getSharedPreferences("default", Context.MODE_PRIVATE)
return SettingsRepository(manager, preferences) return SettingsRepository(manager, preferences)
@@ -26,7 +26,7 @@ import com.rtbishop.look4sat.framework.data.dao.RadiosDao
import com.rtbishop.look4sat.framework.model.SatEntry import com.rtbishop.look4sat.framework.model.SatEntry
import com.rtbishop.look4sat.framework.model.SatRadio import com.rtbishop.look4sat.framework.model.SatRadio
@Database(entities = [SatEntry::class, SatRadio::class], version = 2, exportSchema = true) @Database(entities = [SatEntry::class, SatRadio::class], version = 2)
abstract class MainDatabase : RoomDatabase() { abstract class MainDatabase : RoomDatabase() {
abstract fun entriesDao(): EntriesDao abstract fun entriesDao(): EntriesDao
@@ -21,7 +21,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.model.DataState import com.rtbishop.look4sat.model.DataState
import com.rtbishop.look4sat.model.SatItem import com.rtbishop.look4sat.model.SatItem
@@ -31,7 +31,8 @@ import com.rtbishop.look4sat.presentation.dialogs.TypesDialog
import com.rtbishop.look4sat.presentation.onClick import com.rtbishop.look4sat.presentation.onClick
@Composable @Composable
fun EntriesScreen(navToPasses: () -> Unit, viewModel: EntriesViewModel = hiltViewModel()) { fun EntriesScreen(navToPasses: () -> Unit) {
val viewModel = viewModel(EntriesViewModel::class.java, factory = EntriesViewModel.Factory)
val state = viewModel.satData.collectAsState(initial = DataState.Loading) val state = viewModel.satData.collectAsState(initial = DataState.Loading)
val showDialog = rememberSaveable { mutableStateOf(false) } val showDialog = rememberSaveable { mutableStateOf(false) }
@@ -227,6 +228,7 @@ fun EntriesCard(state: DataState<List<SatItem>>, onSelected: (List<Int>, Boolean
} }
} }
} }
else -> CardLoadingIndicator() else -> CardLoadingIndicator()
} }
} }
@@ -18,23 +18,24 @@
package com.rtbishop.look4sat.presentation.entries package com.rtbishop.look4sat.presentation.entries
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.rtbishop.look4sat.MainApplication
import com.rtbishop.look4sat.domain.IDataRepository import com.rtbishop.look4sat.domain.IDataRepository
import com.rtbishop.look4sat.domain.ISatelliteRepository import com.rtbishop.look4sat.domain.ISatelliteRepository
import com.rtbishop.look4sat.domain.ISettingsRepository import com.rtbishop.look4sat.domain.ISettingsRepository
import com.rtbishop.look4sat.model.DataState import com.rtbishop.look4sat.model.DataState
import com.rtbishop.look4sat.model.SatItem import com.rtbishop.look4sat.model.SatItem
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.* import java.util.*
import javax.inject.Inject
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@HiltViewModel class EntriesViewModel(
class EntriesViewModel @Inject constructor(
private val dataRepository: IDataRepository, private val dataRepository: IDataRepository,
private val satelliteRepository: ISatelliteRepository, private val satelliteRepository: ISatelliteRepository,
private val settingsRepository: ISettingsRepository private val settingsRepository: ISettingsRepository
@@ -113,4 +114,18 @@ class EntriesViewModel @Inject constructor(
} }
} }
} }
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
val applicationKey = ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY
initializer {
val container = (this[applicationKey] as MainApplication).container
EntriesViewModel(
container.dataRepository,
container.satelliteRepository,
container.settingsRepository
)
}
}
}
} }
@@ -1,7 +1,13 @@
package com.rtbishop.look4sat.presentation.map package com.rtbishop.look4sat.presentation.map
import android.content.Context import android.content.Context
import android.graphics.* import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@@ -19,9 +25,9 @@ import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.domain.Satellite import com.rtbishop.look4sat.domain.Satellite
import com.rtbishop.look4sat.model.GeoPos import com.rtbishop.look4sat.model.GeoPos
@@ -59,7 +65,8 @@ private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
private val labelRect = Rect() private val labelRect = Rect()
@Composable @Composable
fun MapScreen(viewModel: MapViewModel = hiltViewModel()) { fun MapScreen() {
val viewModel = viewModel(MapViewModel::class.java, factory = MapViewModel.Factory)
val context = LocalContext.current val context = LocalContext.current
val preferences = context.getSharedPreferences("default", Context.MODE_PRIVATE) val preferences = context.getSharedPreferences("default", Context.MODE_PRIVATE)
Configuration.getInstance().load(context, preferences) Configuration.getInstance().load(context, preferences)
@@ -18,7 +18,11 @@
package com.rtbishop.look4sat.presentation.map package com.rtbishop.look4sat.presentation.map
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.rtbishop.look4sat.MainApplication
import com.rtbishop.look4sat.domain.IDataRepository import com.rtbishop.look4sat.domain.IDataRepository
import com.rtbishop.look4sat.domain.ISatelliteRepository import com.rtbishop.look4sat.domain.ISatelliteRepository
import com.rtbishop.look4sat.domain.ISettingsRepository import com.rtbishop.look4sat.domain.ISettingsRepository
@@ -28,19 +32,20 @@ import com.rtbishop.look4sat.model.SatPos
import com.rtbishop.look4sat.utility.QthConverter import com.rtbishop.look4sat.utility.QthConverter
import com.rtbishop.look4sat.utility.toDegrees import com.rtbishop.look4sat.utility.toDegrees
import com.rtbishop.look4sat.utility.toTimerString import com.rtbishop.look4sat.utility.toTimerString
import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job
import kotlinx.coroutines.* import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import java.util.* import kotlinx.coroutines.isActive
import javax.inject.Inject import kotlinx.coroutines.launch
import java.util.Date
import kotlin.collections.set import kotlin.collections.set
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@HiltViewModel class MapViewModel(
class MapViewModel @Inject constructor(
private val dataRepository: IDataRepository, private val dataRepository: IDataRepository,
private val satelliteRepository: ISatelliteRepository, private val satelliteRepository: ISatelliteRepository,
private val settingsRepository: ISettingsRepository private val settingsRepository: ISettingsRepository
@@ -221,4 +226,18 @@ class MapViewModel @Inject constructor(
private fun clip(currentValue: Double, minValue: Double, maxValue: Double): Double { private fun clip(currentValue: Double, minValue: Double, maxValue: Double): Double {
return min(max(currentValue, minValue), maxValue) return min(max(currentValue, minValue), maxValue)
} }
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
val applicationKey = ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY
initializer {
val container = (this[applicationKey] as MainApplication).container
MapViewModel(
container.dataRepository,
container.satelliteRepository,
container.settingsRepository
)
}
}
}
} }
@@ -20,7 +20,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.* import androidx.compose.ui.unit.*
import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.domain.NearEarthSatellite import com.rtbishop.look4sat.domain.NearEarthSatellite
import com.rtbishop.look4sat.model.DataState import com.rtbishop.look4sat.model.DataState
@@ -38,7 +38,8 @@ import java.util.*
private val sdf = SimpleDateFormat("HH:mm:ss", Locale.ENGLISH) private val sdf = SimpleDateFormat("HH:mm:ss", Locale.ENGLISH)
@Composable @Composable
fun PassesScreen(navToRadar: (Int, Long) -> Unit, viewModel: PassesViewModel = hiltViewModel()) { fun PassesScreen(navToRadar: (Int, Long) -> Unit) {
val viewModel = viewModel(PassesViewModel::class.java, factory = PassesViewModel.Factory)
val state = viewModel.passes.collectAsState(initial = null) val state = viewModel.passes.collectAsState(initial = null)
val timerText = viewModel.timerText.collectAsState(initial = null) val timerText = viewModel.timerText.collectAsState(initial = null)
val isRefreshing = state.value is DataState.Loading val isRefreshing = state.value is DataState.Loading
@@ -102,7 +103,7 @@ private fun PassPreview() {
) )
val satellite = NearEarthSatellite(data) val satellite = NearEarthSatellite(data)
val pass = SatPass(1L, 25.0, 10L, 75.0, 850, 45.0, satellite, 0.5f) val pass = SatPass(1L, 25.0, 10L, 75.0, 850, 45.0, satellite, 0.5f)
MainTheme { Pass(pass = pass, { _,_ -> }) } MainTheme { Pass(pass = pass, { _, _ -> }) }
} }
@Composable @Composable
@@ -18,21 +18,26 @@
package com.rtbishop.look4sat.presentation.passes package com.rtbishop.look4sat.presentation.passes
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.rtbishop.look4sat.MainApplication
import com.rtbishop.look4sat.domain.IDataRepository import com.rtbishop.look4sat.domain.IDataRepository
import com.rtbishop.look4sat.domain.ISatelliteRepository import com.rtbishop.look4sat.domain.ISatelliteRepository
import com.rtbishop.look4sat.domain.ISettingsRepository import com.rtbishop.look4sat.domain.ISettingsRepository
import com.rtbishop.look4sat.model.DataState import com.rtbishop.look4sat.model.DataState
import com.rtbishop.look4sat.model.SatPass import com.rtbishop.look4sat.model.SatPass
import com.rtbishop.look4sat.utility.toTimerString import com.rtbishop.look4sat.utility.toTimerString
import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job
import kotlinx.coroutines.* import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
@HiltViewModel class PassesViewModel(
class PassesViewModel @Inject constructor(
private val dataRepository: IDataRepository, private val dataRepository: IDataRepository,
private val satelliteRepository: ISatelliteRepository, private val satelliteRepository: ISatelliteRepository,
private val settingsRepository: ISettingsRepository private val settingsRepository: ISettingsRepository
@@ -113,4 +118,18 @@ class PassesViewModel @Inject constructor(
) )
} }
} }
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
val applicationKey = ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY
initializer {
val container = (this[applicationKey] as MainApplication).container
PassesViewModel(
container.dataRepository,
container.satelliteRepository,
container.settingsRepository
)
}
}
}
} }
@@ -1,10 +1,21 @@
package com.rtbishop.look4sat.presentation.radar package com.rtbishop.look4sat.presentation.radar
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.* import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -16,12 +27,13 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.model.SatRadio import com.rtbishop.look4sat.model.SatRadio
@Composable @Composable
fun RadarScreen(viewModel: RadarViewModel = hiltViewModel()) { fun RadarScreen() {
val viewModel = viewModel(RadarViewModel::class.java, factory = RadarViewModel.Factory)
val currentPass = viewModel.getPass().collectAsState(null) val currentPass = viewModel.getPass().collectAsState(null)
Column(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { Column(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
@@ -55,9 +67,11 @@ fun RadarScreen(viewModel: RadarViewModel = hiltViewModel()) {
) )
} }
} }
ElevatedCard(modifier = Modifier ElevatedCard(
.fillMaxSize() modifier = Modifier
.weight(1f)) { .fillMaxSize()
.weight(1f)
) {
viewModel.radarData.value?.let { data -> viewModel.radarData.value?.let { data ->
RadarViewCompose( RadarViewCompose(
item = data.satPos, item = data.satPos,
@@ -66,9 +80,11 @@ fun RadarScreen(viewModel: RadarViewModel = hiltViewModel()) {
) )
} }
} }
ElevatedCard(modifier = Modifier ElevatedCard(
.fillMaxSize() modifier = Modifier
.weight(1f)) { .fillMaxSize()
.weight(1f)
) {
TransmittersList(transmitters = viewModel.transmitters.value) TransmittersList(transmitters = viewModel.transmitters.value)
} }
} }
@@ -93,7 +109,10 @@ private fun TransmitterItem(radio: SatRadio) {
.background(MaterialTheme.colorScheme.surface) .background(MaterialTheme.colorScheme.surface)
.padding(6.dp) .padding(6.dp)
) { ) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Icon( Icon(
painter = painterResource(id = R.drawable.ic_next), painter = painterResource(id = R.drawable.ic_next),
contentDescription = null, modifier = Modifier.rotate(180f) contentDescription = null, modifier = Modifier.rotate(180f)
@@ -22,8 +22,17 @@ import android.util.Log
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.rtbishop.look4sat.domain.* import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.rtbishop.look4sat.MainApplication
import com.rtbishop.look4sat.domain.IDataRepository
import com.rtbishop.look4sat.domain.ISatelliteRepository
import com.rtbishop.look4sat.domain.ISensorRepository
import com.rtbishop.look4sat.domain.ISettingsRepository
import com.rtbishop.look4sat.domain.Satellite
import com.rtbishop.look4sat.framework.BluetoothReporter import com.rtbishop.look4sat.framework.BluetoothReporter
import com.rtbishop.look4sat.framework.NetworkReporter import com.rtbishop.look4sat.framework.NetworkReporter
import com.rtbishop.look4sat.model.GeoPos import com.rtbishop.look4sat.model.GeoPos
@@ -32,15 +41,12 @@ import com.rtbishop.look4sat.model.SatPos
import com.rtbishop.look4sat.model.SatRadio import com.rtbishop.look4sat.model.SatRadio
import com.rtbishop.look4sat.utility.round import com.rtbishop.look4sat.utility.round
import com.rtbishop.look4sat.utility.toDegrees import com.rtbishop.look4sat.utility.toDegrees
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel class RadarViewModel(
class RadarViewModel @Inject constructor(
private val bluetoothReporter: BluetoothReporter, private val bluetoothReporter: BluetoothReporter,
private val networkReporter: NetworkReporter, private val networkReporter: NetworkReporter,
private val savedStateHandle: SavedStateHandle, private val savedStateHandle: SavedStateHandle,
@@ -149,4 +155,23 @@ class RadarViewModel @Inject constructor(
transmitters.value = list transmitters.value = list
} }
} }
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
val applicationKey = ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY
initializer {
val savedStateHandle = createSavedStateHandle()
val container = (this[applicationKey] as MainApplication).container
RadarViewModel(
container.bluetoothReporter,
container.networkReporter,
savedStateHandle,
container.dataRepository,
container.satelliteRepository,
container.sensorRepository,
container.settingsRepository
)
}
}
}
} }
@@ -4,9 +4,22 @@ import android.Manifest
import android.os.Build import android.os.Build
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.* import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.Icon
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -21,7 +34,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.rtbishop.look4sat.BuildConfig import com.rtbishop.look4sat.BuildConfig
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.model.GeoPos import com.rtbishop.look4sat.model.GeoPos
@@ -32,7 +45,8 @@ import com.rtbishop.look4sat.presentation.dialogs.PositionDialog
import com.rtbishop.look4sat.presentation.gotoUrl import com.rtbishop.look4sat.presentation.gotoUrl
import com.rtbishop.look4sat.presentation.showToast import com.rtbishop.look4sat.presentation.showToast
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.Date
import java.util.Locale
private const val POLICY_URL = "https://sites.google.com/view/look4sat-privacy-policy/home" private const val POLICY_URL = "https://sites.google.com/view/look4sat-privacy-policy/home"
private const val LICENSE_URL = "https://www.gnu.org/licenses/gpl-3.0.html" private const val LICENSE_URL = "https://www.gnu.org/licenses/gpl-3.0.html"
@@ -41,7 +55,8 @@ private const val DONATE_URL = "https://ko-fi.com/rt_bishop"
private const val FDROID_URL = "https://f-droid.org/en/packages/com.rtbishop.look4sat/" private const val FDROID_URL = "https://f-droid.org/en/packages/com.rtbishop.look4sat/"
@Composable @Composable
fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) { fun SettingsScreen() {
val viewModel = viewModel(SettingsViewModel::class.java, factory = SettingsViewModel.Factory)
val context = LocalContext.current val context = LocalContext.current
// Permissions setup // Permissions setup
@@ -19,20 +19,20 @@ package com.rtbishop.look4sat.presentation.settings
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.rtbishop.look4sat.MainApplication
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.domain.IDataRepository import com.rtbishop.look4sat.domain.IDataRepository
import com.rtbishop.look4sat.domain.ISettingsRepository import com.rtbishop.look4sat.domain.ISettingsRepository
import com.rtbishop.look4sat.model.DataState import com.rtbishop.look4sat.model.DataState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel class SettingsViewModel(
class SettingsViewModel @Inject constructor( private val dataRepository: IDataRepository, private val settingsRepository: ISettingsRepository
private val dataRepository: IDataRepository,
private val settingsRepository: ISettingsRepository
) : ViewModel() { ) : ViewModel() {
private val defaultLocationSettings = LocationSettings(false, private val defaultLocationSettings = LocationSettings(false,
@@ -157,4 +157,14 @@ class SettingsViewModel @Inject constructor(
// fun setBTDeviceName(value: String) = settings.setBTDeviceName(value) // fun setBTDeviceName(value: String) = settings.setBTDeviceName(value)
// fun getBTDeviceAddr(): String = settings.getBTDeviceAddr() // fun getBTDeviceAddr(): String = settings.getBTDeviceAddr()
// fun setBTDeviceAddr(value: String) = settings.setBTDeviceAddr(value) // fun setBTDeviceAddr(value: String) = settings.setBTDeviceAddr(value)
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
val applicationKey = ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY
initializer {
val container = (this[applicationKey] as MainApplication).container
SettingsViewModel(container.dataRepository, container.settingsRepository)
}
}
}
} }
+10 -11
View File
@@ -1,9 +1,9 @@
buildscript { buildscript {
ext { ext {
application_version = '7.4.2' gradle_plugin_version = '7.4.2'
library_version = '7.1.2' library_plugin_version = '7.1.2'
ksp_version = '1.8.10-1.0.9'
kotlin_android_version = '1.8.10' kotlin_android_version = '1.8.10'
hilt_android_version = '2.45'
// Android // Android
splashscreen_version = '1.0.0' splashscreen_version = '1.0.0'
room_version = '2.5.0' room_version = '2.5.0'
@@ -12,28 +12,27 @@ buildscript {
compose_version = '1.3.3' compose_version = '1.3.3'
compose_compiler_version = '1.4.3' compose_compiler_version = '1.4.3'
material3_version = '1.0.1' material3_version = '1.0.1'
hilt_compose_version = '1.0.0' lifecycle_version = '2.6.0'
lifecycle_version = '2.5.1'
navigation_version = '2.5.3' navigation_version = '2.5.3'
// Utility // Utility
timber_version = '5.0.1' timber_version = '5.0.1'
osmdroid_version = '6.1.14' osmdroid_version = '6.1.14'
json_version = '20220924' json_version = '20230227'
leakcanary_version = '2.10' leakcanary_version = '2.10'
// Test // Test
junit_version = '4.13.2' junit_version = '4.13.2'
mockito_version = '5.1.1' mockito_version = '5.2.0'
coroutines_test_version = '1.6.4' coroutines_test_version = '1.6.4'
} }
} }
plugins { plugins {
id "com.android.application" version "$application_version" apply false id "com.android.application" version "$gradle_plugin_version" apply false
id "com.android.library" version "$library_version" apply false id "com.android.library" version "$library_plugin_version" apply false
id "com.google.devtools.ksp" version "$ksp_version" apply false
id "org.jetbrains.kotlin.android" version "$kotlin_android_version" apply false id "org.jetbrains.kotlin.android" version "$kotlin_android_version" apply false
id "com.google.dagger.hilt.android" version "$hilt_android_version" apply false
} }
task clean(type: Delete) { tasks.register('clean', Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }
+3 -3
View File
@@ -1,6 +1,6 @@
#Fri Feb 17 17:52:48 GMT 2023 #Mon Mar 20 10:37:41 GMT 2023
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists