Further classes and dependencies cleanup
|
@ -6,9 +6,8 @@ plugins {
|
|||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion '33.0.0'
|
||||
namespace 'com.rtbishop.look4sat'
|
||||
compileSdk 33
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.rtbishop.look4sat"
|
||||
|
@ -21,7 +20,6 @@ android {
|
|||
vectorDrawables {
|
||||
useSupportLibrary true
|
||||
}
|
||||
|
||||
kapt {
|
||||
arguments {
|
||||
arg("room.schemaLocation", "$projectDir/schemas")
|
||||
|
@ -31,6 +29,9 @@ android {
|
|||
correctErrorTypes true
|
||||
}
|
||||
}
|
||||
buildFeatures {
|
||||
compose true
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
|
@ -38,29 +39,25 @@ android {
|
|||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
buildFeatures {
|
||||
compose true
|
||||
viewBinding true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion compose_compiler_version
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion compose_compiler_version
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||
}
|
||||
testOptions {
|
||||
animationsDisabled true
|
||||
unitTests.includeAndroidResources true
|
||||
}
|
||||
packagingOptions {
|
||||
resources {
|
||||
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
||||
}
|
||||
}
|
||||
testOptions {
|
||||
animationsDisabled true
|
||||
unitTests.includeAndroidResources true
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
|
@ -69,43 +66,34 @@ kotlin {
|
|||
|
||||
dependencies {
|
||||
implementation project(":base")
|
||||
implementation "androidx.core:core-ktx:$core_ktx_version"
|
||||
implementation "androidx.core:core-splashscreen:$core_splashscreen_version"
|
||||
implementation "androidx.constraintlayout:constraintlayout:$constraint_version"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
|
||||
// Android
|
||||
implementation "androidx.core:core-splashscreen:$splashscreen_version"
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
implementation "androidx.room:room-ktx:$room_version"
|
||||
kapt "androidx.room:room-compiler:$room_version"
|
||||
implementation "com.google.dagger:hilt-android:$hilt_version"
|
||||
kapt "com.google.dagger:hilt-compiler:$hilt_version"
|
||||
implementation "com.jakewharton.timber:timber:$timber_version"
|
||||
implementation "org.osmdroid:osmdroid-android:$osmdroid_version"
|
||||
|
||||
implementation "com.google.dagger:hilt-android:$hilt_android_version"
|
||||
kapt "com.google.dagger:hilt-compiler:$hilt_android_version"
|
||||
// Compose
|
||||
implementation "androidx.activity:activity-compose:$activity_compose_version"
|
||||
implementation "androidx.compose.animation:animation:$compose_version"
|
||||
implementation "androidx.compose.compiler:compiler:$compose_compiler_version"
|
||||
implementation "androidx.compose.material:material:$material_version"
|
||||
implementation "androidx.compose.material3:material3:$material3_version"
|
||||
implementation "androidx.compose.runtime:runtime:$compose_version"
|
||||
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
|
||||
implementation "androidx.compose.ui:ui:$compose_version"
|
||||
implementation "androidx.compose.ui:ui-tooling:$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.navigation:navigation-compose:$navigation_version"
|
||||
|
||||
// Utility
|
||||
implementation "com.jakewharton.timber:timber:$timber_version"
|
||||
implementation "org.osmdroid:osmdroid-android:$osmdroid_version"
|
||||
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
|
||||
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
|
||||
debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakcanary_version"
|
||||
|
||||
// Test
|
||||
testImplementation "junit:junit:$junit_version"
|
||||
testImplementation "org.mockito:mockito-core:$mockito_version"
|
||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_test_version"
|
||||
|
||||
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
|
||||
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_test_version"
|
||||
androidTestImplementation "org.mockito:mockito-android:$mockito_version"
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
|
@ -12,13 +13,16 @@
|
|||
|
||||
<application
|
||||
android:name=".presentation.MainApplication"
|
||||
android:allowBackup="false"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Look4Sat.Main">
|
||||
android:theme="@style/Theme.Look4Sat.Main"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".presentation.MainActivity"
|
||||
android:exported="true"
|
||||
|
@ -30,4 +34,4 @@
|
|||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
|
Przed Szerokość: | Wysokość: | Rozmiar: 25 KiB |
|
@ -22,8 +22,6 @@ import android.content.Context
|
|||
import android.content.pm.ActivityInfo
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.os.SystemClock
|
||||
import android.view.View
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
|
@ -47,15 +45,3 @@ class MainActivity : ComponentActivity() {
|
|||
super.attachBaseContext(newBase)
|
||||
}
|
||||
}
|
||||
|
||||
fun View.clickWithDebounce(debounceTime: Long = 875L, action: () -> Unit) {
|
||||
this.setOnClickListener(object : View.OnClickListener {
|
||||
private var lastClickTime: Long = 0
|
||||
|
||||
override fun onClick(v: View) {
|
||||
if (SystemClock.elapsedRealtime() - lastClickTime < debounceTime) return
|
||||
else action()
|
||||
lastClickTime = SystemClock.elapsedRealtime()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import androidx.compose.material.icons.outlined.PlayArrow
|
|||
import androidx.compose.material.icons.outlined.Search
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
|
@ -40,7 +40,7 @@ fun EntriesScreen(
|
|||
viewModel: EntriesViewModel = hiltViewModel(),
|
||||
passesViewModel: PassesViewModel = hiltViewModel()
|
||||
) {
|
||||
val state = viewModel.satData.observeAsState(DataState.Loading)
|
||||
val state = viewModel.satData.collectAsState(initial = DataState.Loading)
|
||||
|
||||
val showDialog = rememberSaveable { mutableStateOf(false) }
|
||||
val toggleDialog = { showDialog.value = showDialog.value.not() }
|
||||
|
|
|
@ -17,50 +17,59 @@
|
|||
*/
|
||||
package com.rtbishop.look4sat.presentation.entriesScreen
|
||||
|
||||
import androidx.lifecycle.*
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.rtbishop.look4sat.domain.IDataRepository
|
||||
import com.rtbishop.look4sat.domain.ISettingsManager
|
||||
import com.rtbishop.look4sat.domain.model.DataState
|
||||
import com.rtbishop.look4sat.domain.model.SatItem
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@HiltViewModel
|
||||
class EntriesViewModel @Inject constructor(
|
||||
private val repository: IDataRepository,
|
||||
private val settings: ISettingsManager
|
||||
private val repository: IDataRepository, private val settings: ISettingsManager
|
||||
) : ViewModel() {
|
||||
|
||||
private val satType = MutableLiveData("All")
|
||||
private val satType = MutableStateFlow("All")
|
||||
// private val transModes = MutableLiveData(settings.loadModesSelection())
|
||||
private val currentQuery = MutableLiveData(String())
|
||||
private val itemsFromRepo = liveData {
|
||||
delay(250)
|
||||
emit(loadEntriesWithSelection())
|
||||
} as MutableLiveData
|
||||
private val itemsWithType = satType.switchMap { type ->
|
||||
private val currentQuery = MutableStateFlow(String())
|
||||
private val itemsFromRepo = MutableStateFlow<List<SatItem>>(emptyList())
|
||||
private val itemsWithType = satType.flatMapLatest { type ->
|
||||
itemsFromRepo.map { items -> filterByType(items, type) }
|
||||
}
|
||||
// private val itemsWithModes = transModes.switchMap { modes ->
|
||||
// itemsWithType.map { items -> filterByModes(items, modes) }
|
||||
// }
|
||||
private val itemsWithQuery = currentQuery.switchMap { query ->
|
||||
private val itemsWithQuery = currentQuery.flatMapLatest { query ->
|
||||
itemsWithType.map { items -> filterByQuery(items, query) }
|
||||
}
|
||||
val satData = itemsWithQuery.map { items -> DataState.Success(items) }
|
||||
val satTypes: List<String> = settings.sourcesMap.keys.sorted()
|
||||
|
||||
fun getSatType() = satType.value ?: "All"
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
delay(250)
|
||||
itemsFromRepo.value = loadEntriesWithSelection()
|
||||
}
|
||||
}
|
||||
|
||||
fun getSatType() = satType.value
|
||||
|
||||
fun setSatType(type: String) {
|
||||
satType.value = type
|
||||
}
|
||||
|
||||
fun selectCurrentItems(selectAll: Boolean) {
|
||||
itemsWithQuery.value?.let { itemsWithQuery ->
|
||||
updateSelection(itemsWithQuery.map { item -> item.catnum }, selectAll)
|
||||
viewModelScope.launch {
|
||||
val lastValue = itemsWithQuery.last()
|
||||
updateSelection(lastValue.map { item -> item.catnum }, selectAll)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,13 +83,13 @@ class EntriesViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun saveSelection(): List<Int> {
|
||||
return itemsFromRepo.value?.let { itemsAll ->
|
||||
return itemsFromRepo.value.let { itemsAll ->
|
||||
itemsAll.filter { item -> item.isSelected }.map { item -> item.catnum }
|
||||
} ?: emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateSelection(catNums: List<Int>, isSelected: Boolean) {
|
||||
itemsFromRepo.value?.let { itemsAll ->
|
||||
itemsFromRepo.value.let { itemsAll ->
|
||||
val copiedList = itemsAll.map { item -> item.copy() }
|
||||
catNums.forEach { catnum ->
|
||||
copiedList.find { item -> item.catnum == catnum }?.isSelected = isSelected
|
||||
|
|
|
@ -11,7 +11,7 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
@ -63,10 +63,10 @@ fun MapScreen(viewModel: MapViewModel = hiltViewModel()) {
|
|||
val context = LocalContext.current
|
||||
Configuration.getInstance().load(context, viewModel.getPreferences())
|
||||
viewModel.selectDefaultSatellite(-1)
|
||||
val stationPos = viewModel.stationPos.observeAsState()
|
||||
val positions = viewModel.positions.observeAsState()
|
||||
val satTrack = viewModel.track.observeAsState()
|
||||
val footprint = viewModel.footprint.observeAsState()
|
||||
val stationPos = viewModel.stationPos.collectAsState(initial = null)
|
||||
val positions = viewModel.positions.collectAsState(initial = null)
|
||||
val satTrack = viewModel.track.collectAsState(initial = null)
|
||||
val footprint = viewModel.footprint.collectAsState(initial = null)
|
||||
val positionClick = { satellite: Satellite -> viewModel.selectSatellite(satellite) }
|
||||
Timber.d("MapScreen recomposition")
|
||||
Column(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
|
@ -195,8 +195,7 @@ private fun MapView(modifier: Modifier = Modifier, update: ((map: MapView) -> Un
|
|||
|
||||
@Composable
|
||||
private fun rememberMapViewWithLifecycle(context: Context = LocalContext.current): MapView {
|
||||
// add clipToOutline = true if needed
|
||||
val mapView = remember { MapView(context).apply { id = R.id.osm_map_view } }.apply {
|
||||
val mapView = remember { MapView(context) }.apply {
|
||||
setMultiTouchControls(true)
|
||||
setTileSource(TileSourceFactory.WIKIMEDIA)
|
||||
minZoomLevel = getMinZoom(resources.displayMetrics.heightPixels) + 0.25
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
package com.rtbishop.look4sat.presentation.mapScreen
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.lifecycle.*
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.rtbishop.look4sat.domain.IDataRepository
|
||||
import com.rtbishop.look4sat.domain.ISatelliteManager
|
||||
import com.rtbishop.look4sat.domain.ISettingsManager
|
||||
|
@ -30,8 +31,12 @@ import com.rtbishop.look4sat.utility.toDegrees
|
|||
import com.rtbishop.look4sat.utility.toTimerString
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.set
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
|
@ -50,23 +55,23 @@ class MapViewModel @Inject constructor(
|
|||
private var dataUpdateRate = 1000L
|
||||
private var selectedSatellite: Satellite? = null
|
||||
|
||||
val stationPos = liveData {
|
||||
val stationPos = flow {
|
||||
val osmLat = clipLat(stationPosition.lat)
|
||||
val osmLon = clipLon(stationPosition.lon)
|
||||
emit(GeoPos(osmLat, osmLon))
|
||||
}
|
||||
|
||||
private val _track = MutableLiveData<List<List<GeoPos>>>()
|
||||
val track: LiveData<List<List<GeoPos>>> = _track
|
||||
private val _track = MutableSharedFlow<List<List<GeoPos>>>()
|
||||
val track: SharedFlow<List<List<GeoPos>>> = _track
|
||||
|
||||
private val _footprint = MutableLiveData<SatPos>()
|
||||
val footprint: LiveData<SatPos> = _footprint
|
||||
private val _footprint = MutableSharedFlow<SatPos>()
|
||||
val footprint: SharedFlow<SatPos> = _footprint
|
||||
|
||||
private val _mapData = MutableLiveData<MapData>()
|
||||
val mapData: LiveData<MapData> = this._mapData
|
||||
private val _mapData = MutableSharedFlow<MapData>()
|
||||
val mapData: SharedFlow<MapData> = _mapData
|
||||
|
||||
private val _positions = MutableLiveData<Map<Satellite, GeoPos>>()
|
||||
val positions: LiveData<Map<Satellite, GeoPos>> = _positions
|
||||
private val _positions = MutableSharedFlow<Map<Satellite, GeoPos>>()
|
||||
val positions: SharedFlow<Map<Satellite, GeoPos>> = _positions
|
||||
|
||||
fun getPreferences() = preferences
|
||||
|
||||
|
@ -144,7 +149,7 @@ class MapViewModel @Inject constructor(
|
|||
currentTrack.add(currentPosition)
|
||||
}
|
||||
satTracks.add(currentTrack)
|
||||
_track.postValue(satTracks)
|
||||
_track.emit(satTracks)
|
||||
}
|
||||
|
||||
private suspend fun getPositions(satellites: List<Satellite>, pos: GeoPos, date: Date) {
|
||||
|
@ -155,12 +160,12 @@ class MapViewModel @Inject constructor(
|
|||
val osmLon = clipLon(satPos.longitude.toDegrees())
|
||||
positions[satellite] = GeoPos(osmLat, osmLon)
|
||||
}
|
||||
_positions.postValue(positions)
|
||||
_positions.emit(positions)
|
||||
}
|
||||
|
||||
private suspend fun getSatFootprint(satellite: Satellite, pos: GeoPos, date: Date) {
|
||||
val satPos = satelliteManager.getPosition(satellite, pos, date.time)
|
||||
_footprint.postValue(satPos)
|
||||
_footprint.emit(satPos)
|
||||
}
|
||||
|
||||
private suspend fun getSatData(sat: Satellite, pos: GeoPos, date: Date) {
|
||||
|
@ -202,7 +207,7 @@ class MapViewModel @Inject constructor(
|
|||
phase,
|
||||
visibility
|
||||
)
|
||||
_mapData.postValue(satData)
|
||||
_mapData.emit(satData)
|
||||
}
|
||||
|
||||
private fun clipLat(latitude: Double): Double {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
@file:OptIn(ExperimentalMaterialApi::class)
|
||||
|
||||
package com.rtbishop.look4sat.presentation.passesScreen
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
|
@ -8,14 +6,9 @@ import androidx.compose.foundation.clickable
|
|||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.PullRefreshState
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
|
@ -34,16 +27,19 @@ import com.rtbishop.look4sat.domain.predict.NearEarthSat
|
|||
import com.rtbishop.look4sat.domain.predict.OrbitalData
|
||||
import com.rtbishop.look4sat.domain.predict.SatPass
|
||||
import com.rtbishop.look4sat.presentation.MainTheme
|
||||
import com.rtbishop.look4sat.presentation.pullRefresh.PullRefreshIndicator
|
||||
import com.rtbishop.look4sat.presentation.pullRefresh.PullRefreshState
|
||||
import com.rtbishop.look4sat.presentation.pullRefresh.pullRefresh
|
||||
import com.rtbishop.look4sat.presentation.pullRefresh.rememberPullRefreshState
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
private val sdf = SimpleDateFormat("HH:mm:ss", Locale.ENGLISH)
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun PassesScreen(navToRadar: () -> Unit, viewModel: PassesViewModel = hiltViewModel()) {
|
||||
val state = viewModel.passes.observeAsState()
|
||||
val timerText = viewModel.timerText.observeAsState()
|
||||
val state = viewModel.passes.collectAsState(initial = null)
|
||||
val timerText = viewModel.timerText.collectAsState(initial = null)
|
||||
val isRefreshing = state.value is DataState.Loading
|
||||
val refreshState = rememberPullRefreshState(refreshing = isRefreshing,
|
||||
onRefresh = { viewModel.calculatePasses() })
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
*/
|
||||
package com.rtbishop.look4sat.presentation.passesScreen
|
||||
|
||||
import androidx.lifecycle.*
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.rtbishop.look4sat.domain.IDataRepository
|
||||
import com.rtbishop.look4sat.domain.ISatelliteManager
|
||||
import com.rtbishop.look4sat.domain.ISettingsManager
|
||||
|
@ -26,6 +27,8 @@ import com.rtbishop.look4sat.domain.predict.SatPass
|
|||
import com.rtbishop.look4sat.utility.toTimerString
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
|
@ -35,13 +38,12 @@ class PassesViewModel @Inject constructor(
|
|||
private val settings: ISettingsManager
|
||||
) : ViewModel() {
|
||||
|
||||
private val _passes = MutableLiveData<DataState<List<SatPass>>>()
|
||||
private val _passes = MutableStateFlow<DataState<List<SatPass>>>(DataState.Loading)
|
||||
private val timerData = Triple("Next - Id:Null", "Name: Null", "00:00:00")
|
||||
private val _timerText = MutableLiveData<Triple<String, String, String>>()
|
||||
private val _timerText = MutableStateFlow(timerData)
|
||||
private var passesProcessing: Job? = null
|
||||
val entriesTotal: LiveData<Int> = repository.getEntriesTotal().asLiveData()
|
||||
val passes: LiveData<DataState<List<SatPass>>> = _passes
|
||||
val timerText: LiveData<Triple<String, String, String>> = _timerText
|
||||
val passes: StateFlow<DataState<List<SatPass>>> = _passes
|
||||
val timerText: StateFlow<Triple<String, String, String>?> = _timerText
|
||||
|
||||
fun getHoursAhead() = settings.getHoursAhead()
|
||||
|
||||
|
@ -63,18 +65,18 @@ class PassesViewModel @Inject constructor(
|
|||
val name = nextPass.name
|
||||
val millisBeforeStart = nextPass.aosTime.minus(timeNow)
|
||||
val timerString = millisBeforeStart.toTimerString()
|
||||
_timerText.postValue(Triple("Next - Id:$catNum", name, timerString))
|
||||
_timerText.emit(Triple("Next - Id:$catNum", name, timerString))
|
||||
} catch (e: NoSuchElementException) {
|
||||
val lastPass = newPasses.last()
|
||||
val catNum = lastPass.catNum
|
||||
val name = lastPass.name
|
||||
val millisBeforeEnd = lastPass.losTime.minus(timeNow)
|
||||
val timerString = millisBeforeEnd.toTimerString()
|
||||
_timerText.postValue(Triple("Next - Id:$catNum", name, timerString))
|
||||
_timerText.emit(Triple("Next - Id:$catNum", name, timerString))
|
||||
}
|
||||
} else _timerText.postValue(timerData)
|
||||
} else _timerText.emit(timerData)
|
||||
|
||||
_passes.postValue(DataState.Success(newPasses))
|
||||
_passes.emit(DataState.Success(newPasses))
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
|
@ -97,7 +99,7 @@ class PassesViewModel @Inject constructor(
|
|||
selection: List<Int>? = null
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
_passes.postValue(DataState.Loading)
|
||||
_passes.emit(DataState.Loading)
|
||||
// _timerText.postValue(timerDefaultText)
|
||||
passesProcessing?.cancelAndJoin()
|
||||
selection?.let { items -> settings.saveEntriesSelection(items) }
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* Copyright 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.presentation.pullRefresh
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.contentColorFor
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.geometry.center
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.graphics.drawscope.rotate
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
|
||||
/**
|
||||
* The default indicator for Compose pull-to-refresh, based on Android's SwipeRefreshLayout.
|
||||
* @param refreshing A boolean representing whether a refresh is occurring.
|
||||
* @param state The [PullRefreshState] which controls where and how the indicator will be drawn.
|
||||
* @param modifier Modifiers for the indicator.
|
||||
* @param backgroundColor The color of the indicator's background.
|
||||
* @param contentColor The color of the indicator's arc and arrow.
|
||||
* @param scale A boolean controlling whether the indicator's size scales with pull progress or not.
|
||||
*/
|
||||
@Composable
|
||||
fun PullRefreshIndicator(
|
||||
refreshing: Boolean,
|
||||
state: PullRefreshState,
|
||||
modifier: Modifier = Modifier,
|
||||
backgroundColor: Color = MaterialTheme.colorScheme.surface,
|
||||
contentColor: Color = contentColorFor(backgroundColor),
|
||||
scale: Boolean = false
|
||||
) {
|
||||
val showElevation by remember(refreshing, state) {
|
||||
derivedStateOf { refreshing || state.position > 0.5f }
|
||||
}
|
||||
|
||||
Surface(
|
||||
modifier = modifier
|
||||
.size(IndicatorSize)
|
||||
.pullRefreshIndicatorTransform(state, scale),
|
||||
shape = SpinnerShape,
|
||||
color = backgroundColor,
|
||||
shadowElevation = if (showElevation) Elevation else 0.dp,
|
||||
) {
|
||||
Crossfade(
|
||||
targetState = refreshing, animationSpec = tween(durationMillis = CrossfadeDurationMs)
|
||||
) { refreshing ->
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center
|
||||
) {
|
||||
val spinnerSize = (ArcRadius + StrokeWidth).times(2)
|
||||
|
||||
if (refreshing) {
|
||||
CircularProgressIndicator(
|
||||
color = contentColor,
|
||||
strokeWidth = StrokeWidth,
|
||||
modifier = Modifier.size(spinnerSize),
|
||||
)
|
||||
} else {
|
||||
CircularArrowIndicator(state, contentColor, Modifier.size(spinnerSize))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier.size MUST be specified.
|
||||
*/
|
||||
@Composable
|
||||
private fun CircularArrowIndicator(
|
||||
state: PullRefreshState,
|
||||
color: Color,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
val path = remember { Path().apply { fillType = PathFillType.EvenOdd } }
|
||||
|
||||
Canvas(modifier.semantics { contentDescription = "Refreshing" }) {
|
||||
val values = ArrowValues(state.progress)
|
||||
|
||||
rotate(degrees = values.rotation) {
|
||||
val arcRadius = ArcRadius.toPx() + StrokeWidth.toPx() / 2f
|
||||
val arcBounds = Rect(
|
||||
size.center.x - arcRadius,
|
||||
size.center.y - arcRadius,
|
||||
size.center.x + arcRadius,
|
||||
size.center.y + arcRadius
|
||||
)
|
||||
drawArc(
|
||||
color = color,
|
||||
alpha = values.alpha,
|
||||
startAngle = values.startAngle,
|
||||
sweepAngle = values.endAngle - values.startAngle,
|
||||
useCenter = false,
|
||||
topLeft = arcBounds.topLeft,
|
||||
size = arcBounds.size,
|
||||
style = Stroke(
|
||||
width = StrokeWidth.toPx(), cap = StrokeCap.Square
|
||||
)
|
||||
)
|
||||
drawArrow(path, arcBounds, color, values)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
private class ArrowValues(
|
||||
val alpha: Float,
|
||||
val rotation: Float,
|
||||
val startAngle: Float,
|
||||
val endAngle: Float,
|
||||
val scale: Float
|
||||
)
|
||||
|
||||
private fun ArrowValues(progress: Float): ArrowValues {
|
||||
// Discard first 40% of progress. Scale remaining progress to full range between 0 and 100%.
|
||||
val adjustedPercent = max(min(1f, progress) - 0.4f, 0f) * 5 / 3
|
||||
// How far beyond the threshold pull has gone, as a percentage of the threshold.
|
||||
val overshootPercent = abs(progress) - 1.0f
|
||||
// Limit the overshoot to 200%. Linear between 0 and 200.
|
||||
val linearTension = overshootPercent.coerceIn(0f, 2f)
|
||||
// Non-linear tension. Increases with linearTension, but at a decreasing rate.
|
||||
val tensionPercent = linearTension - linearTension.pow(2) / 4
|
||||
|
||||
// Calculations based on SwipeRefreshLayout specification.
|
||||
val alpha = progress.coerceIn(0f, 1f)
|
||||
val endTrim = adjustedPercent * MaxProgressArc
|
||||
val rotation = (-0.25f + 0.4f * adjustedPercent + tensionPercent) * 0.5f
|
||||
val startAngle = rotation * 360
|
||||
val endAngle = (rotation + endTrim) * 360
|
||||
val scale = min(1f, adjustedPercent)
|
||||
|
||||
return ArrowValues(alpha, rotation, startAngle, endAngle, scale)
|
||||
}
|
||||
|
||||
private fun DrawScope.drawArrow(arrow: Path, bounds: Rect, color: Color, values: ArrowValues) {
|
||||
arrow.reset()
|
||||
arrow.moveTo(0f, 0f) // Move to left corner
|
||||
arrow.lineTo(x = ArrowWidth.toPx() * values.scale, y = 0f) // Line to right corner
|
||||
|
||||
// Line to tip of arrow
|
||||
arrow.lineTo(
|
||||
x = ArrowWidth.toPx() * values.scale / 2, y = ArrowHeight.toPx() * values.scale
|
||||
)
|
||||
|
||||
val radius = min(bounds.width, bounds.height) / 2f
|
||||
val inset = ArrowWidth.toPx() * values.scale / 2f
|
||||
arrow.translate(
|
||||
Offset(
|
||||
x = radius + bounds.center.x - inset, y = bounds.center.y + StrokeWidth.toPx() / 2f
|
||||
)
|
||||
)
|
||||
arrow.close()
|
||||
rotate(degrees = values.endAngle) {
|
||||
drawPath(path = arrow, color = color, alpha = values.alpha)
|
||||
}
|
||||
}
|
||||
|
||||
private const val CrossfadeDurationMs = 100
|
||||
private const val MaxProgressArc = 0.8f
|
||||
|
||||
private val IndicatorSize = 40.dp
|
||||
private val SpinnerShape = CircleShape
|
||||
private val ArcRadius = 7.5.dp
|
||||
private val StrokeWidth = 2.5.dp
|
||||
private val ArrowWidth = 10.dp
|
||||
private val ArrowHeight = 5.dp
|
||||
private val Elevation = 6.dp
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.presentation.pullRefresh
|
||||
|
||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.Drag
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.debugInspectorInfo
|
||||
import androidx.compose.ui.platform.inspectable
|
||||
import androidx.compose.ui.unit.Velocity
|
||||
|
||||
/**
|
||||
* A modifier for translating the position and scaling the size of a pull-to-refresh indicator
|
||||
* based on the given [PullRefreshState].
|
||||
* @param state The [PullRefreshState] which determines the position of the indicator.
|
||||
* @param scale A boolean controlling whether the indicator's size scales with pull progress or not.
|
||||
*/
|
||||
fun Modifier.pullRefreshIndicatorTransform(
|
||||
state: PullRefreshState,
|
||||
scale: Boolean = false,
|
||||
) = composed(inspectorInfo = debugInspectorInfo {
|
||||
name = "pullRefreshIndicatorTransform"
|
||||
properties["state"] = state
|
||||
properties["scale"] = scale
|
||||
}) {
|
||||
var height by remember { mutableStateOf(0) }
|
||||
|
||||
Modifier
|
||||
.onSizeChanged { height = it.height }
|
||||
.graphicsLayer {
|
||||
translationY = state.position - height
|
||||
|
||||
if (scale && !state.refreshing) {
|
||||
val scaleFraction = LinearOutSlowInEasing
|
||||
.transform(state.position / state.threshold)
|
||||
.coerceIn(0f, 1f)
|
||||
scaleX = scaleFraction
|
||||
scaleY = scaleFraction
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PullRefresh modifier to be used in conjunction with [PullRefreshState]. Provides a connection
|
||||
* to the nested scroll system. Based on Android's SwipeRefreshLayout.
|
||||
* @param state The [PullRefreshState] associated with this pull-to-refresh component.
|
||||
* The state will be updated by this modifier.
|
||||
* @param enabled If not enabled, all scroll delta and fling velocity will be ignored.
|
||||
*/
|
||||
fun Modifier.pullRefresh(
|
||||
state: PullRefreshState, enabled: Boolean = true
|
||||
) = inspectable(inspectorInfo = debugInspectorInfo {
|
||||
name = "pullRefresh"
|
||||
properties["state"] = state
|
||||
properties["enabled"] = enabled
|
||||
}) {
|
||||
Modifier.pullRefresh(state::onPull, { state.onRelease() }, enabled)
|
||||
}
|
||||
|
||||
/**
|
||||
* A modifier for building pull-to-refresh components. Provides a connection to the nested scroll
|
||||
* system.
|
||||
* @param onPull Callback for dispatching vertical scroll delta, takes float pullDelta as argument.
|
||||
* Positive delta (pulling down) is dispatched only if the child does not consume it (i.e. pulling
|
||||
* down despite being at the top of a scrollable component), whereas negative delta (swiping up) is
|
||||
* dispatched first (in case it is needed to push the indicator back up), and then whatever is not
|
||||
* consumed is passed on to the child.
|
||||
* @param onRelease Callback for when drag is released, takes float flingVelocity as argument.
|
||||
* @param enabled If not enabled, all scroll delta and fling velocity will be ignored and neither
|
||||
* [onPull] nor [onRelease] will be invoked.
|
||||
*/
|
||||
fun Modifier.pullRefresh(
|
||||
onPull: (pullDelta: Float) -> Float,
|
||||
onRelease: suspend (flingVelocity: Float) -> Unit,
|
||||
enabled: Boolean = true
|
||||
) = inspectable(inspectorInfo = debugInspectorInfo {
|
||||
name = "pullRefresh"
|
||||
properties["onPull"] = onPull
|
||||
properties["onRelease"] = onRelease
|
||||
properties["enabled"] = enabled
|
||||
}) {
|
||||
Modifier.nestedScroll(PullRefreshNestedScrollConnection(onPull, onRelease, enabled))
|
||||
}
|
||||
|
||||
private class PullRefreshNestedScrollConnection(
|
||||
private val onPull: (pullDelta: Float) -> Float,
|
||||
private val onRelease: suspend (flingVelocity: Float) -> Unit,
|
||||
private val enabled: Boolean
|
||||
) : NestedScrollConnection {
|
||||
|
||||
override fun onPreScroll(
|
||||
available: Offset, source: NestedScrollSource
|
||||
): Offset = when {
|
||||
!enabled -> Offset.Zero
|
||||
source == Drag && available.y < 0 -> Offset(0f, onPull(available.y)) // Swiping up
|
||||
else -> Offset.Zero
|
||||
}
|
||||
|
||||
override fun onPostScroll(
|
||||
consumed: Offset, available: Offset, source: NestedScrollSource
|
||||
): Offset = when {
|
||||
!enabled -> Offset.Zero
|
||||
source == Drag && available.y > 0 -> Offset(0f, onPull(available.y)) // Pulling down
|
||||
else -> Offset.Zero
|
||||
}
|
||||
|
||||
override suspend fun onPreFling(available: Velocity): Velocity {
|
||||
onRelease(available.y)
|
||||
return Velocity.Zero
|
||||
}
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* Copyright 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.rtbishop.look4sat.presentation.pullRefresh
|
||||
|
||||
import androidx.compose.animation.core.animate
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.pow
|
||||
|
||||
/**
|
||||
* Creates a [PullRefreshState] that is remembered across compositions.
|
||||
* Changes to [refreshing] will result in [PullRefreshState] being updated.
|
||||
* @param refreshing A boolean representing whether a refresh is currently occurring.
|
||||
* @param onRefresh The function to be called to trigger a refresh.
|
||||
* @param refreshThreshold The threshold below which, if a release
|
||||
* occurs, [onRefresh] will be called.
|
||||
* @param refreshingOffset The offset at which the indicator will be drawn while refreshing. This
|
||||
* offset corresponds to the position of the bottom of the indicator.
|
||||
*/
|
||||
@Composable
|
||||
fun rememberPullRefreshState(
|
||||
refreshing: Boolean,
|
||||
onRefresh: () -> Unit,
|
||||
refreshThreshold: Dp = PullRefreshDefaults.RefreshThreshold,
|
||||
refreshingOffset: Dp = PullRefreshDefaults.RefreshingOffset,
|
||||
): PullRefreshState {
|
||||
require(refreshThreshold > 0.dp) { "The refresh trigger must be greater than zero!" }
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
val onRefreshState = rememberUpdatedState(onRefresh)
|
||||
val thresholdPx: Float
|
||||
val refreshingOffsetPx: Float
|
||||
|
||||
with(LocalDensity.current) {
|
||||
thresholdPx = refreshThreshold.toPx()
|
||||
refreshingOffsetPx = refreshingOffset.toPx()
|
||||
}
|
||||
|
||||
// refreshThreshold and refreshingOffset should not be changed after instantiation, so any
|
||||
// changes to these values are ignored.
|
||||
val state = remember(scope) {
|
||||
PullRefreshState(scope, onRefreshState, refreshingOffsetPx, thresholdPx)
|
||||
}
|
||||
|
||||
SideEffect {
|
||||
state.setRefreshing(refreshing)
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* A state object that can be used in conjunction with [pullRefresh] to add pull-to-refresh
|
||||
* behaviour to a scroll component. Based on Android's SwipeRefreshLayout.
|
||||
*
|
||||
* Provides [progress], a float representing how far the user has pulled as a percentage of the
|
||||
* refreshThreshold. Values of one or less indicate that the user has not yet pulled past the
|
||||
* threshold. Values greater than one indicate how far past the threshold the user has pulled.
|
||||
*
|
||||
* Can be used in conjunction with [pullRefreshIndicatorTransform] to implement Android-like
|
||||
* pull-to-refresh behaviour with a custom indicator.
|
||||
*
|
||||
* Should be created using [rememberPullRefreshState].
|
||||
*/
|
||||
class PullRefreshState internal constructor(
|
||||
private val animationScope: CoroutineScope,
|
||||
private val onRefreshState: State<() -> Unit>,
|
||||
private val refreshingOffset: Float,
|
||||
internal val threshold: Float
|
||||
) {
|
||||
/**
|
||||
* A float representing how far the user has pulled as a percentage of the refreshThreshold.
|
||||
*
|
||||
* If the component has not been pulled at all, progress is zero. If the pull has reached
|
||||
* halfway to the threshold, progress is 0.5f. A value greater than 1 indicates that pull has
|
||||
* gone beyond the refreshThreshold - e.g. a value of 2f indicates that the user has pulled to
|
||||
* two times the refreshThreshold.
|
||||
*/
|
||||
val progress get() = adjustedDistancePulled / threshold
|
||||
|
||||
internal val refreshing get() = _refreshing
|
||||
internal val position get() = _position
|
||||
|
||||
private val adjustedDistancePulled by derivedStateOf { distancePulled * DragMultiplier }
|
||||
|
||||
private var _refreshing by mutableStateOf(false)
|
||||
private var _position by mutableStateOf(0f)
|
||||
private var distancePulled by mutableStateOf(0f)
|
||||
|
||||
internal fun onPull(pullDelta: Float): Float {
|
||||
if (this._refreshing) return 0f // Already refreshing, do nothing.
|
||||
|
||||
val newOffset = (distancePulled + pullDelta).coerceAtLeast(0f)
|
||||
val dragConsumed = newOffset - distancePulled
|
||||
distancePulled = newOffset
|
||||
_position = calculateIndicatorPosition()
|
||||
return dragConsumed
|
||||
}
|
||||
|
||||
internal fun onRelease() {
|
||||
if (!this._refreshing) {
|
||||
if (adjustedDistancePulled > threshold) {
|
||||
onRefreshState.value()
|
||||
} else {
|
||||
animateIndicatorTo(0f)
|
||||
}
|
||||
}
|
||||
distancePulled = 0f
|
||||
}
|
||||
|
||||
internal fun setRefreshing(refreshing: Boolean) {
|
||||
if (this._refreshing != refreshing) {
|
||||
this._refreshing = refreshing
|
||||
this.distancePulled = 0f
|
||||
animateIndicatorTo(if (refreshing) refreshingOffset else 0f)
|
||||
}
|
||||
}
|
||||
|
||||
private fun animateIndicatorTo(offset: Float) = animationScope.launch {
|
||||
animate(initialValue = _position, targetValue = offset) { value, _ ->
|
||||
_position = value
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateIndicatorPosition(): Float = when {
|
||||
// If drag hasn't gone past the threshold, the position is the adjustedDistancePulled.
|
||||
adjustedDistancePulled <= threshold -> adjustedDistancePulled
|
||||
else -> {
|
||||
// How far beyond the threshold pull has gone, as a percentage of the threshold.
|
||||
val overshootPercent = abs(progress) - 1.0f
|
||||
// Limit the overshoot to 200%. Linear between 0 and 200.
|
||||
val linearTension = overshootPercent.coerceIn(0f, 2f)
|
||||
// Non-linear tension. Increases with linearTension, but at a decreasing rate.
|
||||
val tensionPercent = linearTension - linearTension.pow(2) / 4
|
||||
// The additional offset beyond the threshold.
|
||||
val extraOffset = threshold * tensionPercent
|
||||
threshold + extraOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default parameter values for [rememberPullRefreshState].
|
||||
*/
|
||||
object PullRefreshDefaults {
|
||||
/**
|
||||
* If the indicator is below this threshold offset when it is released, a refresh
|
||||
* will be triggered.
|
||||
*/
|
||||
val RefreshThreshold = 80.dp
|
||||
|
||||
/**
|
||||
* The offset at which the indicator should be rendered whilst a refresh is occurring.
|
||||
*/
|
||||
val RefreshingOffset = 56.dp
|
||||
}
|
||||
|
||||
/**
|
||||
* The distance pulled is multiplied by this value to give us the adjusted distance pulled, which
|
||||
* is used in calculating the indicator position (when the adjusted distance pulled is less than
|
||||
* the refresh threshold, it is the indicator position, otherwise the indicator position is
|
||||
* derived from the progress).
|
||||
*/
|
||||
private const val DragMultiplier = 0.5f
|
|
@ -1,145 +0,0 @@
|
|||
/*
|
||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.rtbishop.look4sat.presentation.radarScreen
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
import com.rtbishop.look4sat.R
|
||||
import com.rtbishop.look4sat.databinding.FragmentRadarBinding
|
||||
import com.rtbishop.look4sat.domain.predict.SatPass
|
||||
import com.rtbishop.look4sat.domain.predict.SatPos
|
||||
import com.rtbishop.look4sat.utility.toDegrees
|
||||
import com.rtbishop.look4sat.utility.toTimerString
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class RadarFragment : Fragment(R.layout.fragment_radar) {
|
||||
|
||||
private val viewModel: RadarViewModel by viewModels()
|
||||
private val radioAdapter = RadioAdapter()
|
||||
private var binding: FragmentRadarBinding? = null
|
||||
private var radarView: RadarView? = null
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding = FragmentRadarBinding.bind(view).apply {
|
||||
radarRecycler.apply {
|
||||
setHasFixedSize(true)
|
||||
this.adapter = radioAdapter
|
||||
this.layoutManager = LinearLayoutManager(requireContext())
|
||||
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||
addItemDecoration(DividerItemDecoration(requireContext(), 1))
|
||||
}
|
||||
setupObservers()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
viewModel.enableSensor()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
viewModel.disableSensor()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
binding?.radarRecycler?.adapter = null
|
||||
radarView = null
|
||||
binding = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun setupObservers() {
|
||||
viewModel.getPass(45000, System.currentTimeMillis()).observe(viewLifecycleOwner) { pass ->
|
||||
binding?.run {
|
||||
radarView = RadarView(requireContext()).apply {
|
||||
setShowAim(viewModel.getUseCompass())
|
||||
setScanning(viewModel.getShowSweep())
|
||||
}
|
||||
radarCard.addView(radarView)
|
||||
viewModel.radarData.observe(viewLifecycleOwner) { passData ->
|
||||
radarView?.setPosition(passData.satPos)
|
||||
radarView?.setPositions(passData.satTrack)
|
||||
setPassText(pass, passData.satPos)
|
||||
}
|
||||
viewModel.transmitters.observe(viewLifecycleOwner) { list ->
|
||||
if (list.isNotEmpty()) {
|
||||
radioAdapter.submitList(list)
|
||||
radarProgress.visibility = View.INVISIBLE
|
||||
} else {
|
||||
radarProgress.visibility = View.INVISIBLE
|
||||
radarEmptyLayout.visibility = View.VISIBLE
|
||||
}
|
||||
radarView?.invalidate()
|
||||
}
|
||||
viewModel.orientation.observe(viewLifecycleOwner) { value ->
|
||||
radarView?.setOrientation(value.first, value.second, value.third)
|
||||
}
|
||||
// radarBtnBack.clickWithDebounce { findNavController().navigateUp() }
|
||||
// radarBtnMap.clickWithDebounce {
|
||||
// val direction = RadarFragmentDirections.globalToMap(pass.catNum)
|
||||
// findNavController().navigate(direction)
|
||||
// }
|
||||
// radarBtnNotify.isEnabled = false
|
||||
// radarBtnSettings.clickWithDebounce {
|
||||
// val direction = RadarFragmentDirections.globalToSettings()
|
||||
// findNavController().navigate(direction)
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setPassText(satPass: SatPass, satPos: SatPos) {
|
||||
binding?.run {
|
||||
val timeNow = System.currentTimeMillis()
|
||||
val radarAzim = getString(R.string.radar_az_value)
|
||||
val radarElev = getString(R.string.radar_el_value)
|
||||
val radarAlt = getString(R.string.radar_alt_value)
|
||||
val radarDist = getString(R.string.radar_dist_value)
|
||||
radarAzValue.text = String.format(radarAzim, satPos.azimuth.toDegrees())
|
||||
radarElValue.text = String.format(radarElev, satPos.elevation.toDegrees())
|
||||
radarAltValue.text = String.format(radarAlt, satPos.altitude)
|
||||
radarDstValue.text = String.format(radarDist, satPos.distance)
|
||||
if (satPos.eclipsed) {
|
||||
radarVisibility.text = getText(R.string.radar_eclipsed)
|
||||
} else {
|
||||
radarVisibility.text = getText(R.string.radar_visible)
|
||||
}
|
||||
if (!satPass.isDeepSpace) {
|
||||
if (timeNow < satPass.aosTime) {
|
||||
val millisBeforeStart = satPass.aosTime.minus(timeNow)
|
||||
radarTimer.text = millisBeforeStart.toTimerString()
|
||||
} else {
|
||||
val millisBeforeEnd = satPass.losTime.minus(timeNow)
|
||||
radarTimer.text = millisBeforeEnd.toTimerString()
|
||||
if (timeNow > satPass.losTime) {
|
||||
radarTimer.text = 0L.toTimerString()
|
||||
// findNavController().navigateUp()
|
||||
}
|
||||
}
|
||||
} else radarTimer.text = 0L.toTimerString()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -55,3 +55,79 @@ fun RadarScreen(viewModel: RadarViewModel = hiltViewModel()) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
//private val divider = 1000000f
|
||||
//radioDownlink.text = String.format(Locale.ENGLISH, link, downlink / divider)
|
||||
//radioUplink.text = String.format(Locale.ENGLISH, link, uplink / divider)
|
||||
|
||||
//private fun setupObservers() {
|
||||
// viewModel.getPass(45000, System.currentTimeMillis()).observe(viewLifecycleOwner) { pass ->
|
||||
// binding?.run {
|
||||
// radarView = RadarView(requireContext()).apply {
|
||||
// setShowAim(viewModel.getUseCompass())
|
||||
// setScanning(viewModel.getShowSweep())
|
||||
// }
|
||||
// radarCard.addView(radarView)
|
||||
// viewModel.radarData.observe(viewLifecycleOwner) { passData ->
|
||||
// radarView?.setPosition(passData.satPos)
|
||||
// radarView?.setPositions(passData.satTrack)
|
||||
// setPassText(pass, passData.satPos)
|
||||
// }
|
||||
// viewModel.transmitters.observe(viewLifecycleOwner) { list ->
|
||||
// if (list.isNotEmpty()) {
|
||||
// radioAdapter.submitList(list)
|
||||
// radarProgress.visibility = View.INVISIBLE
|
||||
// } else {
|
||||
// radarProgress.visibility = View.INVISIBLE
|
||||
// radarEmptyLayout.visibility = View.VISIBLE
|
||||
// }
|
||||
// radarView?.invalidate()
|
||||
// }
|
||||
// viewModel.orientation.observe(viewLifecycleOwner) { value ->
|
||||
// radarView?.setOrientation(value.first, value.second, value.third)
|
||||
// }
|
||||
// radarBtnBack.clickWithDebounce { findNavController().navigateUp() }
|
||||
// radarBtnMap.clickWithDebounce {
|
||||
// val direction = RadarFragmentDirections.globalToMap(pass.catNum)
|
||||
// findNavController().navigate(direction)
|
||||
// }
|
||||
// radarBtnNotify.isEnabled = false
|
||||
// radarBtnSettings.clickWithDebounce {
|
||||
// val direction = RadarFragmentDirections.globalToSettings()
|
||||
// findNavController().navigate(direction)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//private fun setPassText(satPass: SatPass, satPos: SatPos) {
|
||||
// binding?.run {
|
||||
// val timeNow = System.currentTimeMillis()
|
||||
// val radarAzim = getString(R.string.radar_az_value)
|
||||
// val radarElev = getString(R.string.radar_el_value)
|
||||
// val radarAlt = getString(R.string.radar_alt_value)
|
||||
// val radarDist = getString(R.string.radar_dist_value)
|
||||
// radarAzValue.text = String.format(radarAzim, satPos.azimuth.toDegrees())
|
||||
// radarElValue.text = String.format(radarElev, satPos.elevation.toDegrees())
|
||||
// radarAltValue.text = String.format(radarAlt, satPos.altitude)
|
||||
// radarDstValue.text = String.format(radarDist, satPos.distance)
|
||||
// if (satPos.eclipsed) {
|
||||
// radarVisibility.text = getText(R.string.radar_eclipsed)
|
||||
// } else {
|
||||
// radarVisibility.text = getText(R.string.radar_visible)
|
||||
// }
|
||||
// if (!satPass.isDeepSpace) {
|
||||
// if (timeNow < satPass.aosTime) {
|
||||
// val millisBeforeStart = satPass.aosTime.minus(timeNow)
|
||||
// radarTimer.text = millisBeforeStart.toTimerString()
|
||||
// } else {
|
||||
// val millisBeforeEnd = satPass.losTime.minus(timeNow)
|
||||
// radarTimer.text = millisBeforeEnd.toTimerString()
|
||||
// if (timeNow > satPass.losTime) {
|
||||
// radarTimer.text = 0L.toTimerString()
|
||||
//// findNavController().navigateUp()
|
||||
// }
|
||||
// }
|
||||
// } else radarTimer.text = 0L.toTimerString()
|
||||
// }
|
||||
//}
|
||||
|
|
|
@ -19,7 +19,10 @@ package com.rtbishop.look4sat.presentation.radarScreen
|
|||
|
||||
import android.hardware.GeomagneticField
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.*
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.rtbishop.look4sat.domain.IDataRepository
|
||||
import com.rtbishop.look4sat.domain.ISatelliteManager
|
||||
import com.rtbishop.look4sat.domain.ISettingsManager
|
||||
|
@ -34,6 +37,7 @@ import com.rtbishop.look4sat.utility.round
|
|||
import com.rtbishop.look4sat.utility.toDegrees
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
@ -56,7 +60,7 @@ class RadarViewModel @Inject constructor(
|
|||
val transmitters: LiveData<List<SatRadio>> = _transmitters
|
||||
val orientation: LiveData<Triple<Float, Float, Float>> = _orientation
|
||||
|
||||
fun getPass(catNum: Int, aosTime: Long) = liveData {
|
||||
fun getPass(catNum: Int, aosTime: Long) = flow {
|
||||
satManager.calculatedPasses.collect { passes ->
|
||||
val pass = passes.find { pass -> pass.catNum == catNum && pass.aosTime == aosTime }
|
||||
pass?.let { satPass ->
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
/*
|
||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.rtbishop.look4sat.presentation.radarScreen
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.AsyncListDiffer
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.rtbishop.look4sat.R
|
||||
import com.rtbishop.look4sat.databinding.ItemRadioBinding
|
||||
import com.rtbishop.look4sat.domain.model.SatRadio
|
||||
import java.util.*
|
||||
|
||||
class RadioAdapter : RecyclerView.Adapter<RadioAdapter.TransHolder>() {
|
||||
|
||||
private val diffCallback = object : DiffUtil.ItemCallback<SatRadio>() {
|
||||
override fun areItemsTheSame(oldItem: SatRadio, newItem: SatRadio): Boolean {
|
||||
return oldItem.uuid == newItem.uuid
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: SatRadio, newItem: SatRadio): Boolean {
|
||||
return oldItem.downlink == newItem.downlink
|
||||
}
|
||||
}
|
||||
private val differ = AsyncListDiffer(this, diffCallback)
|
||||
|
||||
fun submitList(items: List<SatRadio>) = differ.submitList(items)
|
||||
|
||||
override fun getItemCount() = differ.currentList.size
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransHolder {
|
||||
return TransHolder.from(parent)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: TransHolder, position: Int) {
|
||||
holder.bind(differ.currentList[position])
|
||||
}
|
||||
|
||||
class TransHolder private constructor(private val binding: ItemRadioBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
private val divider = 1000000f
|
||||
private val stringYes = itemView.context.getString(R.string.radio_string_yes)
|
||||
private val stringNo = itemView.context.getString(R.string.radio_string_no)
|
||||
private val link = itemView.context.getString(R.string.radio_link_low)
|
||||
private val linkNull = itemView.context.getString(R.string.radio_no_link)
|
||||
private val mode = itemView.context.getString(R.string.radio_mode)
|
||||
private val inverted = itemView.context.getString(R.string.radio_inverted)
|
||||
|
||||
fun bind(radio: SatRadio) {
|
||||
binding.run {
|
||||
radioInfo.text = radio.info
|
||||
radio.downlink.let { downlink ->
|
||||
if (downlink != null) {
|
||||
radioDownlink.text = String.format(Locale.ENGLISH, link, downlink / divider)
|
||||
} else {
|
||||
radioDownlink.text = linkNull
|
||||
}
|
||||
}
|
||||
radio.uplink.let { uplink ->
|
||||
if (uplink != null) {
|
||||
radioUplink.text = String.format(Locale.ENGLISH, link, uplink / divider)
|
||||
} else {
|
||||
radioUplink.text = linkNull
|
||||
}
|
||||
}
|
||||
if (radio.mode != null) {
|
||||
radioMode.text = String.format(mode, radio.mode)
|
||||
} else {
|
||||
radioMode.text = String.format(mode, stringNo)
|
||||
}
|
||||
if (radio.isInverted) {
|
||||
radioInverted.text = String.format(inverted, stringYes)
|
||||
} else {
|
||||
radioInverted.text = String.format(inverted, stringNo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun from(parent: ViewGroup): TransHolder {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
return TransHolder(ItemRadioBinding.inflate(inflater, parent, false))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,293 +0,0 @@
|
|||
/*
|
||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.rtbishop.look4sat.presentation.settingsScreen
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.widget.NestedScrollView
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.asLiveData
|
||||
import com.rtbishop.look4sat.R
|
||||
import com.rtbishop.look4sat.databinding.FragmentSettingsBinding
|
||||
import com.rtbishop.look4sat.domain.model.DataState
|
||||
import com.rtbishop.look4sat.domain.predict.GeoPos
|
||||
import com.rtbishop.look4sat.presentation.clickWithDebounce
|
||||
import com.rtbishop.look4sat.utility.isValidIPv4
|
||||
import com.rtbishop.look4sat.utility.isValidPort
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SettingsFragment : Fragment(R.layout.fragment_settings) {
|
||||
|
||||
private val viewModel: SettingsViewModel by viewModels()
|
||||
private val bluetooth = when {
|
||||
Build.VERSION.SDK_INT < Build.VERSION_CODES.S -> Manifest.permission.BLUETOOTH
|
||||
else -> Manifest.permission.BLUETOOTH_CONNECT
|
||||
}
|
||||
private val bluetoothContract = ActivityResultContracts.RequestPermission()
|
||||
private val bluetoothRequest = registerForActivityResult(bluetoothContract) { isGranted ->
|
||||
if (!isGranted) {
|
||||
showToast(getString(R.string.BTremote_perm_error))
|
||||
toggleBTstate(isGranted)
|
||||
}
|
||||
}
|
||||
private val locationFine = Manifest.permission.ACCESS_FINE_LOCATION
|
||||
private val locationCoarse = Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
private val locationContract = ActivityResultContracts.RequestMultiplePermissions()
|
||||
private val locationRequest = registerForActivityResult(locationContract) { permissions ->
|
||||
when {
|
||||
permissions[locationFine] == true -> viewModel.setPositionFromGps()
|
||||
permissions[locationCoarse] == true -> viewModel.setPositionFromNet()
|
||||
else -> showToast(getString(R.string.location_gps_error))
|
||||
}
|
||||
}
|
||||
private val contentContract = ActivityResultContracts.GetContent()
|
||||
private val contentRequest = registerForActivityResult(contentContract) { uri ->
|
||||
uri?.let { viewModel.updateFromFile(uri.toString()) }
|
||||
}
|
||||
private lateinit var binding: FragmentSettingsBinding
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding = FragmentSettingsBinding.bind(view).apply {
|
||||
// settingsBtnBack.clickWithDebounce { findNavController().navigateUp() }
|
||||
settingsScroll.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, y, _, newY ->
|
||||
if (y > newY) settingsFab.hide() else settingsFab.show()
|
||||
})
|
||||
settingsBtnGithub.clickWithDebounce {
|
||||
gotoUrl("https://github.com/rt-bishop/Look4Sat/")
|
||||
}
|
||||
settingsFab.clickWithDebounce {
|
||||
gotoUrl("https://ko-fi.com/rt_bishop")
|
||||
}
|
||||
settingsBtnFdroid.clickWithDebounce {
|
||||
gotoUrl("https://f-droid.org/en/packages/com.rtbishop.look4sat/")
|
||||
}
|
||||
}
|
||||
setupLocationCard()
|
||||
setupDataCard()
|
||||
setupRemoteCard()
|
||||
setupBTCard()
|
||||
setupOtherCard()
|
||||
viewModel.stationPosition.asLiveData().observe(viewLifecycleOwner) { stationPos ->
|
||||
stationPos?.let { handleStationPosition(stationPos) }
|
||||
}
|
||||
viewModel.getUpdateState().asLiveData().observe(viewLifecycleOwner) { updateState ->
|
||||
updateState?.let { handleSatState(updateState) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupLocationCard() {
|
||||
binding.run {
|
||||
setPositionText(viewModel.getStationPosition())
|
||||
settingsLocation.locationBtnGps.clickWithDebounce {
|
||||
locationRequest.launch(arrayOf(locationFine, locationCoarse))
|
||||
}
|
||||
// settingsLocation.locationBtnManual.clickWithDebounce {
|
||||
// val action = SettingsFragmentDirections.globalToPosition()
|
||||
// findNavController().navigate(action)
|
||||
// }
|
||||
// settingsLocation.locationBtnQth.clickWithDebounce {
|
||||
// val action = SettingsFragmentDirections.globalToLocator()
|
||||
// findNavController().navigate(action)
|
||||
// }
|
||||
// getNavResult<Pair<Double, Double>>(R.id.nav_settings, "position") { position ->
|
||||
// viewModel.setStationPosition(position.first, position.second)
|
||||
// }
|
||||
// getNavResult<String>(R.id.nav_settings, "locator") { locator ->
|
||||
// viewModel.setPositionFromQth(locator)
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupDataCard() {
|
||||
binding.run {
|
||||
setUpdateTime(viewModel.getLastUpdateTime())
|
||||
settingsData.dataBtnWeb.clickWithDebounce { viewModel.updateFromWeb() }
|
||||
settingsData.dataBtnFile.clickWithDebounce { contentRequest.launch("*/*") }
|
||||
settingsData.dataBtnClear.clickWithDebounce { viewModel.clearAllData() }
|
||||
viewModel.entriesTotal.observe(viewLifecycleOwner) { number ->
|
||||
val entriesFormat = getString(R.string.data_entries)
|
||||
settingsData.dataEntries.text = String.format(entriesFormat, number)
|
||||
}
|
||||
viewModel.radiosTotal.observe(viewLifecycleOwner) { number ->
|
||||
val radiosFormat = getString(R.string.data_radios)
|
||||
settingsData.dataRadios.text = String.format(radiosFormat, number)
|
||||
}
|
||||
// getNavResult<List<String>>(R.id.nav_settings, "sources") {
|
||||
// viewModel.updateFromWeb()
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupRemoteCard() {
|
||||
binding.run {
|
||||
settingsRemote.remoteSwitch.apply {
|
||||
isChecked = viewModel.getRotatorEnabled()
|
||||
settingsRemote.remoteIp.isEnabled = isChecked
|
||||
settingsRemote.remoteIpEdit.setText(viewModel.getRotatorServer())
|
||||
settingsRemote.remotePort.isEnabled = isChecked
|
||||
settingsRemote.remotePortEdit.setText(viewModel.getRotatorPort())
|
||||
setOnCheckedChangeListener { _, isChecked ->
|
||||
viewModel.setRotatorEnabled(isChecked)
|
||||
settingsRemote.remoteIp.isEnabled = isChecked
|
||||
settingsRemote.remotePort.isEnabled = isChecked
|
||||
}
|
||||
}
|
||||
settingsRemote.remoteIpEdit.doOnTextChanged { text, _, _, _ ->
|
||||
if (text.toString().isValidIPv4()) viewModel.setRotatorServer(text.toString())
|
||||
}
|
||||
settingsRemote.remotePortEdit.doOnTextChanged { text, _, _, _ ->
|
||||
if (text.toString().isValidPort()) viewModel.setRotatorPort(text.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupBTCard() {
|
||||
binding.run {
|
||||
settingsBtremote.BTremoteSwitch.apply {
|
||||
isChecked = viewModel.getBTEnabled()
|
||||
settingsBtremote.BTremoteAddress.isEnabled = isChecked
|
||||
settingsBtremote.BTAddressEdit.setText(viewModel.getBTDeviceAddr())
|
||||
settingsBtremote.BTremoteFormat.isEnabled = isChecked
|
||||
settingsBtremote.BTFormatEdit.setText(viewModel.getBTFormat())
|
||||
setOnCheckedChangeListener { _, isChecked ->
|
||||
toggleBTstate(isChecked)
|
||||
bluetoothRequest.launch(bluetooth)
|
||||
}
|
||||
}
|
||||
settingsBtremote.BTAddressEdit.doOnTextChanged { text, _, _, _ ->
|
||||
viewModel.setBTDeviceAddr(text.toString())
|
||||
}
|
||||
settingsBtremote.BTFormatEdit.doOnTextChanged { text, _, _, _ ->
|
||||
viewModel.setBTFormat(text.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleBTstate(value: Boolean) {
|
||||
binding.run {
|
||||
viewModel.setBTEnabled(value)
|
||||
settingsBtremote.BTremoteSwitch.isChecked = value
|
||||
settingsBtremote.BTremoteAddress.isEnabled = value
|
||||
settingsBtremote.BTremoteFormat.isEnabled = value
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupOtherCard() {
|
||||
binding.run {
|
||||
settingsOther.otherSwitchUtc.apply {
|
||||
isChecked = viewModel.getUseUTC()
|
||||
setOnCheckedChangeListener { _, isChecked -> viewModel.setUseUTC(isChecked) }
|
||||
}
|
||||
settingsOther.otherSwitchUpdate.apply {
|
||||
isChecked = viewModel.getAutoUpdateEnabled()
|
||||
setOnCheckedChangeListener { _, isChecked -> viewModel.setAutoUpdateEnabled(isChecked) }
|
||||
}
|
||||
settingsOther.otherSwitchSweep.apply {
|
||||
isChecked = viewModel.getShowSweep()
|
||||
setOnCheckedChangeListener { _, isChecked -> viewModel.setShowSweep(isChecked) }
|
||||
}
|
||||
settingsOther.otherSwitchSensors.apply {
|
||||
isChecked = viewModel.getUseCompass()
|
||||
setOnCheckedChangeListener { _, isChecked -> viewModel.setUseCompass(isChecked) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleStationPosition(pos: DataState<GeoPos>) {
|
||||
when (pos) {
|
||||
is DataState.Success -> {
|
||||
setPositionText(pos.data)
|
||||
binding.settingsLocation.locationProgress.isIndeterminate = false
|
||||
viewModel.setPositionHandled()
|
||||
showToast(getString(R.string.location_success))
|
||||
}
|
||||
is DataState.Error -> {
|
||||
binding.settingsLocation.locationProgress.isIndeterminate = false
|
||||
viewModel.setPositionHandled()
|
||||
showToast(pos.message.toString())
|
||||
}
|
||||
DataState.Loading -> {
|
||||
binding.settingsLocation.locationProgress.isIndeterminate = true
|
||||
}
|
||||
DataState.Handled -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setPositionText(geoPos: GeoPos) {
|
||||
binding.run {
|
||||
val latFormat = getString(R.string.location_lat)
|
||||
val lonFormat = getString(R.string.location_lon)
|
||||
settingsLocation.locationLat.text = String.format(latFormat, geoPos.lat)
|
||||
settingsLocation.locationLon.text = String.format(lonFormat, geoPos.lon)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSatState(state: DataState<Long>) {
|
||||
when (state) {
|
||||
is DataState.Success -> {
|
||||
binding.settingsData.dataProgress.isIndeterminate = false
|
||||
setUpdateTime(state.data)
|
||||
viewModel.setUpdateHandled()
|
||||
if (state.data == 0L) {
|
||||
showToast(getString(R.string.data_clear_success))
|
||||
} else {
|
||||
showToast(getString(R.string.data_success))
|
||||
}
|
||||
}
|
||||
is DataState.Error -> {
|
||||
binding.settingsData.dataProgress.isIndeterminate = false
|
||||
viewModel.setUpdateHandled()
|
||||
showToast(getString(R.string.data_error))
|
||||
}
|
||||
is DataState.Loading -> {
|
||||
binding.settingsData.dataProgress.isIndeterminate = true
|
||||
}
|
||||
is DataState.Handled -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setUpdateTime(updateTime: Long) {
|
||||
val updatePattern = getString(R.string.data_update)
|
||||
val updateDate = if (updateTime == 0L) {
|
||||
getString(R.string.pass_placeholder)
|
||||
} else {
|
||||
SimpleDateFormat("d MMM yyyy - HH:mm:ss", Locale.getDefault()).format(Date(updateTime))
|
||||
}
|
||||
binding.settingsData.dataUpdate.text = String.format(updatePattern, updateDate)
|
||||
}
|
||||
|
||||
private fun showToast(message: String) {
|
||||
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun gotoUrl(url: String) {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||
}
|
||||
}
|
|
@ -132,3 +132,250 @@ private fun CardCredits(modifier: Modifier = Modifier) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
//private val viewModel: SettingsViewModel by viewModels()
|
||||
//private val bluetooth = when {
|
||||
// Build.VERSION.SDK_INT < Build.VERSION_CODES.S -> Manifest.permission.BLUETOOTH
|
||||
// else -> Manifest.permission.BLUETOOTH_CONNECT
|
||||
//}
|
||||
//private val bluetoothContract = ActivityResultContracts.RequestPermission()
|
||||
//private val bluetoothRequest = registerForActivityResult(bluetoothContract) { isGranted ->
|
||||
// if (!isGranted) {
|
||||
// showToast(getString(R.string.BTremote_perm_error))
|
||||
// toggleBTstate(isGranted)
|
||||
// }
|
||||
//}
|
||||
//private val locationFine = Manifest.permission.ACCESS_FINE_LOCATION
|
||||
//private val locationCoarse = Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
//private val locationContract = ActivityResultContracts.RequestMultiplePermissions()
|
||||
//private val locationRequest = registerForActivityResult(locationContract) { permissions ->
|
||||
// when {
|
||||
// permissions[locationFine] == true -> viewModel.setPositionFromGps()
|
||||
// permissions[locationCoarse] == true -> viewModel.setPositionFromNet()
|
||||
// else -> showToast(getString(R.string.location_gps_error))
|
||||
// }
|
||||
//}
|
||||
//private val contentContract = ActivityResultContracts.GetContent()
|
||||
//private val contentRequest = registerForActivityResult(contentContract) { uri ->
|
||||
// uri?.let { viewModel.updateFromFile(uri.toString()) }
|
||||
//}
|
||||
//private lateinit var binding: FragmentSettingsBinding
|
||||
//
|
||||
//override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
// super.onViewCreated(view, savedInstanceState)
|
||||
// binding = FragmentSettingsBinding.bind(view).apply {
|
||||
// settingsBtnBack.clickWithDebounce { findNavController().navigateUp() }
|
||||
// settingsScroll.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, y, _, newY ->
|
||||
// if (y > newY) settingsFab.hide() else settingsFab.show()
|
||||
// })
|
||||
// settingsBtnGithub.clickWithDebounce {
|
||||
// gotoUrl("https://github.com/rt-bishop/Look4Sat/")
|
||||
// }
|
||||
// settingsFab.clickWithDebounce {
|
||||
// gotoUrl("https://ko-fi.com/rt_bishop")
|
||||
// }
|
||||
// settingsBtnFdroid.clickWithDebounce {
|
||||
// gotoUrl("https://f-droid.org/en/packages/com.rtbishop.look4sat/")
|
||||
// }
|
||||
// }
|
||||
// setupLocationCard()
|
||||
// setupDataCard()
|
||||
// setupRemoteCard()
|
||||
// setupBTCard()
|
||||
// setupOtherCard()
|
||||
// viewModel.stationPosition.asLiveData().observe(viewLifecycleOwner) { stationPos ->
|
||||
// stationPos?.let { handleStationPosition(stationPos) }
|
||||
// }
|
||||
// viewModel.getUpdateState().asLiveData().observe(viewLifecycleOwner) { updateState ->
|
||||
// updateState?.let { handleSatState(updateState) }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//private fun setupLocationCard() {
|
||||
// binding.run {
|
||||
// setPositionText(viewModel.getStationPosition())
|
||||
// settingsLocation.locationBtnGps.clickWithDebounce {
|
||||
// locationRequest.launch(arrayOf(locationFine, locationCoarse))
|
||||
// }
|
||||
// settingsLocation.locationBtnManual.clickWithDebounce {
|
||||
// val action = SettingsFragmentDirections.globalToPosition()
|
||||
// findNavController().navigate(action)
|
||||
// }
|
||||
// settingsLocation.locationBtnQth.clickWithDebounce {
|
||||
// val action = SettingsFragmentDirections.globalToLocator()
|
||||
// findNavController().navigate(action)
|
||||
// }
|
||||
// getNavResult<Pair<Double, Double>>(R.id.nav_settings, "position") { position ->
|
||||
// viewModel.setStationPosition(position.first, position.second)
|
||||
// }
|
||||
// getNavResult<String>(R.id.nav_settings, "locator") { locator ->
|
||||
// viewModel.setPositionFromQth(locator)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//private fun setupDataCard() {
|
||||
// binding.run {
|
||||
// setUpdateTime(viewModel.getLastUpdateTime())
|
||||
// settingsData.dataBtnWeb.clickWithDebounce { viewModel.updateFromWeb() }
|
||||
// settingsData.dataBtnFile.clickWithDebounce { contentRequest.launch("*/*") }
|
||||
// settingsData.dataBtnClear.clickWithDebounce { viewModel.clearAllData() }
|
||||
// viewModel.entriesTotal.observe(viewLifecycleOwner) { number ->
|
||||
// val entriesFormat = getString(R.string.data_entries)
|
||||
// settingsData.dataEntries.text = String.format(entriesFormat, number)
|
||||
// }
|
||||
// viewModel.radiosTotal.observe(viewLifecycleOwner) { number ->
|
||||
// val radiosFormat = getString(R.string.data_radios)
|
||||
// settingsData.dataRadios.text = String.format(radiosFormat, number)
|
||||
// }
|
||||
// getNavResult<List<String>>(R.id.nav_settings, "sources") {
|
||||
// viewModel.updateFromWeb()
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//private fun setupRemoteCard() {
|
||||
// binding.run {
|
||||
// settingsRemote.remoteSwitch.apply {
|
||||
// isChecked = viewModel.getRotatorEnabled()
|
||||
// settingsRemote.remoteIp.isEnabled = isChecked
|
||||
// settingsRemote.remoteIpEdit.setText(viewModel.getRotatorServer())
|
||||
// settingsRemote.remotePort.isEnabled = isChecked
|
||||
// settingsRemote.remotePortEdit.setText(viewModel.getRotatorPort())
|
||||
// setOnCheckedChangeListener { _, isChecked ->
|
||||
// viewModel.setRotatorEnabled(isChecked)
|
||||
// settingsRemote.remoteIp.isEnabled = isChecked
|
||||
// settingsRemote.remotePort.isEnabled = isChecked
|
||||
// }
|
||||
// }
|
||||
// settingsRemote.remoteIpEdit.doOnTextChanged { text, _, _, _ ->
|
||||
// if (text.toString().isValidIPv4()) viewModel.setRotatorServer(text.toString())
|
||||
// }
|
||||
// settingsRemote.remotePortEdit.doOnTextChanged { text, _, _, _ ->
|
||||
// if (text.toString().isValidPort()) viewModel.setRotatorPort(text.toString())
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//private fun setupBTCard() {
|
||||
// binding.run {
|
||||
// settingsBtremote.BTremoteSwitch.apply {
|
||||
// isChecked = viewModel.getBTEnabled()
|
||||
// settingsBtremote.BTremoteAddress.isEnabled = isChecked
|
||||
// settingsBtremote.BTAddressEdit.setText(viewModel.getBTDeviceAddr())
|
||||
// settingsBtremote.BTremoteFormat.isEnabled = isChecked
|
||||
// settingsBtremote.BTFormatEdit.setText(viewModel.getBTFormat())
|
||||
// setOnCheckedChangeListener { _, isChecked ->
|
||||
// toggleBTstate(isChecked)
|
||||
// bluetoothRequest.launch(bluetooth)
|
||||
// }
|
||||
// }
|
||||
// settingsBtremote.BTAddressEdit.doOnTextChanged { text, _, _, _ ->
|
||||
// viewModel.setBTDeviceAddr(text.toString())
|
||||
// }
|
||||
// settingsBtremote.BTFormatEdit.doOnTextChanged { text, _, _, _ ->
|
||||
// viewModel.setBTFormat(text.toString())
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//private fun toggleBTstate(value: Boolean) {
|
||||
// binding.run {
|
||||
// viewModel.setBTEnabled(value)
|
||||
// settingsBtremote.BTremoteSwitch.isChecked = value
|
||||
// settingsBtremote.BTremoteAddress.isEnabled = value
|
||||
// settingsBtremote.BTremoteFormat.isEnabled = value
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//private fun setupOtherCard() {
|
||||
// binding.run {
|
||||
// settingsOther.otherSwitchUtc.apply {
|
||||
// isChecked = viewModel.getUseUTC()
|
||||
// setOnCheckedChangeListener { _, isChecked -> viewModel.setUseUTC(isChecked) }
|
||||
// }
|
||||
// settingsOther.otherSwitchUpdate.apply {
|
||||
// isChecked = viewModel.getAutoUpdateEnabled()
|
||||
// setOnCheckedChangeListener { _, isChecked -> viewModel.setAutoUpdateEnabled(isChecked) }
|
||||
// }
|
||||
// settingsOther.otherSwitchSweep.apply {
|
||||
// isChecked = viewModel.getShowSweep()
|
||||
// setOnCheckedChangeListener { _, isChecked -> viewModel.setShowSweep(isChecked) }
|
||||
// }
|
||||
// settingsOther.otherSwitchSensors.apply {
|
||||
// isChecked = viewModel.getUseCompass()
|
||||
// setOnCheckedChangeListener { _, isChecked -> viewModel.setUseCompass(isChecked) }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//private fun handleStationPosition(pos: DataState<GeoPos>) {
|
||||
// when (pos) {
|
||||
// is DataState.Success -> {
|
||||
// setPositionText(pos.data)
|
||||
// binding.settingsLocation.locationProgress.isIndeterminate = false
|
||||
// viewModel.setPositionHandled()
|
||||
// showToast(getString(R.string.location_success))
|
||||
// }
|
||||
// is DataState.Error -> {
|
||||
// binding.settingsLocation.locationProgress.isIndeterminate = false
|
||||
// viewModel.setPositionHandled()
|
||||
// showToast(pos.message.toString())
|
||||
// }
|
||||
// DataState.Loading -> {
|
||||
// binding.settingsLocation.locationProgress.isIndeterminate = true
|
||||
// }
|
||||
// DataState.Handled -> {}
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//private fun setPositionText(geoPos: GeoPos) {
|
||||
// binding.run {
|
||||
// val latFormat = getString(R.string.location_lat)
|
||||
// val lonFormat = getString(R.string.location_lon)
|
||||
// settingsLocation.locationLat.text = String.format(latFormat, geoPos.lat)
|
||||
// settingsLocation.locationLon.text = String.format(lonFormat, geoPos.lon)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//private fun handleSatState(state: DataState<Long>) {
|
||||
// when (state) {
|
||||
// is DataState.Success -> {
|
||||
// binding.settingsData.dataProgress.isIndeterminate = false
|
||||
// setUpdateTime(state.data)
|
||||
// viewModel.setUpdateHandled()
|
||||
// if (state.data == 0L) {
|
||||
// showToast(getString(R.string.data_clear_success))
|
||||
// } else {
|
||||
// showToast(getString(R.string.data_success))
|
||||
// }
|
||||
// }
|
||||
// is DataState.Error -> {
|
||||
// binding.settingsData.dataProgress.isIndeterminate = false
|
||||
// viewModel.setUpdateHandled()
|
||||
// showToast(getString(R.string.data_error))
|
||||
// }
|
||||
// is DataState.Loading -> {
|
||||
// binding.settingsData.dataProgress.isIndeterminate = true
|
||||
// }
|
||||
// is DataState.Handled -> {}
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//private fun setUpdateTime(updateTime: Long) {
|
||||
// val updatePattern = getString(R.string.data_update)
|
||||
// val updateDate = if (updateTime == 0L) {
|
||||
// getString(R.string.pass_placeholder)
|
||||
// } else {
|
||||
// SimpleDateFormat("d MMM yyyy - HH:mm:ss", Locale.getDefault()).format(Date(updateTime))
|
||||
// }
|
||||
// binding.settingsData.dataUpdate.text = String.format(updatePattern, updateDate)
|
||||
//}
|
||||
//
|
||||
//private fun showToast(message: String) {
|
||||
// Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
|
||||
//}
|
||||
//
|
||||
//private fun gotoUrl(url: String) {
|
||||
// startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||
//}
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
package com.rtbishop.look4sat.presentation.settingsScreen
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import com.rtbishop.look4sat.domain.IDataRepository
|
||||
import com.rtbishop.look4sat.domain.ILocationManager
|
||||
import com.rtbishop.look4sat.domain.ISettingsManager
|
||||
|
@ -35,8 +34,8 @@ class SettingsViewModel @Inject constructor(
|
|||
private val settings: ISettingsManager
|
||||
) : ViewModel() {
|
||||
|
||||
val entriesTotal = repository.getEntriesTotal().asLiveData()
|
||||
val radiosTotal = repository.getRadiosTotal().asLiveData()
|
||||
val entriesTotal = repository.getEntriesTotal()
|
||||
val radiosTotal = repository.getRadiosTotal()
|
||||
|
||||
fun updateFromFile(uri: String) = repository.updateFromFile(uri)
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="512"
|
||||
android:viewportHeight="512">
|
||||
<path
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="287.97"
|
||||
android:viewportHeight="287.97">
|
||||
<path
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="128"
|
||||
android:viewportHeight="128">
|
||||
<path
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
style="@style/SurfaceCard">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/BTremote_title"
|
||||
style="@style/SettingsTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:text="@string/BTremote_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/BTremote_switch"
|
||||
style="@style/SettingsText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="42dp"
|
||||
android:text="@string/BTremote_switch"
|
||||
app:layout_constraintEnd_toEndOf="@+id/BTremote_title"
|
||||
app:layout_constraintStart_toStartOf="@+id/BTremote_title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/BTremote_title"
|
||||
app:trackTint="@color/textDisabled" />
|
||||
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/BTremote_address"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="@+id/BTremote_switch"
|
||||
app:layout_constraintStart_toStartOf="@+id/BTremote_switch"
|
||||
app:layout_constraintTop_toBottomOf="@id/BTremote_switch">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/BT_address_edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:hint="@string/BTremote_device_hint"
|
||||
android:textColorHint="@color/textMain" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/BTremote_format"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/BTremote_address"
|
||||
app:layout_constraintStart_toStartOf="@+id/BTremote_address"
|
||||
app:layout_constraintTop_toBottomOf="@id/BTremote_address">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/BT_format_edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:hint="@string/BTremote_output_hint"
|
||||
android:textColorHint="@color/textMain" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
|
@ -1,107 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
style="@style/SurfaceCard">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/data_title"
|
||||
style="@style/SettingsTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/data_title"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/data_progress"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/data_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/data_title"
|
||||
app:layout_constraintTop_toTopOf="@+id/data_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/data_update"
|
||||
style="@style/SettingsText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="@string/data_update"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
app:layout_constraintStart_toStartOf="@+id/data_title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/data_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/data_entries"
|
||||
style="@style/SettingsText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="@string/data_entries"
|
||||
app:layout_constraintEnd_toStartOf="@+id/data_radios"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
app:layout_constraintStart_toStartOf="@+id/data_update"
|
||||
app:layout_constraintTop_toBottomOf="@+id/data_update" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/data_radios"
|
||||
style="@style/SettingsText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/data_radios"
|
||||
app:layout_constraintBaseline_toBaselineOf="@+id/data_entries"
|
||||
app:layout_constraintEnd_toEndOf="@+id/data_progress"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/data_entries" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/data_btn_web"
|
||||
style="@style/NormalButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:text="@string/btn_web"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/data_btn_file"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/data_entries" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/data_btn_file"
|
||||
style="@style/NormalButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:text="@string/btn_file"
|
||||
app:layout_constraintBaseline_toBaselineOf="@+id/data_btn_web"
|
||||
app:layout_constraintEnd_toStartOf="@+id/data_btn_clear"
|
||||
app:layout_constraintStart_toEndOf="@+id/data_btn_web" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/data_btn_clear"
|
||||
style="@style/NormalButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:text="@string/btn_clear"
|
||||
app:layout_constraintBaseline_toBaselineOf="@+id/data_btn_web"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/data_btn_file" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
|
@ -1,99 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
style="@style/SurfaceCard">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/location_title"
|
||||
style="@style/SettingsTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/location_title"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/location_progress"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/location_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/location_title"
|
||||
app:layout_constraintTop_toTopOf="@+id/location_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/location_lat"
|
||||
style="@style/SettingsText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="@string/location_lat"
|
||||
app:layout_constraintEnd_toStartOf="@+id/location_lon"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
app:layout_constraintStart_toStartOf="@+id/location_title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/location_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/location_lon"
|
||||
style="@style/SettingsText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/location_lon"
|
||||
app:layout_constraintBaseline_toBaselineOf="@+id/location_lat"
|
||||
app:layout_constraintEnd_toEndOf="@+id/location_progress"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/location_lat" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/location_btn_gps"
|
||||
style="@style/NormalButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:text="@string/btn_gps"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/location_btn_manual"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/location_lat" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/location_btn_manual"
|
||||
style="@style/NormalButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:text="@string/btn_manual"
|
||||
app:layout_constraintBaseline_toBaselineOf="@+id/location_btn_gps"
|
||||
app:layout_constraintEnd_toStartOf="@+id/location_btn_qth"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/location_btn_gps" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/location_btn_qth"
|
||||
style="@style/NormalButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:text="@string/btn_qth"
|
||||
app:layout_constraintBaseline_toBaselineOf="@+id/location_btn_gps"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/location_btn_manual" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
|
@ -1,81 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
style="@style/SurfaceCard">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/other_title"
|
||||
style="@style/SettingsTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:text="@string/other_title"
|
||||
app:layout_constraintBottom_toTopOf="@+id/other_switch_utc"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/other_switch_utc"
|
||||
style="@style/SettingsText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:minHeight="42dp"
|
||||
android:text="@string/other_switch_utc"
|
||||
app:layout_constraintBottom_toTopOf="@+id/other_switch_update"
|
||||
app:layout_constraintEnd_toEndOf="@+id/other_title"
|
||||
app:layout_constraintStart_toStartOf="@+id/other_title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/other_title"
|
||||
app:trackTint="@color/textDisabled" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/other_switch_update"
|
||||
style="@style/SettingsText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="42dp"
|
||||
android:text="@string/other_switch_update"
|
||||
app:layout_constraintBottom_toTopOf="@+id/other_switch_sweep"
|
||||
app:layout_constraintEnd_toEndOf="@+id/other_switch_utc"
|
||||
app:layout_constraintStart_toStartOf="@+id/other_switch_utc"
|
||||
app:layout_constraintTop_toBottomOf="@+id/other_switch_utc"
|
||||
app:trackTint="@color/textDisabled" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/other_switch_sweep"
|
||||
style="@style/SettingsText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="42dp"
|
||||
android:text="@string/other_switch_sweep"
|
||||
app:layout_constraintBottom_toTopOf="@+id/other_switch_sensors"
|
||||
app:layout_constraintEnd_toEndOf="@+id/other_switch_update"
|
||||
app:layout_constraintStart_toStartOf="@+id/other_switch_update"
|
||||
app:layout_constraintTop_toBottomOf="@+id/other_switch_update"
|
||||
app:trackTint="@color/textDisabled" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/other_switch_sensors"
|
||||
style="@style/SettingsText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:minHeight="42dp"
|
||||
android:text="@string/other_switch_sensors"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/other_switch_sweep"
|
||||
app:layout_constraintStart_toStartOf="@+id/other_switch_sweep"
|
||||
app:layout_constraintTop_toBottomOf="@+id/other_switch_sweep"
|
||||
app:trackTint="@color/textDisabled" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
|
@ -1,79 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
style="@style/SurfaceCard">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/remote_title"
|
||||
style="@style/SettingsTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:text="@string/remote_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/remote_switch"
|
||||
style="@style/SettingsText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="42dp"
|
||||
android:text="@string/remote_switch"
|
||||
app:layout_constraintEnd_toEndOf="@+id/remote_title"
|
||||
app:layout_constraintStart_toStartOf="@+id/remote_title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/remote_title"
|
||||
app:trackTint="@color/textDisabled" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/remote_ip"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/remote_port"
|
||||
app:layout_constraintStart_toStartOf="@+id/remote_switch"
|
||||
app:layout_constraintTop_toBottomOf="@+id/remote_switch"
|
||||
app:layout_constraintWidth_percent="0.64">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/remote_ip_edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:digits="0123456789."
|
||||
android:hint="@string/remote_ip_hint"
|
||||
android:inputType="number"
|
||||
android:textColorHint="@color/textMain" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/remote_port"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/remote_ip"
|
||||
app:layout_constraintEnd_toEndOf="@+id/remote_switch"
|
||||
app:layout_constraintStart_toEndOf="@+id/remote_ip"
|
||||
app:layout_constraintTop_toTopOf="@+id/remote_ip">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/remote_port_edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/remote_port_hint"
|
||||
android:inputType="number"
|
||||
android:textColorHint="@color/textMain" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
|
@ -1,236 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:transitionGroup="true"
|
||||
android:keepScreenOn="true">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/radar_toolbar"
|
||||
style="@style/Toolbar"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/radar_btn_back"
|
||||
style="@style/ToolbarButton"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:contentDescription="@string/btn_back"
|
||||
android:src="@drawable/ic_prev" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/radar_timer"
|
||||
style="@style/ToolbarTimer" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/radar_btn_map"
|
||||
style="@style/ToolbarButton"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:contentDescription="@string/btn_map"
|
||||
android:src="@drawable/ic_world" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/radar_card"
|
||||
style="@style/SurfaceCard"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="@dimen/view_default_margin"
|
||||
android:layout_marginTop="@dimen/view_default_margin"
|
||||
android:layout_marginEnd="@dimen/view_default_margin"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/radar_toolbar" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/radar_az_value"
|
||||
style="@style/RadarValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/radar_az_value"
|
||||
app:layout_constraintStart_toStartOf="@+id/radar_card"
|
||||
app:layout_constraintTop_toTopOf="@+id/radar_card" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/radar_az"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/radar_az_text"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
app:layout_constraintStart_toStartOf="@+id/radar_az_value"
|
||||
app:layout_constraintTop_toBottomOf="@+id/radar_az_value" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/radar_el_value"
|
||||
style="@style/RadarValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="@string/radar_el_value"
|
||||
app:layout_constraintBaseline_toBaselineOf="@+id/radar_az_value"
|
||||
app:layout_constraintEnd_toEndOf="@+id/radar_card" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/radar_el"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/radar_el_text"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
app:layout_constraintBaseline_toBaselineOf="@+id/radar_az"
|
||||
app:layout_constraintEnd_toEndOf="@+id/radar_el_value" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/radar_alt_value"
|
||||
style="@style/RadarValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="@string/radar_alt_value"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/radar_card"
|
||||
app:layout_constraintStart_toStartOf="@+id/radar_az_value" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/radar_alt"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/radar_alt_text"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
app:layout_constraintBottom_toTopOf="@+id/radar_alt_value"
|
||||
app:layout_constraintStart_toStartOf="@+id/radar_alt_value" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/radar_dst_value"
|
||||
style="@style/RadarValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/radar_dist_value"
|
||||
app:layout_constraintBaseline_toBaselineOf="@+id/radar_alt_value"
|
||||
app:layout_constraintEnd_toEndOf="@+id/radar_el_value" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/radar_dst"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/radar_dist_text"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
app:layout_constraintBaseline_toBaselineOf="@+id/radar_alt"
|
||||
app:layout_constraintEnd_toEndOf="@+id/radar_dst_value" />
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/radar_list_card"
|
||||
style="@style/SurfaceCard"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="@dimen/view_default_margin"
|
||||
android:layout_marginTop="@dimen/view_default_margin"
|
||||
android:layout_marginEnd="@dimen/view_default_margin"
|
||||
android:layout_marginBottom="@dimen/view_default_margin"
|
||||
app:layout_constraintBottom_toTopOf="@+id/radar_coordinator"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/radar_card">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/radar_recycler"
|
||||
style="@style/Recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/radar_progress"
|
||||
style="@style/RecyclerProgress" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/radar_empty_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="invisible"
|
||||
tools:ignore="UseCompoundDrawables">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/radar_empty_img"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:contentDescription="@string/radio_no_data"
|
||||
android:src="@drawable/ic_satellite" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/radar_empty_msg"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:text="@string/radio_no_data"
|
||||
android:textSize="@dimen/text_size_mediumLarge" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/radar_coordinator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<com.google.android.material.bottomappbar.BottomAppBar
|
||||
android:id="@+id/radar_bottom_bar"
|
||||
style="@style/BottomBar" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/radar_btn_notify"
|
||||
style="@style/NormalButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_marginStart="@dimen/button_margin_side"
|
||||
android:text="@string/btn_notify"
|
||||
android:textColor="@color/textDisabled"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/radar_coordinator"
|
||||
app:layout_constraintEnd_toStartOf="@+id/radar_visibility"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/radar_coordinator"
|
||||
app:layout_constraintStart_toStartOf="@+id/radar_coordinator"
|
||||
app:layout_constraintTop_toTopOf="@+id/radar_coordinator" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/radar_visibility"
|
||||
style="@style/WorldMapText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_horizontal"
|
||||
android:maxLines="1"
|
||||
android:text="@string/radar_visible"
|
||||
app:layout_constraintBaseline_toBaselineOf="@+id/radar_btn_notify"
|
||||
app:layout_constraintEnd_toStartOf="@+id/radar_btn_settings"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/radar_btn_notify" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/radar_btn_settings"
|
||||
style="@style/NormalButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_marginEnd="@dimen/button_margin_side"
|
||||
android:text="@string/btn_settings"
|
||||
app:layout_constraintBaseline_toBaselineOf="@+id/radar_btn_notify"
|
||||
app:layout_constraintEnd_toEndOf="@+id/radar_coordinator"
|
||||
app:layout_constraintStart_toEndOf="@+id/radar_visibility" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,131 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:transitionGroup="true">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/settings_toolbar"
|
||||
style="@style/Toolbar">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/settings_btn_back"
|
||||
style="@style/ToolbarButton"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:contentDescription="@string/btn_back"
|
||||
android:src="@drawable/ic_prev" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/settings_title"
|
||||
style="@style/ToolbarTitle"
|
||||
android:text="@string/btn_settings" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
style="@style/Toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="@dimen/view_default_margin"
|
||||
android:layout_marginTop="@dimen/surface_margin_top"
|
||||
android:layout_marginEnd="@dimen/view_default_margin"
|
||||
android:layout_marginBottom="@dimen/surface_margin_bot"
|
||||
android:backgroundTint="@color/background">
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/settings_scroll"
|
||||
style="@style/Recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<include
|
||||
android:id="@+id/settings_location"
|
||||
layout="@layout/card_location"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/view_default_margin"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<include
|
||||
android:id="@+id/settings_data"
|
||||
layout="@layout/card_data"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/view_default_margin"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/settings_location" />
|
||||
|
||||
<include
|
||||
android:id="@+id/settings_remote"
|
||||
layout="@layout/card_remote"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/view_default_margin"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/settings_data" />
|
||||
|
||||
<include
|
||||
android:id="@+id/settings_btremote"
|
||||
layout="@layout/card_btremote"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/view_default_margin"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/settings_remote" />
|
||||
|
||||
<include
|
||||
android:id="@+id/settings_other"
|
||||
layout="@layout/card_other"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/view_default_margin"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/settings_btremote" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<com.google.android.material.bottomappbar.BottomAppBar
|
||||
android:id="@+id/settings_bottom_bar"
|
||||
style="@style/BottomBar">
|
||||
|
||||
<Button
|
||||
android:id="@+id/settings_btn_github"
|
||||
style="@style/NormalButton"
|
||||
android:layout_width="@dimen/button_width_max"
|
||||
android:layout_marginStart="@dimen/button_margin_side"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:text="@string/btn_github" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/settings_btn_fdroid"
|
||||
style="@style/NormalButton"
|
||||
android:layout_width="@dimen/button_width_max"
|
||||
android:layout_marginEnd="@dimen/button_margin_side"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:text="@string/btn_fdroid" />
|
||||
|
||||
</com.google.android.material.bottomappbar.BottomAppBar>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/settings_fab"
|
||||
style="@style/FloatingActionButton"
|
||||
android:src="@drawable/ic_checkmark"
|
||||
android:contentDescription="@string/btn_donate"
|
||||
app:layout_anchor="@id/settings_bottom_bar" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -1,103 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
style="@style/RecyclerItemCard">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/radio_guide"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.5" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/radio_info"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:text="@string/radio_info"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
app:layout_constraintEnd_toStartOf="@+id/radio_up_img"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
app:layout_constraintStart_toEndOf="@+id/radio_down_img"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/radio_down_img"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:contentDescription="@string/radio_downlink"
|
||||
android:rotation="90"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/radio_info"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/radio_info"
|
||||
app:srcCompat="@drawable/ic_next" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/radio_downlink"
|
||||
style="@style/RadioFreq"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginTop="3dp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/radio_guide"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/radio_info" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/radio_up_img"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:contentDescription="@string/radio_uplink"
|
||||
android:rotation="270"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/radio_info"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/radio_info"
|
||||
app:srcCompat="@drawable/ic_next" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/radio_uplink"
|
||||
style="@style/RadioFreq"
|
||||
android:layout_marginEnd="4dp"
|
||||
app:layout_constraintBaseline_toBaselineOf="@+id/radio_downlink"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/radio_guide" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/radio_mode"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="@string/radio_mode"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/radio_inverted"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/radio_downlink" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/radio_inverted"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="@string/radio_inverted"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
app:layout_constraintBaseline_toBaselineOf="@+id/radio_mode"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/radio_mode" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.osmdroid.views.MapView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/osm_map_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
Przed Szerokość: | Wysokość: | Rozmiar: 2.2 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 3.8 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 1.5 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 2.4 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 2.5 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 5.2 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 3.1 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 5.4 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 4.8 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 8.5 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 6.6 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 12 KiB |
|
@ -1,175 +0,0 @@
|
|||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<style name="Theme.Look4Sat.SplashScreen" parent="Theme.SplashScreen">
|
||||
<item name="android:forceDarkAllowed" tools:targetApi="q">false</item>
|
||||
<item name="postSplashScreenTheme">@style/Theme.Look4Sat.Main</item>
|
||||
<item name="windowSplashScreenBackground">@color/background</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Look4Sat.Main" parent="Theme.MaterialComponents.NoActionBar">
|
||||
<item name="colorAccent">@color/accent</item>
|
||||
<item name="colorControlNormal">@color/textMain</item>
|
||||
<item name="colorPrimary">@color/accent</item>
|
||||
<item name="colorSecondary">@color/accent</item>
|
||||
<!-- Bottom bar colors-->
|
||||
<!-- <item name="colorOnSecondaryContainer">@color/background</item>-->
|
||||
<!-- <item name="colorOnSurfaceVariant">@color/textMain</item>-->
|
||||
<!-- <item name="colorSecondaryContainer">@color/accent</item>-->
|
||||
<!-- <item name="colorOnPrimaryContainer">@color/textMain</item>-->
|
||||
<!-- -->
|
||||
<item name="android:forceDarkAllowed" tools:targetApi="q">false</item>
|
||||
<item name="android:navigationBarColor">@color/background</item>
|
||||
<item name="android:textColorPrimary">@color/textMain</item>
|
||||
<item name="android:statusBarColor">@color/background</item>
|
||||
<item name="android:windowBackground">@color/background</item>
|
||||
</style>
|
||||
|
||||
<style name="Toolbar">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">@dimen/toolbar_size</item>
|
||||
<item name="android:layout_gravity">top|center_horizontal</item>
|
||||
<item name="android:layout_marginStart">@dimen/view_default_margin</item>
|
||||
<item name="android:layout_marginTop">@dimen/view_default_margin</item>
|
||||
<item name="android:layout_marginEnd">@dimen/view_default_margin</item>
|
||||
<item name="cardBackgroundColor">@color/toolbar</item>
|
||||
<item name="cardCornerRadius">@dimen/card_corner_high</item>
|
||||
<item name="cardElevation">@dimen/card_elev_low</item>
|
||||
</style>
|
||||
|
||||
<style name="ToolbarButton">
|
||||
<item name="android:layout_width">@dimen/toolbar_icon_size</item>
|
||||
<item name="android:layout_height">@dimen/toolbar_icon_size</item>
|
||||
<item name="android:background">?actionBarItemBackground</item>
|
||||
<item name="android:tint">@color/textMain</item>
|
||||
</style>
|
||||
|
||||
<style name="ToolbarTimer">
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:layout_gravity">center</item>
|
||||
<item name="android:includeFontPadding">false</item>
|
||||
<item name="android:text">@string/app_timer</item>
|
||||
<item name="android:textColor">@color/accent</item>
|
||||
<item name="android:textSize">@dimen/text_size_app_timer</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
</style>
|
||||
|
||||
<style name="ToolbarTitle">
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:layout_gravity">center</item>
|
||||
<item name="android:background">@color/transparent</item>
|
||||
<item name="android:gravity">center</item>
|
||||
<item name="android:maxLines">1</item>
|
||||
<item name="android:ellipsize">end</item>
|
||||
<item name="android:includeFontPadding">false</item>
|
||||
<item name="android:textColor">@color/accent</item>
|
||||
<item name="android:textColorHint">@color/accent</item>
|
||||
<item name="android:textSize">@dimen/text_size_large</item>
|
||||
</style>
|
||||
|
||||
<style name="FloatingActionButton">
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:backgroundTint">@color/accent</item>
|
||||
</style>
|
||||
|
||||
<style name="NormalButton" parent="Widget.AppCompat.Button">
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:backgroundTint">@color/buttonRegular</item>
|
||||
<item name="android:ellipsize">end</item>
|
||||
<item name="android:minWidth">96dp</item>
|
||||
<item name="android:maxLines">1</item>
|
||||
<item name="android:textAllCaps">false</item>
|
||||
<item name="android:textColor">@color/textMain</item>
|
||||
<item name="android:textSize">@dimen/text_size_medium</item>
|
||||
<item name="cornerRadius">@dimen/btn_corner_high</item>
|
||||
<item name="iconTint">@color/textMain</item>
|
||||
<item name="rippleColor">@color/textMain</item>
|
||||
</style>
|
||||
|
||||
<style name="RadioFreq">
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:includeFontPadding">false</item>
|
||||
<item name="android:text">@string/radio_link_low</item>
|
||||
<item name="android:textColor">@color/accent</item>
|
||||
<item name="android:textSize">@dimen/text_size_frequency</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
</style>
|
||||
|
||||
<style name="Recycler">
|
||||
<item name="android:overScrollMode">never</item>
|
||||
<item name="android:scrollbars">none</item>
|
||||
</style>
|
||||
|
||||
<style name="RecyclerProgress">
|
||||
<item name="android:layout_width">72dp</item>
|
||||
<item name="android:layout_height">72dp</item>
|
||||
<item name="android:layout_gravity">center</item>
|
||||
</style>
|
||||
|
||||
<style name="RecyclerItemCard">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="cardBackgroundColor">@color/cardRegular</item>
|
||||
<item name="cardCornerRadius">@dimen/card_corner_low</item>
|
||||
<item name="cardElevation">@dimen/card_elev_low</item>
|
||||
</style>
|
||||
|
||||
<style name="SurfaceCard">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="cardBackgroundColor">@color/cardRegular</item>
|
||||
<item name="cardCornerRadius">@dimen/card_corner_high</item>
|
||||
<item name="cardElevation">@dimen/card_elev_low</item>
|
||||
</style>
|
||||
|
||||
<style name="BottomBar">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">@dimen/bottom_bar_size</item>
|
||||
<item name="android:layout_gravity">bottom</item>
|
||||
<item name="android:layout_marginBottom">3dp</item>
|
||||
<item name="android:backgroundTint">@color/bottomBar</item>
|
||||
<item name="contentInsetStart">@dimen/bottom_bar_inset</item>
|
||||
<item name="contentInsetEnd">@dimen/bottom_bar_inset</item>
|
||||
<item name="fabCradleMargin">@dimen/view_default_margin</item>
|
||||
<item name="fabCradleRoundedCornerRadius">@dimen/view_default_margin</item>
|
||||
</style>
|
||||
|
||||
<style name="RadarValue">
|
||||
<item name="android:textSize">@dimen/text_size_large</item>
|
||||
<item name="android:textColor">@color/textMain</item>
|
||||
<item name="android:includeFontPadding">false</item>
|
||||
</style>
|
||||
|
||||
<style name="WorldMapText">
|
||||
<item name="android:textSize">@dimen/text_size_medium</item>
|
||||
<item name="android:textColor">@color/accent</item>
|
||||
</style>
|
||||
|
||||
<style name="SettingsText">
|
||||
<item name="android:textSize">@dimen/text_size_medium</item>
|
||||
<item name="android:textColor">@color/textMain</item>
|
||||
</style>
|
||||
|
||||
<style name="SettingsTitle">
|
||||
<item name="android:includeFontPadding">false</item>
|
||||
<item name="android:textSize">@dimen/text_size_medium</item>
|
||||
<item name="android:textColor">@color/accent</item>
|
||||
</style>
|
||||
|
||||
<style name="DialogText">
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:textColor">@color/textMain</item>
|
||||
<item name="android:textSize">@dimen/text_size_medium</item>
|
||||
</style>
|
||||
|
||||
<style name="DialogTitle" parent="ToolbarTitle">
|
||||
<item name="android:textSize">@dimen/text_size_mediumLarge</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,18 @@
|
|||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<style name="Theme.Look4Sat.SplashScreen" parent="Theme.SplashScreen">
|
||||
<item name="android:forceDarkAllowed" tools:targetApi="q">false</item>
|
||||
<item name="postSplashScreenTheme">@style/Theme.Look4Sat.Main</item>
|
||||
<item name="windowSplashScreenBackground">@color/background</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Look4Sat.Main" parent="android:Theme.Material.Light.NoActionBar">
|
||||
<item name="android:forceDarkAllowed" tools:targetApi="q">false</item>
|
||||
<item name="android:navigationBarColor">@color/background</item>
|
||||
<item name="android:textColorPrimary">@color/textMain</item>
|
||||
<item name="android:statusBarColor">@color/background</item>
|
||||
<item name="android:windowBackground">@color/background</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample backup rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/guide/topics/data/autobackup
|
||||
for details.
|
||||
Note: This file is ignored for devices older that API 31
|
||||
See https://developer.android.com/about/versions/12/backup-restore
|
||||
-->
|
||||
<full-backup-content>
|
||||
<!--
|
||||
<include domain="sharedpref" path="."/>
|
||||
<exclude domain="sharedpref" path="device.xml"/>
|
||||
-->
|
||||
</full-backup-content>
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample data extraction rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||
for details.
|
||||
-->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
-->
|
||||
</cloud-backup>
|
||||
<!--
|
||||
<device-transfer>
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
</device-transfer>
|
||||
-->
|
||||
</data-extraction-rules>
|
|
@ -5,4 +5,4 @@
|
|||
<certificates src="user" />
|
||||
</trust-anchors>
|
||||
</debug-overrides>
|
||||
</network-security-config>
|
||||
</network-security-config>
|
||||
|
|
28
build.gradle
|
@ -1,31 +1,29 @@
|
|||
buildscript {
|
||||
ext {
|
||||
hilt_version = '2.45'
|
||||
hilt_compose_version = '1.0.0'
|
||||
application_version = '7.4.2'
|
||||
library_version = '7.1.2'
|
||||
kotlin_android_version = '1.8.10'
|
||||
core_ktx_version = '1.9.0'
|
||||
core_splashscreen_version = '1.0.0'
|
||||
constraint_version = '2.1.4'
|
||||
hilt_android_version = '2.45'
|
||||
// Android
|
||||
splashscreen_version = '1.0.0'
|
||||
room_version = '2.5.0'
|
||||
// Compose
|
||||
activity_compose_version = '1.6.1'
|
||||
compose_version = '1.3.3'
|
||||
compose_compiler_version = '1.4.3'
|
||||
material3_version = '1.0.1'
|
||||
hilt_compose_version = '1.0.0'
|
||||
lifecycle_version = '2.5.1'
|
||||
navigation_version = '2.5.3'
|
||||
work_version = '2.7.1'
|
||||
room_version = '2.5.0'
|
||||
// Utility
|
||||
timber_version = '5.0.1'
|
||||
osmdroid_version = '6.1.14'
|
||||
json_version = '20220924'
|
||||
compose_version = '1.3.3'
|
||||
compose_compiler_version = '1.4.3'
|
||||
activity_compose_version = '1.6.1'
|
||||
material_version = '1.3.1'
|
||||
material3_version = '1.0.1'
|
||||
leakcanary_version = '2.10'
|
||||
// Test
|
||||
junit_version = '4.13.2'
|
||||
mockito_version = '5.1.1'
|
||||
coroutines_test_version = '1.6.4'
|
||||
androidx_test_version = '1.5.2'
|
||||
androidx_junit_version = '1.1.5'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,7 +31,7 @@ plugins {
|
|||
id "com.android.application" version "$application_version" apply false
|
||||
id "com.android.library" version "$library_version" apply false
|
||||
id "org.jetbrains.kotlin.android" version "$kotlin_android_version" apply false
|
||||
id "com.google.dagger.hilt.android" version "$hilt_version" apply false
|
||||
id "com.google.dagger.hilt.android" version "$hilt_android_version" apply false
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
|
|