mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 19:17:21 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7282a3290 | ||
|
|
2abe0c4582 | ||
|
|
c2081e93b8 | ||
|
|
c9d7f5df09 | ||
|
|
c910e37d00 | ||
|
|
c0debc038a | ||
|
|
c3e2c75b60 | ||
|
|
f4c7814061 | ||
|
|
3a163fb939 | ||
|
|
e41ac8f367 | ||
|
|
04da45e278 | ||
|
|
b1562b4796 | ||
|
|
b5adc73a6d | ||
|
|
4fc76ecbd9 | ||
|
|
fb296786a5 | ||
|
|
e41cee61cf | ||
|
|
c9d0c414d0 | ||
|
|
b156f666ef | ||
|
|
baf61ca9bc | ||
|
|
1685691d2f | ||
|
|
e531eb85e3 | ||
|
|
ccad82aa6d | ||
|
|
836fac2bb7 | ||
|
|
216f96caef | ||
|
|
c5c8df0b5e | ||
|
|
b9a25cc941 | ||
|
|
bbb89b7e6b | ||
|
|
bf1ac5fc2c | ||
|
|
73557d30f2 | ||
|
|
0de157f3e2 | ||
|
|
9529f74c48 | ||
|
|
378c3ac4bf | ||
|
|
e4a4b7eeb1 | ||
|
|
7653de2cee | ||
|
|
53697ef0c4 | ||
|
|
8c9d2031c2 | ||
|
|
a006d0558a | ||
|
|
278c06170b | ||
|
|
a2f37e6b9f | ||
|
|
93fa038b36 | ||
|
|
9f10f1d2e4 | ||
|
|
dd76dcfc98 | ||
|
|
b35c3a0e61 | ||
|
|
47a83a8bfe | ||
|
|
de2978138a | ||
|
|
abb1bd4718 | ||
|
|
ec553bf429 | ||
|
|
488fe352cd | ||
|
|
7fdc939450 | ||
|
|
ae08c7c937 | ||
|
|
4ee74a6789 | ||
|
|
5804f42211 | ||
|
|
29c3b2f2f1 | ||
|
|
20bf1cedd0 | ||
|
|
8a24c50448 | ||
|
|
3b761f9589 | ||
|
|
20306ec83e | ||
|
|
930e05cc58 | ||
|
|
acc308379c | ||
|
|
65941a8304 | ||
|
|
c58cf94181 | ||
|
|
88228bfad7 | ||
|
|
550ffdf92d | ||
|
|
89be2dfa43 | ||
|
|
03323172ea | ||
|
|
74cbad5127 | ||
|
|
0c71c4db29 | ||
|
|
6189fb497e | ||
|
|
00549bbcba | ||
|
|
9fd0d5f2e2 | ||
|
|
567200cfe4 | ||
|
|
0404f7be90 | ||
|
|
afe9f77869 | ||
|
|
72f78e5c6b | ||
|
|
ffd1fc1551 | ||
|
|
9d6dbc8d31 | ||
|
|
6400d1d395 | ||
|
|
dc680231ea | ||
|
|
97e6f8b7f5 | ||
|
|
22d0db1d78 | ||
|
|
bdc0000476 | ||
|
|
64b5010a44 | ||
|
|
92eec290b1 | ||
|
|
a7c840d532 | ||
|
|
80ba3644f7 | ||
|
|
a0c6ceef15 | ||
|
|
8b8c443bd1 | ||
|
|
72f71b2153 | ||
|
|
7349ee6c5e | ||
|
|
04c4ef25d2 | ||
|
|
b5da3d3ac4 | ||
|
|
28a6fecab3 | ||
|
|
7eb25e45b3 | ||
|
|
e9dc166e1e | ||
|
|
a7427f3632 | ||
|
|
9b38e2c63e | ||
|
|
61cbc212b5 | ||
|
|
27156b5070 | ||
|
|
b8bb6d41f9 | ||
|
|
794d132086 | ||
|
|
608b7e9311 | ||
|
|
b0b2283ca5 | ||
|
|
38787b45c1 | ||
|
|
f9ebe84012 | ||
|
|
f34cb8eb3c | ||
|
|
148104880e | ||
|
|
f559c60154 | ||
|
|
82032109dd | ||
|
|
7d3d746c4d | ||
|
|
a9a4aa9737 | ||
|
|
eebe691419 | ||
|
|
6926f87747 | ||
|
|
971379b773 | ||
|
|
ad3bdff823 | ||
|
|
9c137ada9f | ||
|
|
0f4a70b204 | ||
|
|
f58f851151 | ||
|
|
be26b3e1dc | ||
|
|
9ca8ec65c4 |
No files matched your search
+15
-12
@@ -3,18 +3,20 @@ name: Look4Sat CI
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- '*'
|
- v**
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout latest commit
|
- name: Checkout Latest Commit
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup java environment
|
- name: Setup java environment
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v4
|
||||||
with: { java-version: 11 }
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: '17'
|
||||||
|
|
||||||
- name: Assemble APK and Bundle
|
- name: Assemble APK and Bundle
|
||||||
run: |
|
run: |
|
||||||
@@ -32,7 +34,7 @@ jobs:
|
|||||||
alias: ${{ secrets.KEY_ALIAS }}
|
alias: ${{ secrets.KEY_ALIAS }}
|
||||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||||
env:
|
env:
|
||||||
BUILD_TOOLS_VERSION: "31.0.0"
|
BUILD_TOOLS_VERSION: "35.0.1"
|
||||||
|
|
||||||
- name: Sign Bundle
|
- name: Sign Bundle
|
||||||
uses: r0adkll/sign-android-release@v1
|
uses: r0adkll/sign-android-release@v1
|
||||||
@@ -44,24 +46,25 @@ jobs:
|
|||||||
alias: ${{ secrets.KEY_ALIAS }}
|
alias: ${{ secrets.KEY_ALIAS }}
|
||||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||||
env:
|
env:
|
||||||
BUILD_TOOLS_VERSION: "31.0.0"
|
BUILD_TOOLS_VERSION: "35.0.1"
|
||||||
|
|
||||||
- name: Rename APK
|
- name: Rename APK and Bundle
|
||||||
run: |
|
run: |
|
||||||
mv ${{steps.sign_apk.outputs.signedReleaseFile}} app/build/outputs/apk/release/look4sat.apk
|
mv ${{steps.sign_apk.outputs.signedReleaseFile}} app/build/outputs/apk/release/look4sat.apk
|
||||||
|
mv ${{steps.sign_bundle.outputs.signedReleaseFile}} app/build/outputs/apk/release/look4sat.aab
|
||||||
|
|
||||||
- name: Deploy Bundle to Play Store
|
- name: Deploy Bundle to Play Store
|
||||||
uses: r0adkll/upload-google-play@v1.0.15
|
uses: r0adkll/upload-google-play@v1
|
||||||
with:
|
with:
|
||||||
serviceAccountJsonPlainText: ${{secrets.SERVICE_ACCOUNT_JSON}}
|
serviceAccountJsonPlainText: ${{secrets.SERVICE_ACCOUNT_JSON}}
|
||||||
packageName: com.rtbishop.look4sat
|
packageName: com.rtbishop.look4sat
|
||||||
track: production
|
releaseFiles: app/build/outputs/apk/release/look4sat.aab
|
||||||
|
track: beta
|
||||||
whatsNewDirectory: fastlane/metadata/android/en-US/whatsnew
|
whatsNewDirectory: fastlane/metadata/android/en-US/whatsnew
|
||||||
releaseFiles: ${{steps.sign_bundle.outputs.signedReleaseFile}}
|
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
uses: ncipollo/release-action@v1
|
uses: ncipollo/release-action@v1
|
||||||
with:
|
with:
|
||||||
artifacts: app/build/outputs/apk/release/look4sat.apk
|
artifacts: "app/build/outputs/apk/release/look4sat.apk,app/build/outputs/apk/release/look4sat.aab"
|
||||||
bodyFile: fastlane/metadata/android/en-US/whatsnew/whatsnew-en-US
|
bodyFile: fastlane/metadata/android/en-US/whatsnew/whatsnew-en-US
|
||||||
token: ${{ secrets.RELEASE_TOKEN }}
|
token: ${{ secrets.RELEASE_TOKEN }}
|
||||||
@@ -8,23 +8,25 @@
|
|||||||
### Radio satellite tracker and pass predictor for Android, inspired by Gpredict
|
### Radio satellite tracker and pass predictor for Android, inspired by Gpredict
|
||||||
|
|
||||||
<p float="left">
|
<p float="left">
|
||||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/1.png" width="160"/>
|
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/1.png" width="180"/>
|
||||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/2.png" width="160"/>
|
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/2.png" width="180"/>
|
||||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/3.png" width="160"/>
|
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/3.png" width="180"/>
|
||||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/4.png" width="160">
|
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/4.png" width="180">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
### Track satellite passes with ease!
|
### Track satellite passes with ease!
|
||||||
|
|
||||||
Thanks to the huge database provided by Celestrak and SatNOGS you have access to more than 3000 active satellites orbiting Earth. You can search the entire DB by satellite name or by NORAD catnum.
|
Thanks to the huge database provided by [Celestrak](https://celestrak.com/) and [SatNOGS](https://satnogs.org/) you have access to more than 5000 active satellites orbiting Earth. You can search the entire DB by satellite name or by NORAD catnum.
|
||||||
|
|
||||||
Satellite positions and passes are calculated relative to your location. To get reliable info make sure to set the observation position using GPS or QTH Locator in the Settings menu.
|
Satellite positions and passes are calculated relative to your location. To get reliable info make sure to set the observation position using GPS or QTH Locator in the Settings menu.
|
||||||
|
|
||||||
The application is built using Kotlin, Coroutines, Architecture Components and Jetpack Navigation. It is now and always will be completely ad-free and open-source.
|
The application is built using Kotlin, Coroutines, Architecture Components and Jetpack Navigation. It is now and always will be completely ad-free and open-source.
|
||||||
|
|
||||||
|
Huge thanks to [DownloadAstro](https://appoftheday.downloadastro.com/app/look4sat-satellite-tracker/) team for their interest to the app and the interview published.
|
||||||
|
|
||||||
## Main features:
|
## Main features:
|
||||||
|
|
||||||
* Predicting satellite positions and passes for up to 4 days (96 hours)
|
* Predicting satellite positions and passes for up to a week
|
||||||
* Showing the list of currently active and upcoming satellite passes
|
* Showing the list of currently active and upcoming satellite passes
|
||||||
* Showing the active pass progress, polar trajectory and transceivers info
|
* Showing the active pass progress, polar trajectory and transceivers info
|
||||||
* Showing the satellite positional data, footprint and ground track on a map
|
* Showing the satellite positional data, footprint and ground track on a map
|
||||||
|
|||||||
+71
-40
@@ -1,21 +1,22 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "com.android.application"
|
id "com.android.application"
|
||||||
id "kotlin-android"
|
id "org.jetbrains.kotlin.android"
|
||||||
id "kotlin-kapt"
|
id "org.jetbrains.kotlin.kapt"
|
||||||
id "dagger.hilt.android.plugin"
|
id "dagger.hilt.android.plugin"
|
||||||
|
id "androidx.navigation.safeargs.kotlin"
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 31
|
namespace 'com.rtbishop.look4sat'
|
||||||
buildToolsVersion "31.0.0"
|
compileSdk 35
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.rtbishop.look4sat"
|
applicationId "com.rtbishop.look4sat"
|
||||||
minSdkVersion 21
|
minSdk 21
|
||||||
resConfigs "en,ru"
|
targetSdk 35
|
||||||
targetSdkVersion 31
|
versionCode 323
|
||||||
versionCode 260
|
versionName '3.2.3'
|
||||||
versionName "2.6.0"
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
kapt {
|
kapt {
|
||||||
arguments {
|
arguments {
|
||||||
@@ -25,59 +26,89 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// def props = new Properties()
|
|
||||||
// file("../securityKeys.properties").withInputStream { props.load(it) }
|
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
// buildConfigField "String", "ApiKey", props.getProperty("apiKey")
|
minifyEnabled true
|
||||||
// minifyEnabled true
|
shrinkResources true
|
||||||
// shrinkResources true
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
||||||
postprocessing {
|
|
||||||
removeUnusedCode true
|
|
||||||
removeUnusedResources true
|
|
||||||
obfuscate false
|
|
||||||
optimizeCode true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
|
// compose true
|
||||||
viewBinding true
|
viewBinding true
|
||||||
}
|
}
|
||||||
|
composeOptions {
|
||||||
|
kotlinCompilerExtensionVersion compose_compiler_version
|
||||||
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_11
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
targetCompatibility JavaVersion.VERSION_11
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||||
}
|
}
|
||||||
packagingOptions {
|
testOptions {
|
||||||
resources.excludes += "DebugProbesKt.bin"
|
animationsDisabled true
|
||||||
|
unitTests.includeAndroidResources true
|
||||||
}
|
}
|
||||||
|
androidResources {
|
||||||
|
generateLocaleConfig true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvmToolchain(17)
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':core')
|
implementation project(":base")
|
||||||
implementation "androidx.core:core-splashscreen:$splash_screen_version"
|
implementation "androidx.core:core-ktx:$core_ktx_version"
|
||||||
implementation "androidx.constraintlayout:constraintlayout:$constraint_layout_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-runtime-ktx:$lifecycle_version"
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||||
|
implementation "androidx.navigation:navigation-runtime-ktx:$navigation_version"
|
||||||
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
|
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
|
||||||
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
|
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
|
||||||
implementation "androidx.preference:preference-ktx:$preference_version"
|
|
||||||
implementation "androidx.room:room-runtime:$room_version"
|
implementation "androidx.room:room-runtime:$room_version"
|
||||||
implementation "androidx.room:room-ktx:$room_version"
|
implementation "androidx.room:room-ktx:$room_version"
|
||||||
kapt "androidx.room:room-compiler:$room_version"
|
kapt "androidx.room:room-compiler:$room_version"
|
||||||
|
implementation "com.google.android.material:material:$material_version"
|
||||||
implementation "com.google.dagger:hilt-android:$hilt_version"
|
implementation "com.google.dagger:hilt-android:$hilt_version"
|
||||||
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
|
kapt "com.google.dagger:hilt-compiler:$hilt_version"
|
||||||
|
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||||
implementation "org.osmdroid:osmdroid-android:$osmdroid_version"
|
implementation "org.osmdroid:osmdroid-android:$osmdroid_version"
|
||||||
implementation "com.jakewharton.timber:timber:$timber_version"
|
|
||||||
|
// implementation "androidx.compose.ui:ui:$compose_version"
|
||||||
|
// implementation "androidx.compose.ui:ui-tooling:$compose_version"
|
||||||
|
// implementation "androidx.compose.runtime:runtime:$compose_version"
|
||||||
|
// implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
|
||||||
|
// implementation "androidx.compose.compiler:compiler:$compose_compiler_version"
|
||||||
|
// implementation "androidx.compose.animation:animation:$compose_version"
|
||||||
|
// implementation "androidx.compose.foundation:foundation:$compose_foundation_version"
|
||||||
|
// implementation "androidx.activity:activity-compose:$activity_compose_version"
|
||||||
|
// implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"
|
||||||
|
// implementation "androidx.compose.material:material:$compose_material_version"
|
||||||
|
// implementation "androidx.compose.material:material-icons-extended:$compose_material_version"
|
||||||
|
// implementation "com.google.android.material:compose-theme-adapter:$material_adapter_version"
|
||||||
|
|
||||||
|
// debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
|
||||||
|
debugImplementation "androidx.fragment:fragment-testing:$fragment_test_version"
|
||||||
|
debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakcanary_version"
|
||||||
|
|
||||||
testImplementation "junit:junit:$junit_version"
|
testImplementation "junit:junit:$junit_version"
|
||||||
testImplementation "org.mockito:mockito-android:$mockito_version"
|
testImplementation "org.mockito:mockito-core:$mockito_version"
|
||||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
|
testImplementation "org.robolectric:robolectric:$robolectric_version"
|
||||||
debugImplementation "com.squareup.leakcanary:leakcanary-android:$leak_canary_version"
|
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_test_version"
|
||||||
|
|
||||||
|
androidTestImplementation "androidx.test:core:$androidx_test_version"
|
||||||
|
androidTestImplementation "androidx.test:runner:$androidx_test_version"
|
||||||
|
androidTestImplementation "androidx.test:rules:$androidx_test_version"
|
||||||
|
androidTestImplementation "androidx.test.ext:junit:$androidx_junit_version"
|
||||||
|
androidTestImplementation "androidx.test.ext:truth:$androidx_test_version"
|
||||||
|
androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"
|
||||||
|
// androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
|
||||||
|
androidTestImplementation "org.mockito:mockito-android:$mockito_version"
|
||||||
|
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_test_version"
|
||||||
}
|
}
|
||||||
Vendored
+11
-21
@@ -1,21 +1,11 @@
|
|||||||
# Add project specific ProGuard rules here.
|
# Please add these rules to your existing keep rules in order to suppress warnings.
|
||||||
# You can control the set of applied configuration files using the
|
# This is generated automatically by the Android Gradle plugin.
|
||||||
# proguardFiles setting in build.gradle.
|
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
||||||
#
|
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
||||||
# For more details, see
|
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
|
||||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
-dontwarn org.conscrypt.Conscrypt$Version
|
||||||
|
-dontwarn org.conscrypt.Conscrypt
|
||||||
# If your project uses WebView with JS, uncomment the following
|
-dontwarn org.conscrypt.ConscryptHostnameVerifier
|
||||||
# and specify the fully qualified class name to the JavaScript interface
|
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
|
||||||
# class:
|
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
|
||||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
-dontwarn org.openjsse.net.ssl.OpenJSSE
|
||||||
# public *;
|
|
||||||
#}
|
|
||||||
|
|
||||||
# Uncomment this to preserve the line number information for
|
|
||||||
# debugging stack traces.
|
|
||||||
#-keepattributes SourceFile,LineNumberTable
|
|
||||||
|
|
||||||
# If you keep the line number information, uncomment this to
|
|
||||||
# hide the original source file name.
|
|
||||||
#-renamesourcefileattribute SourceFile
|
|
||||||
+32
-46
@@ -1,111 +1,111 @@
|
|||||||
{
|
{
|
||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 4,
|
"version": 1,
|
||||||
"identityHash": "0f08defa24a78647801005e9277faac7",
|
"identityHash": "834981e960b712254e56b633f3772115",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "entries",
|
"tableName": "entries",
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`isSelected` INTEGER NOT NULL, `name` TEXT NOT NULL, `epoch` REAL NOT NULL, `meanmo` REAL NOT NULL, `eccn` REAL NOT NULL, `incl` REAL NOT NULL, `raan` REAL NOT NULL, `argper` REAL NOT NULL, `meanan` REAL NOT NULL, `catnum` INTEGER NOT NULL, `bstar` REAL NOT NULL, `xincl` REAL NOT NULL, `xnodeo` REAL NOT NULL, `omegao` REAL NOT NULL, `xmo` REAL NOT NULL, `xno` REAL NOT NULL, `isDeepspace` INTEGER NOT NULL, PRIMARY KEY(`catnum`))",
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`comment` TEXT, `name` TEXT NOT NULL, `epoch` REAL NOT NULL, `meanmo` REAL NOT NULL, `eccn` REAL NOT NULL, `incl` REAL NOT NULL, `raan` REAL NOT NULL, `argper` REAL NOT NULL, `meanan` REAL NOT NULL, `catnum` INTEGER NOT NULL, `bstar` REAL NOT NULL, `xincl` REAL NOT NULL, `xnodeo` REAL NOT NULL, `omegao` REAL NOT NULL, `xmo` REAL NOT NULL, `xno` REAL NOT NULL, `isDeepspace` INTEGER NOT NULL, PRIMARY KEY(`catnum`))",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "isSelected",
|
"fieldPath": "comment",
|
||||||
"columnName": "isSelected",
|
"columnName": "comment",
|
||||||
"affinity": "INTEGER",
|
"affinity": "TEXT",
|
||||||
"notNull": true
|
"notNull": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "tle.name",
|
"fieldPath": "data.name",
|
||||||
"columnName": "name",
|
"columnName": "name",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "tle.epoch",
|
"fieldPath": "data.epoch",
|
||||||
"columnName": "epoch",
|
"columnName": "epoch",
|
||||||
"affinity": "REAL",
|
"affinity": "REAL",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "tle.meanmo",
|
"fieldPath": "data.meanmo",
|
||||||
"columnName": "meanmo",
|
"columnName": "meanmo",
|
||||||
"affinity": "REAL",
|
"affinity": "REAL",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "tle.eccn",
|
"fieldPath": "data.eccn",
|
||||||
"columnName": "eccn",
|
"columnName": "eccn",
|
||||||
"affinity": "REAL",
|
"affinity": "REAL",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "tle.incl",
|
"fieldPath": "data.incl",
|
||||||
"columnName": "incl",
|
"columnName": "incl",
|
||||||
"affinity": "REAL",
|
"affinity": "REAL",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "tle.raan",
|
"fieldPath": "data.raan",
|
||||||
"columnName": "raan",
|
"columnName": "raan",
|
||||||
"affinity": "REAL",
|
"affinity": "REAL",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "tle.argper",
|
"fieldPath": "data.argper",
|
||||||
"columnName": "argper",
|
"columnName": "argper",
|
||||||
"affinity": "REAL",
|
"affinity": "REAL",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "tle.meanan",
|
"fieldPath": "data.meanan",
|
||||||
"columnName": "meanan",
|
"columnName": "meanan",
|
||||||
"affinity": "REAL",
|
"affinity": "REAL",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "tle.catnum",
|
"fieldPath": "data.catnum",
|
||||||
"columnName": "catnum",
|
"columnName": "catnum",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "tle.bstar",
|
"fieldPath": "data.bstar",
|
||||||
"columnName": "bstar",
|
"columnName": "bstar",
|
||||||
"affinity": "REAL",
|
"affinity": "REAL",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "tle.xincl",
|
"fieldPath": "data.xincl",
|
||||||
"columnName": "xincl",
|
"columnName": "xincl",
|
||||||
"affinity": "REAL",
|
"affinity": "REAL",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "tle.xnodeo",
|
"fieldPath": "data.xnodeo",
|
||||||
"columnName": "xnodeo",
|
"columnName": "xnodeo",
|
||||||
"affinity": "REAL",
|
"affinity": "REAL",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "tle.omegao",
|
"fieldPath": "data.omegao",
|
||||||
"columnName": "omegao",
|
"columnName": "omegao",
|
||||||
"affinity": "REAL",
|
"affinity": "REAL",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "tle.xmo",
|
"fieldPath": "data.xmo",
|
||||||
"columnName": "xmo",
|
"columnName": "xmo",
|
||||||
"affinity": "REAL",
|
"affinity": "REAL",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "tle.xno",
|
"fieldPath": "data.xno",
|
||||||
"columnName": "xno",
|
"columnName": "xno",
|
||||||
"affinity": "REAL",
|
"affinity": "REAL",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "tle.isDeepspace",
|
"fieldPath": "data.isDeepspace",
|
||||||
"columnName": "isDeepspace",
|
"columnName": "isDeepspace",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
@@ -121,8 +121,8 @@
|
|||||||
"foreignKeys": []
|
"foreignKeys": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "transmitters",
|
"tableName": "radios",
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `info` TEXT NOT NULL, `isAlive` INTEGER NOT NULL, `downlink` INTEGER, `uplink` INTEGER, `mode` TEXT, `isInverted` INTEGER NOT NULL, `catnum` INTEGER, PRIMARY KEY(`uuid`))",
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `info` TEXT NOT NULL, `isAlive` INTEGER NOT NULL, `downlink` INTEGER, `uplink` INTEGER, `mode` TEXT, `isInverted` INTEGER NOT NULL, `catnum` INTEGER, `comment` TEXT, PRIMARY KEY(`uuid`))",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "uuid",
|
"fieldPath": "uuid",
|
||||||
@@ -171,6 +171,12 @@
|
|||||||
"columnName": "catnum",
|
"columnName": "catnum",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER",
|
||||||
"notNull": false
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "comment",
|
||||||
|
"columnName": "comment",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
@@ -181,32 +187,12 @@
|
|||||||
},
|
},
|
||||||
"indices": [],
|
"indices": [],
|
||||||
"foreignKeys": []
|
"foreignKeys": []
|
||||||
},
|
|
||||||
{
|
|
||||||
"tableName": "sources",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, PRIMARY KEY(`sourceUrl`))",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "sourceUrl",
|
|
||||||
"columnName": "sourceUrl",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"columnNames": [
|
|
||||||
"sourceUrl"
|
|
||||||
],
|
|
||||||
"autoGenerate": false
|
|
||||||
},
|
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"views": [],
|
"views": [],
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0f08defa24a78647801005e9277faac7')"
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '834981e960b712254e56b633f3772115')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 2,
|
||||||
|
"identityHash": "f43ab966fb7b0508057c359530582ff5",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "entries",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`comment` TEXT, `name` TEXT NOT NULL, `epoch` REAL NOT NULL, `meanmo` REAL NOT NULL, `eccn` REAL NOT NULL, `incl` REAL NOT NULL, `raan` REAL NOT NULL, `argper` REAL NOT NULL, `meanan` REAL NOT NULL, `catnum` INTEGER NOT NULL, `bstar` REAL NOT NULL, `xincl` REAL NOT NULL, `xnodeo` REAL NOT NULL, `omegao` REAL NOT NULL, `xmo` REAL NOT NULL, `xno` REAL NOT NULL, `orbitalPeriod` REAL NOT NULL, `isDeepSpace` INTEGER NOT NULL, PRIMARY KEY(`catnum`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "comment",
|
||||||
|
"columnName": "comment",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "data.name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "data.epoch",
|
||||||
|
"columnName": "epoch",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "data.meanmo",
|
||||||
|
"columnName": "meanmo",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "data.eccn",
|
||||||
|
"columnName": "eccn",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "data.incl",
|
||||||
|
"columnName": "incl",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "data.raan",
|
||||||
|
"columnName": "raan",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "data.argper",
|
||||||
|
"columnName": "argper",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "data.meanan",
|
||||||
|
"columnName": "meanan",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "data.catnum",
|
||||||
|
"columnName": "catnum",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "data.bstar",
|
||||||
|
"columnName": "bstar",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "data.xincl",
|
||||||
|
"columnName": "xincl",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "data.xnodeo",
|
||||||
|
"columnName": "xnodeo",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "data.omegao",
|
||||||
|
"columnName": "omegao",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "data.xmo",
|
||||||
|
"columnName": "xmo",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "data.xno",
|
||||||
|
"columnName": "xno",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "data.orbitalPeriod",
|
||||||
|
"columnName": "orbitalPeriod",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "data.isDeepSpace",
|
||||||
|
"columnName": "isDeepSpace",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"catnum"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "radios",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `info` TEXT NOT NULL, `isAlive` INTEGER NOT NULL, `downlink` INTEGER, `uplink` INTEGER, `mode` TEXT, `isInverted` INTEGER NOT NULL, `catnum` INTEGER, `comment` TEXT, PRIMARY KEY(`uuid`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "uuid",
|
||||||
|
"columnName": "uuid",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "info",
|
||||||
|
"columnName": "info",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isAlive",
|
||||||
|
"columnName": "isAlive",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "downlink",
|
||||||
|
"columnName": "downlink",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "uplink",
|
||||||
|
"columnName": "uplink",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "mode",
|
||||||
|
"columnName": "mode",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isInverted",
|
||||||
|
"columnName": "isInverted",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "catnum",
|
||||||
|
"columnName": "catnum",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "comment",
|
||||||
|
"columnName": "comment",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"uuid"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f43ab966fb7b0508057c359530582ff5')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
{
|
|
||||||
"formatVersion": 1,
|
|
||||||
"database": {
|
|
||||||
"version": 2,
|
|
||||||
"identityHash": "555494c464b5a29285eb5493cc8aa5f6",
|
|
||||||
"entities": [
|
|
||||||
{
|
|
||||||
"tableName": "entries",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tle` TEXT NOT NULL, `catNum` INTEGER NOT NULL, `name` TEXT NOT NULL, `isSelected` INTEGER NOT NULL, PRIMARY KEY(`catNum`))",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "tle",
|
|
||||||
"columnName": "tle",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "catNum",
|
|
||||||
"columnName": "catNum",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "name",
|
|
||||||
"columnName": "name",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "isSelected",
|
|
||||||
"columnName": "isSelected",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"columnNames": [
|
|
||||||
"catNum"
|
|
||||||
],
|
|
||||||
"autoGenerate": false
|
|
||||||
},
|
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tableName": "transmitters",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `info` TEXT NOT NULL, `isAlive` INTEGER NOT NULL, `downlink` INTEGER, `uplink` INTEGER, `mode` TEXT, `isInverted` INTEGER NOT NULL, `catNum` INTEGER, PRIMARY KEY(`uuid`))",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "uuid",
|
|
||||||
"columnName": "uuid",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "info",
|
|
||||||
"columnName": "info",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "isAlive",
|
|
||||||
"columnName": "isAlive",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "downlink",
|
|
||||||
"columnName": "downlink",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "uplink",
|
|
||||||
"columnName": "uplink",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "mode",
|
|
||||||
"columnName": "mode",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "isInverted",
|
|
||||||
"columnName": "isInverted",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "catNum",
|
|
||||||
"columnName": "catNum",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"columnNames": [
|
|
||||||
"uuid"
|
|
||||||
],
|
|
||||||
"autoGenerate": false
|
|
||||||
},
|
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tableName": "sources",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, PRIMARY KEY(`sourceUrl`))",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "sourceUrl",
|
|
||||||
"columnName": "sourceUrl",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"columnNames": [
|
|
||||||
"sourceUrl"
|
|
||||||
],
|
|
||||||
"autoGenerate": false
|
|
||||||
},
|
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"views": [],
|
|
||||||
"setupQueries": [
|
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
|
||||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '555494c464b5a29285eb5493cc8aa5f6')"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
{
|
|
||||||
"formatVersion": 1,
|
|
||||||
"database": {
|
|
||||||
"version": 3,
|
|
||||||
"identityHash": "555494c464b5a29285eb5493cc8aa5f6",
|
|
||||||
"entities": [
|
|
||||||
{
|
|
||||||
"tableName": "entries",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tle` TEXT NOT NULL, `catNum` INTEGER NOT NULL, `name` TEXT NOT NULL, `isSelected` INTEGER NOT NULL, PRIMARY KEY(`catNum`))",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "tle",
|
|
||||||
"columnName": "tle",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "catNum",
|
|
||||||
"columnName": "catNum",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "name",
|
|
||||||
"columnName": "name",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "isSelected",
|
|
||||||
"columnName": "isSelected",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"columnNames": [
|
|
||||||
"catNum"
|
|
||||||
],
|
|
||||||
"autoGenerate": false
|
|
||||||
},
|
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tableName": "transmitters",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `info` TEXT NOT NULL, `isAlive` INTEGER NOT NULL, `downlink` INTEGER, `uplink` INTEGER, `mode` TEXT, `isInverted` INTEGER NOT NULL, `catNum` INTEGER, PRIMARY KEY(`uuid`))",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "uuid",
|
|
||||||
"columnName": "uuid",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "info",
|
|
||||||
"columnName": "info",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "isAlive",
|
|
||||||
"columnName": "isAlive",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "downlink",
|
|
||||||
"columnName": "downlink",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "uplink",
|
|
||||||
"columnName": "uplink",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "mode",
|
|
||||||
"columnName": "mode",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "isInverted",
|
|
||||||
"columnName": "isInverted",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "catNum",
|
|
||||||
"columnName": "catNum",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"columnNames": [
|
|
||||||
"uuid"
|
|
||||||
],
|
|
||||||
"autoGenerate": false
|
|
||||||
},
|
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tableName": "sources",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, PRIMARY KEY(`sourceUrl`))",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "sourceUrl",
|
|
||||||
"columnName": "sourceUrl",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"columnNames": [
|
|
||||||
"sourceUrl"
|
|
||||||
],
|
|
||||||
"autoGenerate": false
|
|
||||||
},
|
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"views": [],
|
|
||||||
"setupQueries": [
|
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
|
||||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '555494c464b5a29285eb5493cc8aa5f6')"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.rtbishop.look4sat
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class ExampleInstrumentedTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun useAppContext() {
|
||||||
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
assertEquals("com.rtbishop.bbctestapp", appContext.packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,25 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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">
|
||||||
package="com.rtbishop.look4sat">
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.BLUETOOTH"
|
||||||
|
android:maxSdkVersion="30" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".presentation.Look4SatApplication"
|
android:name=".presentation.MainApplication"
|
||||||
android:allowBackup="false"
|
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
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">
|
||||||
<activity
|
<activity
|
||||||
android:name=".presentation.Look4SatActivity"
|
android:name=".presentation.MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@style/Theme.Look4Sat.SplashScreen">
|
android:theme="@style/Theme.Look4Sat.SplashScreen">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@@ -23,4 +29,4 @@
|
|||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
|
||||||
* Copyright (C) 2019-2021 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.framework
|
|
||||||
|
|
||||||
import com.rtbishop.look4sat.domain.model.SatEntry as DomainEntry
|
|
||||||
import com.rtbishop.look4sat.domain.model.SatItem as DomainItem
|
|
||||||
import com.rtbishop.look4sat.domain.model.Transmitter as DomainTransmitter
|
|
||||||
import com.rtbishop.look4sat.framework.model.SatEntry as FrameworkEntry
|
|
||||||
import com.rtbishop.look4sat.framework.model.SatItem as FrameworkItem
|
|
||||||
import com.rtbishop.look4sat.framework.model.Transmitter as FrameworkTransmitter
|
|
||||||
|
|
||||||
fun DomainEntry.toFramework() = FrameworkEntry(this.tle, this.isSelected)
|
|
||||||
|
|
||||||
fun DomainTransmitter.toFramework() = FrameworkTransmitter(
|
|
||||||
this.uuid, this.info, this.isAlive, this.downlink,
|
|
||||||
this.uplink, this.mode, this.isInverted, this.catnum
|
|
||||||
)
|
|
||||||
|
|
||||||
fun FrameworkItem.toDomain() = DomainItem(this.catnum, this.name, this.isSelected, this.modes)
|
|
||||||
|
|
||||||
fun FrameworkTransmitter.toDomain() = DomainTransmitter(
|
|
||||||
this.uuid, this.info, this.isAlive, this.downlink,
|
|
||||||
this.uplink, this.mode, this.isInverted, this.catnum
|
|
||||||
)
|
|
||||||
|
|
||||||
fun List<DomainEntry>.toFrameworkEntries() = this.map { it.toFramework() }
|
|
||||||
|
|
||||||
fun List<DomainTransmitter>.toFramework() = this.map { it.toFramework() }
|
|
||||||
|
|
||||||
fun List<FrameworkItem>.toDomainItems() = this.map { it.toDomain() }
|
|
||||||
|
|
||||||
fun List<FrameworkTransmitter>.toDomain() = this.map { it.toDomain() }
|
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* 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.framework
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.location.Location
|
||||||
|
import android.location.LocationListener
|
||||||
|
import android.location.LocationManager
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.rtbishop.look4sat.R
|
||||||
|
import com.rtbishop.look4sat.domain.ILocationManager
|
||||||
|
import com.rtbishop.look4sat.domain.ISettingsManager
|
||||||
|
import com.rtbishop.look4sat.domain.model.DataState
|
||||||
|
import com.rtbishop.look4sat.domain.predict.GeoPos
|
||||||
|
import com.rtbishop.look4sat.utility.QthConverter
|
||||||
|
import com.rtbishop.look4sat.utility.round
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class LocationManager @Inject constructor(
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
private val manager: LocationManager,
|
||||||
|
private val settings: ISettingsManager,
|
||||||
|
) : LocationListener, ILocationManager {
|
||||||
|
|
||||||
|
private val providerDef = LocationManager.PASSIVE_PROVIDER
|
||||||
|
private val providerNet = LocationManager.NETWORK_PROVIDER
|
||||||
|
private val providerGps = LocationManager.GPS_PROVIDER
|
||||||
|
private val locationCoarse = Manifest.permission.ACCESS_COARSE_LOCATION
|
||||||
|
private val locationFine = Manifest.permission.ACCESS_FINE_LOCATION
|
||||||
|
private val _stationPosition = MutableStateFlow<DataState<GeoPos>>(DataState.Handled)
|
||||||
|
private var currentLocator = settings.loadStationLocator()
|
||||||
|
private var currentPosition = settings.loadStationPosition()
|
||||||
|
|
||||||
|
override val stationPosition: StateFlow<DataState<GeoPos>> = _stationPosition
|
||||||
|
|
||||||
|
override fun getStationLocator(): String = currentLocator
|
||||||
|
|
||||||
|
override fun getStationPosition(): GeoPos = currentPosition
|
||||||
|
|
||||||
|
override fun setStationPosition(latitude: Double, longitude: Double) {
|
||||||
|
if (QthConverter.isValidPosition(latitude, longitude)) {
|
||||||
|
val tempLon = if (longitude > 180.0) longitude - 180 else longitude
|
||||||
|
val newLat = latitude.round(4)
|
||||||
|
val newLon = tempLon.round(4)
|
||||||
|
QthConverter.positionToQth(newLat, newLon)?.let { locator ->
|
||||||
|
currentLocator = locator
|
||||||
|
currentPosition = GeoPos(newLat, newLon)
|
||||||
|
settings.saveStationLocator(locator)
|
||||||
|
settings.saveStationPosition(currentPosition)
|
||||||
|
_stationPosition.value = DataState.Success(currentPosition)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_stationPosition.value =
|
||||||
|
DataState.Error(context.getString(R.string.location_manual_error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setPositionFromGps() {
|
||||||
|
val result = ContextCompat.checkSelfPermission(context, locationFine)
|
||||||
|
if (manager.isProviderEnabled(providerGps) && result == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
val location = manager.getLastKnownLocation(providerDef)
|
||||||
|
if (location != null) {
|
||||||
|
setStationPosition(location.latitude, location.longitude)
|
||||||
|
} else {
|
||||||
|
_stationPosition.value = DataState.Loading
|
||||||
|
manager.requestLocationUpdates(providerGps, 0L, 0f, this)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_stationPosition.value = DataState.Error(context.getString(R.string.location_null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setPositionFromNet() {
|
||||||
|
val result = ContextCompat.checkSelfPermission(context, locationCoarse)
|
||||||
|
if (manager.isProviderEnabled(providerNet) && result == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
val location = manager.getLastKnownLocation(providerDef)
|
||||||
|
if (location != null) {
|
||||||
|
setStationPosition(location.latitude, location.longitude)
|
||||||
|
} else {
|
||||||
|
_stationPosition.value = DataState.Loading
|
||||||
|
manager.requestLocationUpdates(providerNet, 0L, 0f, this)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_stationPosition.value = DataState.Error(context.getString(R.string.location_null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setPositionFromQth(locator: String) {
|
||||||
|
val position = QthConverter.qthToPosition(locator)
|
||||||
|
if (position != null) {
|
||||||
|
currentLocator = locator
|
||||||
|
currentPosition = position
|
||||||
|
settings.saveStationLocator(locator)
|
||||||
|
settings.saveStationPosition(currentPosition)
|
||||||
|
_stationPosition.value = DataState.Success(currentPosition)
|
||||||
|
} else {
|
||||||
|
_stationPosition.value = DataState.Error(context.getString(R.string.location_qth_error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setPositionHandled() {
|
||||||
|
_stationPosition.value = DataState.Handled
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLocationChanged(location: Location) {
|
||||||
|
manager.removeUpdates(this)
|
||||||
|
setStationPosition(location.latitude, location.longitude)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onProviderEnabled(provider: String) {}
|
||||||
|
|
||||||
|
override fun onProviderDisabled(provider: String) {}
|
||||||
|
|
||||||
|
@Deprecated("Deprecated in Java")
|
||||||
|
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
|
||||||
|
}
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
package com.rtbishop.look4sat.framework
|
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.hardware.GeomagneticField
|
|
||||||
import android.location.Location
|
|
||||||
import android.location.LocationListener
|
|
||||||
import android.location.LocationManager
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import com.rtbishop.look4sat.domain.LocationProvider
|
|
||||||
import com.rtbishop.look4sat.domain.QthConverter
|
|
||||||
import com.rtbishop.look4sat.domain.predict.GeoPos
|
|
||||||
import com.rtbishop.look4sat.presentation.round
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class LocationSource @Inject constructor(
|
|
||||||
@ApplicationContext private val context: Context,
|
|
||||||
private val preferences: SharedPreferences
|
|
||||||
) : LocationListener, LocationProvider {
|
|
||||||
|
|
||||||
private val locManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
|
||||||
private val _updatedLocation = MutableSharedFlow<GeoPos?>()
|
|
||||||
override val updatedLocation: SharedFlow<GeoPos?> = _updatedLocation
|
|
||||||
|
|
||||||
fun getMagDeclination(stationPos: GeoPos, time: Long = System.currentTimeMillis()): Float {
|
|
||||||
val lat = stationPos.latitude.toFloat()
|
|
||||||
val lon = stationPos.longitude.toFloat()
|
|
||||||
return GeomagneticField(lat, lon, 0f, time).declination
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updatePosition(latitude: Double, longitude: Double) {
|
|
||||||
if (QthConverter.isValidPosition(latitude, longitude)) {
|
|
||||||
_updatedLocation.tryEmit(GeoPos(latitude, longitude))
|
|
||||||
} else _updatedLocation.tryEmit(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updatePositionFromQth(qthString: String) {
|
|
||||||
val position = QthConverter.qthToPosition(qthString)
|
|
||||||
if (position != null) {
|
|
||||||
_updatedLocation.tryEmit(GeoPos(position.latitude, position.longitude))
|
|
||||||
} else _updatedLocation.tryEmit(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updatePositionFromGps() {
|
|
||||||
val provider = LocationManager.GPS_PROVIDER
|
|
||||||
val permission = Manifest.permission.ACCESS_FINE_LOCATION
|
|
||||||
val result = ContextCompat.checkSelfPermission(context, permission)
|
|
||||||
if (locManager.isProviderEnabled(provider) && result == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
locManager.requestLocationUpdates(provider, 0L, 0f, this)
|
|
||||||
} else _updatedLocation.tryEmit(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updatePositionFromNetwork() {
|
|
||||||
val provider = LocationManager.NETWORK_PROVIDER
|
|
||||||
val permission = Manifest.permission.ACCESS_COARSE_LOCATION
|
|
||||||
val result = ContextCompat.checkSelfPermission(context, permission)
|
|
||||||
if (locManager.isProviderEnabled(provider) && result == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
locManager.requestLocationUpdates(provider, 0L, 0f, this)
|
|
||||||
} else _updatedLocation.tryEmit(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLocationChanged(location: Location) {
|
|
||||||
locManager.removeUpdates(this)
|
|
||||||
val latitude = location.latitude.round(4)
|
|
||||||
val longitude = location.longitude.round(4)
|
|
||||||
_updatedLocation.tryEmit(GeoPos(latitude, longitude))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+6
-6
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,12 +21,13 @@ import android.hardware.Sensor
|
|||||||
import android.hardware.SensorEvent
|
import android.hardware.SensorEvent
|
||||||
import android.hardware.SensorEventListener
|
import android.hardware.SensorEventListener
|
||||||
import android.hardware.SensorManager
|
import android.hardware.SensorManager
|
||||||
|
import com.rtbishop.look4sat.domain.predict.RAD2DEG
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
import kotlin.math.round
|
import kotlin.math.round
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class OrientationSource @Inject constructor(private val sensorManager: SensorManager) :
|
class OrientationManager @Inject constructor(private val sensorManager: SensorManager) :
|
||||||
SensorEventListener {
|
SensorEventListener {
|
||||||
|
|
||||||
interface OrientationListener {
|
interface OrientationListener {
|
||||||
@@ -36,7 +37,6 @@ class OrientationSource @Inject constructor(private val sensorManager: SensorMan
|
|||||||
private val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
|
private val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
|
||||||
private val rotationMatrix = FloatArray(9)
|
private val rotationMatrix = FloatArray(9)
|
||||||
private val orientationValues = FloatArray(3)
|
private val orientationValues = FloatArray(3)
|
||||||
private val oneRadDegrees = 57.295779513f
|
|
||||||
private var sensorAccuracy: Int = SensorManager.SENSOR_STATUS_UNRELIABLE
|
private var sensorAccuracy: Int = SensorManager.SENSOR_STATUS_UNRELIABLE
|
||||||
private var orientationListener: OrientationListener? = null
|
private var orientationListener: OrientationListener? = null
|
||||||
|
|
||||||
@@ -67,9 +67,9 @@ class OrientationSource @Inject constructor(private val sensorManager: SensorMan
|
|||||||
private fun updateOrientation(rotationVector: FloatArray) {
|
private fun updateOrientation(rotationVector: FloatArray) {
|
||||||
SensorManager.getRotationMatrixFromVector(rotationMatrix, rotationVector)
|
SensorManager.getRotationMatrixFromVector(rotationMatrix, rotationVector)
|
||||||
SensorManager.getOrientation(rotationMatrix, orientationValues)
|
SensorManager.getOrientation(rotationMatrix, orientationValues)
|
||||||
val azimuth = orientationValues[0] * oneRadDegrees
|
val azimuth = (orientationValues[0] * RAD2DEG).toFloat()
|
||||||
val pitch = orientationValues[1] * oneRadDegrees
|
val pitch = (orientationValues[1] * RAD2DEG).toFloat()
|
||||||
val roll = orientationValues[2] * oneRadDegrees
|
val roll = (orientationValues[2] * RAD2DEG).toFloat()
|
||||||
val magneticAzimuth = (azimuth + 360f) % 360f
|
val magneticAzimuth = (azimuth + 360f) % 360f
|
||||||
val roundedAzimuth = round(magneticAzimuth * 10) / 10
|
val roundedAzimuth = round(magneticAzimuth * 10) / 10
|
||||||
val roundedPitch = round(pitch * 10) / 10
|
val roundedPitch = round(pitch * 10) / 10
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
/*
|
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
|
||||||
* Copyright (C) 2019-2021 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.framework
|
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.hardware.GeomagneticField
|
|
||||||
import android.location.LocationManager
|
|
||||||
import androidx.core.content.edit
|
|
||||||
import com.rtbishop.look4sat.BuildConfig
|
|
||||||
import com.rtbishop.look4sat.domain.predict.GeoPos
|
|
||||||
import com.rtbishop.look4sat.domain.QthConverter
|
|
||||||
import com.rtbishop.look4sat.presentation.round
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class PreferencesSource @Inject constructor(
|
|
||||||
private val locationManager: LocationManager,
|
|
||||||
private val preferences: SharedPreferences
|
|
||||||
) {
|
|
||||||
|
|
||||||
private val keyInitialSetup = "${BuildConfig.VERSION_NAME}update"
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val keyModes = "satModes"
|
|
||||||
const val keyCompass = "compass"
|
|
||||||
const val keyRadarSweep = "radarSweep"
|
|
||||||
const val keyTextLabels = "shouldUseTextLabels"
|
|
||||||
const val keyTimeUTC = "timeUTC"
|
|
||||||
const val keyHoursAhead = "hoursAhead"
|
|
||||||
const val keyMinElevation = "minElevation"
|
|
||||||
const val keyRotator = "isRotatorEnabled"
|
|
||||||
const val keyRotatorAddress = "rotatorAddress"
|
|
||||||
const val keyRotatorPort = "rotatorPort"
|
|
||||||
const val keyLatitude = "stationLat"
|
|
||||||
const val keyLongitude = "stationLon"
|
|
||||||
const val keyPositionGPS = "setPositionGPS"
|
|
||||||
const val keyPositionQTH = "setPositionQTH"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadStationPosition(): GeoPos {
|
|
||||||
val defaultSP = "0.0"
|
|
||||||
val latitude = preferences.getString(keyLatitude, null) ?: defaultSP
|
|
||||||
val longitude = preferences.getString(keyLongitude, null) ?: defaultSP
|
|
||||||
return GeoPos(latitude.toDouble(), longitude.toDouble())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun saveStationPosition(pos: GeoPos) {
|
|
||||||
preferences.edit {
|
|
||||||
putString(keyLatitude, pos.latitude.toString())
|
|
||||||
putString(keyLongitude, pos.longitude.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updatePositionFromGPS(): Boolean {
|
|
||||||
return try {
|
|
||||||
val location = locationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER)
|
|
||||||
if (location == null) false
|
|
||||||
else {
|
|
||||||
val latitude = location.latitude.round(4)
|
|
||||||
val longitude = location.longitude.round(4)
|
|
||||||
val stationPosition = GeoPos(latitude, longitude)
|
|
||||||
saveStationPosition(stationPosition)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} catch (exception: SecurityException) {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updatePositionFromQTH(qthString: String): Boolean {
|
|
||||||
val position = QthConverter.qthToPosition(qthString) ?: return false
|
|
||||||
val stationPosition = GeoPos(position.latitude, position.longitude)
|
|
||||||
saveStationPosition(stationPosition)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getMagDeclination(): Float {
|
|
||||||
val stationPosition = loadStationPosition()
|
|
||||||
val lat = stationPosition.latitude.toFloat()
|
|
||||||
val lon = stationPosition.longitude.toFloat()
|
|
||||||
return GeomagneticField(lat, lon, 0f, System.currentTimeMillis()).declination
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getHoursAhead(): Int {
|
|
||||||
return preferences.getInt(keyHoursAhead, 8)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getMinElevation(): Double {
|
|
||||||
return preferences.getInt(keyMinElevation, 16).toDouble()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shouldUseTextLabels(): Boolean {
|
|
||||||
return preferences.getBoolean(keyTextLabels, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shouldUseUTC(): Boolean {
|
|
||||||
return preferences.getBoolean(keyTimeUTC, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shouldUseCompass(): Boolean {
|
|
||||||
return preferences.getBoolean(keyCompass, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shouldShowSweep(): Boolean {
|
|
||||||
return preferences.getBoolean(keyRadarSweep, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isSetupDone(): Boolean {
|
|
||||||
return preferences.getBoolean(keyInitialSetup, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSetupDone() {
|
|
||||||
preferences.edit { putBoolean(keyInitialSetup, true) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveModesSelection(modes: List<String>) {
|
|
||||||
val modesSet = modes.toSet()
|
|
||||||
preferences.edit {
|
|
||||||
putStringSet(keyModes, modesSet)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadModesSelection(): List<String> {
|
|
||||||
preferences.getStringSet(keyModes, setOf())?.let { modesSet ->
|
|
||||||
return modesSet.toList().sorted()
|
|
||||||
}
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isRotatorEnabled(): Boolean {
|
|
||||||
return preferences.getBoolean(keyRotator, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getRotatorServer(): Pair<String, Int> {
|
|
||||||
val address = preferences.getString(keyRotatorAddress, null) ?: "127.0.0.1"
|
|
||||||
val port = preferences.getString(keyRotatorPort, null) ?: "4533"
|
|
||||||
return Pair(address, port.toInt())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
* 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.framework
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import com.rtbishop.look4sat.domain.ISettingsManager
|
||||||
|
import com.rtbishop.look4sat.domain.predict.GeoPos
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class SettingsManager @Inject constructor(private val prefs: SharedPreferences) : ISettingsManager {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val keyModes = "satModes"
|
||||||
|
const val keyCompass = "compass"
|
||||||
|
const val keyRadarSweep = "radarSweep"
|
||||||
|
const val keyTimeUTC = "timeUTC"
|
||||||
|
const val keyLastUpdateTime = "lastUpdateTime"
|
||||||
|
const val keyAutoUpdateEnabled = "autoUpdateEnabled"
|
||||||
|
const val keyHoursAhead = "hoursAhead"
|
||||||
|
const val keyMinElevation = "minElevation"
|
||||||
|
const val keyRotator = "isRotatorEnabled"
|
||||||
|
const val keyRotatorAddress = "rotatorAddress"
|
||||||
|
const val keyRotatorPort = "rotatorPort"
|
||||||
|
const val keyBTEnabled = "isBTEnabled"
|
||||||
|
const val keyBTDeviceName = "BTDeviceName"
|
||||||
|
const val keyBTDeviceAddr = "BTDeviceAddr"
|
||||||
|
const val keyBTFormat = "BTFormat"
|
||||||
|
const val keyLatitude = "stationLat"
|
||||||
|
const val keyLongitude = "stationLon"
|
||||||
|
const val keyLocator = "stationQTH"
|
||||||
|
const val keySelection = "selection"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadStationLocator(): String {
|
||||||
|
return prefs.getString(keyLocator, null) ?: "null"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveStationLocator(locator: String) {
|
||||||
|
prefs.edit { putString(keyLocator, locator) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadStationPosition(): GeoPos {
|
||||||
|
val defaultSP = "0.0"
|
||||||
|
val latitude = prefs.getString(keyLatitude, null) ?: defaultSP
|
||||||
|
val longitude = prefs.getString(keyLongitude, null) ?: defaultSP
|
||||||
|
return GeoPos(latitude.toDouble(), longitude.toDouble())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveStationPosition(position: GeoPos) {
|
||||||
|
prefs.edit {
|
||||||
|
putString(keyLatitude, position.lat.toString())
|
||||||
|
putString(keyLongitude, position.lon.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveEntriesSelection(catnums: List<Int>) {
|
||||||
|
val stringList = catnums.map { catnum -> catnum.toString() }
|
||||||
|
prefs.edit { putStringSet(keySelection, stringList.toSet()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadEntriesSelection(): List<Int> {
|
||||||
|
val catnums = prefs.getStringSet(keySelection, emptySet())?.map { catnum -> catnum.toInt() }
|
||||||
|
return catnums?.sorted() ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveSatType(type: String, catnums: List<Int>) {
|
||||||
|
val stringList = catnums.map { catnum -> catnum.toString() }
|
||||||
|
prefs.edit { putStringSet("type$type", stringList.toSet()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadSatType(type: String): List<Int> {
|
||||||
|
val catnums = prefs.getStringSet("type$type", emptySet())?.map { catnum -> catnum.toInt() }
|
||||||
|
return catnums ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getHoursAhead(): Int {
|
||||||
|
return prefs.getInt(keyHoursAhead, 24)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setHoursAhead(hoursAhead: Int) {
|
||||||
|
prefs.edit { putInt(keyHoursAhead, hoursAhead) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMinElevation(): Double {
|
||||||
|
return prefs.getDouble(keyMinElevation, 16.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setMinElevation(minElevation: Double) {
|
||||||
|
prefs.edit { putDouble(keyMinElevation, minElevation) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getUseUTC(): Boolean {
|
||||||
|
return prefs.getBoolean(keyTimeUTC, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setUseUTC(value: Boolean) {
|
||||||
|
prefs.edit { putBoolean(keyTimeUTC, value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLastUpdateTime(): Long {
|
||||||
|
return prefs.getLong(keyLastUpdateTime, 0L)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setLastUpdateTime(updateTime: Long) {
|
||||||
|
prefs.edit { putLong(keyLastUpdateTime, updateTime) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAutoUpdateEnabled(): Boolean {
|
||||||
|
return prefs.getBoolean(keyAutoUpdateEnabled, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setAutoUpdateEnabled(value: Boolean) {
|
||||||
|
prefs.edit { putBoolean(keyAutoUpdateEnabled, value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getUseCompass(): Boolean {
|
||||||
|
return prefs.getBoolean(keyCompass, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setUseCompass(value: Boolean) {
|
||||||
|
prefs.edit { putBoolean(keyCompass, value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getShowSweep(): Boolean {
|
||||||
|
return prefs.getBoolean(keyRadarSweep, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setShowSweep(value: Boolean) {
|
||||||
|
prefs.edit { putBoolean(keyRadarSweep, value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveModesSelection(modes: List<String>) {
|
||||||
|
prefs.edit { putStringSet(keyModes, modes.toSet()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadModesSelection(): List<String> {
|
||||||
|
return prefs.getStringSet(keyModes, null)?.toList()?.sorted() ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRotatorEnabled(): Boolean {
|
||||||
|
return prefs.getBoolean(keyRotator, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setRotatorEnabled(value: Boolean) {
|
||||||
|
prefs.edit { putBoolean(keyRotator, value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRotatorServer(): String {
|
||||||
|
return prefs.getString(keyRotatorAddress, null) ?: "127.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setRotatorServer(value: String) {
|
||||||
|
prefs.edit { putString(keyRotatorAddress, value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRotatorPort(): String {
|
||||||
|
return prefs.getString(keyRotatorPort, null) ?: "4533"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setRotatorPort(value: String) {
|
||||||
|
prefs.edit { putString(keyRotatorPort, value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBTEnabled(): Boolean {
|
||||||
|
return prefs.getBoolean(keyBTEnabled, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setBTEnabled(value: Boolean) {
|
||||||
|
prefs.edit { putBoolean(keyBTEnabled, value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBTDeviceAddr(): String {
|
||||||
|
return prefs.getString(keyBTDeviceAddr, null) ?: "00:0C:BF:13:80:5D"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setBTDeviceAddr(value: String) {
|
||||||
|
prefs.edit { putString(keyBTDeviceAddr, value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBTDeviceName(): String {
|
||||||
|
return prefs.getString(keyBTDeviceName, null) ?: "Default"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setBTDeviceName(value: String) {
|
||||||
|
prefs.edit { putString(keyBTDeviceName, value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBTFormat(): String {
|
||||||
|
return prefs.getString(keyBTFormat, null) ?: "W\$AZ \$EL"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setBTFormat(value: String) {
|
||||||
|
prefs.edit { putString(keyBTFormat, value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun SharedPreferences.getDouble(key: String, default: Double): Double {
|
||||||
|
return Double.fromBits(getLong(key, default.toRawBits()))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun SharedPreferences.Editor.putDouble(key: String, double: Double) {
|
||||||
|
putLong(key, double.toRawBits())
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
-11
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -15,18 +15,22 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.presentation
|
package com.rtbishop.look4sat.framework.data
|
||||||
|
|
||||||
import android.app.Application
|
import android.content.ContentResolver
|
||||||
import com.rtbishop.look4sat.BuildConfig
|
import android.net.Uri
|
||||||
import dagger.hilt.android.HiltAndroidApp
|
import com.rtbishop.look4sat.domain.data.IFileDataSource
|
||||||
import timber.log.Timber
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
@HiltAndroidApp
|
class FileDataSource(
|
||||||
class Look4SatApplication : Application() {
|
private val contentResolver: ContentResolver,
|
||||||
|
private val ioDispatcher: CoroutineDispatcher
|
||||||
|
) : IFileDataSource {
|
||||||
|
|
||||||
override fun onCreate() {
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
super.onCreate()
|
override suspend fun getDataStream(uri: String): InputStream? {
|
||||||
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
|
return withContext(ioDispatcher) { contentResolver.openInputStream(Uri.parse(uri)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* 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.framework.data
|
||||||
|
|
||||||
|
import androidx.room.Database
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
import com.rtbishop.look4sat.framework.data.dao.EntriesDao
|
||||||
|
import com.rtbishop.look4sat.framework.data.dao.RadiosDao
|
||||||
|
import com.rtbishop.look4sat.framework.model.SatEntry
|
||||||
|
import com.rtbishop.look4sat.framework.model.SatRadio
|
||||||
|
|
||||||
|
@Database(entities = [SatEntry::class, SatRadio::class], version = 2, exportSchema = true)
|
||||||
|
abstract class LocalDatabase : RoomDatabase() {
|
||||||
|
|
||||||
|
abstract fun entriesDao(): EntriesDao
|
||||||
|
|
||||||
|
abstract fun radiosDao(): RadiosDao
|
||||||
|
}
|
||||||
|
|
||||||
|
val MIGRATION_1_2 = object : Migration(1, 2) {
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
database.execSQL("CREATE TABLE entries_backup (name TEXT NOT NULL, epoch REAL NOT NULL, meanmo REAL NOT NULL, eccn REAL NOT NULL, incl REAL NOT NULL, raan REAL NOT NULL, argper REAL NOT NULL, meanan REAL NOT NULL, catnum INTEGER NOT NULL, bstar REAL NOT NULL, xincl REAL NOT NULL, xnodeo REAL NOT NULL, omegao REAL NOT NULL, xmo REAL NOT NULL, xno REAL NOT NULL, orbitalPeriod REAL NOT NULL, isDeepSpace INTEGER NOT NULL, comment TEXT, PRIMARY KEY(catnum))")
|
||||||
|
database.execSQL("INSERT INTO entries_backup (name, epoch, meanmo, eccn, incl, raan, argper, meanan, catnum, bstar, xincl, xnodeo, omegao, xmo, xno, orbitalPeriod, isDeepSpace, comment) SELECT name, epoch, meanmo, eccn, incl, raan, argper, meanan, catnum, bstar, xincl, xnodeo, omegao, xmo, xno, 1440 / meanmo, 1440 / meanmo >= 225.0, comment FROM entries")
|
||||||
|
database.execSQL("DROP TABLE entries")
|
||||||
|
database.execSQL("ALTER TABLE entries_backup RENAME TO entries")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* 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.framework.data
|
||||||
|
|
||||||
|
import com.rtbishop.look4sat.domain.data.ILocalEntrySource
|
||||||
|
import com.rtbishop.look4sat.domain.predict.Satellite
|
||||||
|
import com.rtbishop.look4sat.framework.data.dao.EntriesDao
|
||||||
|
import com.rtbishop.look4sat.domain.model.SatEntry as DomainEntry
|
||||||
|
import com.rtbishop.look4sat.domain.model.SatItem as DomainItem
|
||||||
|
import com.rtbishop.look4sat.framework.model.SatEntry as FrameworkEntry
|
||||||
|
import com.rtbishop.look4sat.framework.model.SatItem as FrameworkItem
|
||||||
|
|
||||||
|
class LocalEntrySource(private val entriesDao: EntriesDao) : ILocalEntrySource {
|
||||||
|
|
||||||
|
override fun getEntriesTotal() = entriesDao.getEntriesTotal()
|
||||||
|
|
||||||
|
override suspend fun getEntriesWithModes(): List<DomainItem> {
|
||||||
|
return entriesDao.getEntriesWithModes().toDomainItems()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getEntriesWithIds(ids: List<Int>): List<Satellite> {
|
||||||
|
val selectedSatellites = mutableListOf<Satellite>()
|
||||||
|
ids.chunked(999).forEach { idsPart ->
|
||||||
|
val entries = entriesDao.getEntriesWithIds(idsPart)
|
||||||
|
selectedSatellites.addAll(entries.map { entry -> entry.data.getSatellite() })
|
||||||
|
}
|
||||||
|
return selectedSatellites
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun insertEntries(entries: List<DomainEntry>) {
|
||||||
|
entriesDao.insertEntries(entries.toFrameworkEntries())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteEntries() = entriesDao.deleteEntries()
|
||||||
|
|
||||||
|
private fun DomainEntry.toFramework() = FrameworkEntry(this.data, this.comment)
|
||||||
|
|
||||||
|
private fun FrameworkItem.toDomain() = DomainItem(this.catnum, this.name, this.modes, false)
|
||||||
|
|
||||||
|
private fun List<DomainEntry>.toFrameworkEntries() = this.map { entry -> entry.toFramework() }
|
||||||
|
|
||||||
|
private fun List<FrameworkItem>.toDomainItems() = this.map { item -> item.toDomain() }
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* 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.framework.data
|
||||||
|
|
||||||
|
import com.rtbishop.look4sat.domain.model.SatRadio as DomainRadio
|
||||||
|
import com.rtbishop.look4sat.framework.model.SatRadio as FrameworkRadio
|
||||||
|
import com.rtbishop.look4sat.domain.data.ILocalRadioSource
|
||||||
|
import com.rtbishop.look4sat.framework.data.dao.RadiosDao
|
||||||
|
|
||||||
|
class LocalRadioSource(private val radiosDao: RadiosDao) : ILocalRadioSource {
|
||||||
|
|
||||||
|
override fun getRadiosTotal() = radiosDao.getRadiosTotal()
|
||||||
|
|
||||||
|
override suspend fun getRadiosWithId(id: Int): List<DomainRadio> {
|
||||||
|
return radiosDao.getRadiosWithId(id).toDomainRadios()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun insertRadios(radios: List<DomainRadio>) {
|
||||||
|
radiosDao.deleteRadios()
|
||||||
|
radiosDao.insertRadios(radios.toFrameworkRadios())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteRadios() = radiosDao.deleteRadios()
|
||||||
|
|
||||||
|
private fun DomainRadio.toFramework() = FrameworkRadio(
|
||||||
|
this.uuid, this.info, this.isAlive, this.downlink, this.uplink,
|
||||||
|
this.mode, this.isInverted, this.catnum, this.comment
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun FrameworkRadio.toDomain() = DomainRadio(
|
||||||
|
this.uuid, this.info, this.isAlive, this.downlink, this.uplink,
|
||||||
|
this.mode, this.isInverted, this.catnum, this.comment
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun List<DomainRadio>.toFrameworkRadios() = this.map { radio -> radio.toFramework() }
|
||||||
|
|
||||||
|
private fun List<FrameworkRadio>.toDomainRadios() = this.map { radio -> radio.toDomain() }
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* 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.framework.data
|
||||||
|
|
||||||
|
import com.rtbishop.look4sat.domain.data.IRemoteDataSource
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
class RemoteDataSource(
|
||||||
|
private val httpClient: OkHttpClient, private val ioDispatcher: CoroutineDispatcher
|
||||||
|
) : IRemoteDataSource {
|
||||||
|
|
||||||
|
override suspend fun getDataStream(url: String): InputStream? = withContext(ioDispatcher) {
|
||||||
|
try {
|
||||||
|
val request = Request.Builder().url(url).build()
|
||||||
|
httpClient.newCall(request).execute().body?.byteStream()
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* 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.framework.data.dao
|
||||||
|
|
||||||
|
import androidx.room.*
|
||||||
|
import com.rtbishop.look4sat.framework.model.SatEntry
|
||||||
|
import com.rtbishop.look4sat.framework.model.SatItem
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface EntriesDao {
|
||||||
|
|
||||||
|
@Query("SELECT COUNT(*) FROM entries")
|
||||||
|
fun getEntriesTotal(): Flow<Int>
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
@Query("SELECT catnum, name FROM entries ORDER BY name ASC")
|
||||||
|
suspend fun getEntriesWithModes(): List<SatItem>
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
@Query("SELECT * FROM entries WHERE catnum IN (:selectedIds)")
|
||||||
|
suspend fun getEntriesWithIds(selectedIds: List<Int>): List<SatEntry>
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun insertEntries(entries: List<SatEntry>)
|
||||||
|
|
||||||
|
@Query("DELETE FROM entries")
|
||||||
|
suspend fun deleteEntries()
|
||||||
|
}
|
||||||
+13
-14
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -15,26 +15,25 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.framework.local
|
package com.rtbishop.look4sat.framework.data.dao
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.*
|
||||||
import com.rtbishop.look4sat.framework.model.Source
|
import com.rtbishop.look4sat.framework.model.SatRadio
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface SourcesDao {
|
interface RadiosDao {
|
||||||
|
|
||||||
@Query("SELECT sourceUrl FROM sources")
|
@Query("SELECT COUNT(*) FROM radios")
|
||||||
suspend fun getSources(): List<String>
|
fun getRadiosTotal(): Flow<Int>
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
suspend fun updateSources(sources: List<Source>) {
|
@Query("SELECT * FROM radios WHERE catnum = :id AND isAlive = 1")
|
||||||
deleteSources()
|
suspend fun getRadiosWithId(id: Int): List<SatRadio>
|
||||||
insertSources(sources)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Query("DELETE from sources")
|
|
||||||
suspend fun deleteSources()
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun insertSources(sources: List<Source>)
|
suspend fun insertRadios(radios: List<SatRadio>)
|
||||||
|
|
||||||
|
@Query("DELETE FROM radios")
|
||||||
|
suspend fun deleteRadios()
|
||||||
}
|
}
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
|
||||||
* Copyright (C) 2019-2021 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.framework.local
|
|
||||||
|
|
||||||
import androidx.room.*
|
|
||||||
import com.rtbishop.look4sat.framework.model.SatEntry
|
|
||||||
import com.rtbishop.look4sat.framework.model.SatItem
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
interface EntriesDao {
|
|
||||||
|
|
||||||
@Transaction
|
|
||||||
@Query("SELECT catnum, name, isSelected FROM entries ORDER BY name ASC")
|
|
||||||
fun getSatelliteItems(): Flow<List<SatItem>>
|
|
||||||
|
|
||||||
@Query("SELECT * FROM entries WHERE isSelected = 1")
|
|
||||||
suspend fun getSelectedSatellites(): List<SatEntry>
|
|
||||||
|
|
||||||
@Transaction
|
|
||||||
suspend fun updateEntries(entries: List<SatEntry>) {
|
|
||||||
val entriesSelection = getEntriesSelection()
|
|
||||||
insertEntries(entries)
|
|
||||||
restoreSelection(entriesSelection, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Query("SELECT catnum FROM entries WHERE isSelected = 1")
|
|
||||||
suspend fun getEntriesSelection(): List<Int>
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
|
||||||
suspend fun insertEntries(entries: List<SatEntry>)
|
|
||||||
|
|
||||||
@Transaction
|
|
||||||
suspend fun restoreSelection(catnums: List<Int>, isSelected: Boolean) {
|
|
||||||
updateEntriesSelection(catnums, isSelected)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transaction
|
|
||||||
suspend fun updateEntriesSelection(catnums: List<Int>, isSelected: Boolean) {
|
|
||||||
catnums.forEach { catnum -> updateEntrySelection(catnum, isSelected) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Query("UPDATE entries SET isSelected = :isSelected WHERE catnum = :catnum")
|
|
||||||
suspend fun updateEntrySelection(catnum: Int, isSelected: Boolean)
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
|
||||||
* Copyright (C) 2019-2021 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.framework.local
|
|
||||||
|
|
||||||
import com.rtbishop.look4sat.data.LocalDataSource
|
|
||||||
import com.rtbishop.look4sat.domain.model.SatEntry
|
|
||||||
import com.rtbishop.look4sat.domain.model.SatItem
|
|
||||||
import com.rtbishop.look4sat.domain.model.Transmitter
|
|
||||||
import com.rtbishop.look4sat.domain.predict.Satellite
|
|
||||||
import com.rtbishop.look4sat.framework.*
|
|
||||||
import com.rtbishop.look4sat.framework.model.Source
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
|
|
||||||
class LocalSource(
|
|
||||||
private val entriesDao: EntriesDao,
|
|
||||||
private val sourcesDao: SourcesDao,
|
|
||||||
private val transmittersDao: TransmittersDao
|
|
||||||
) : LocalDataSource {
|
|
||||||
|
|
||||||
override fun getSatelliteItems(): Flow<List<SatItem>> {
|
|
||||||
return entriesDao.getSatelliteItems().map { items -> items.toDomainItems() }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getSelectedSatellites(): List<Satellite> {
|
|
||||||
return entriesDao.getSelectedSatellites().map { entry -> entry.tle.createSat() }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getSources() = sourcesDao.getSources()
|
|
||||||
|
|
||||||
override suspend fun getTransmitters(catnum: Int): List<Transmitter> {
|
|
||||||
return transmittersDao.getTransmitters(catnum).toDomain()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun updateEntries(entries: List<SatEntry>) {
|
|
||||||
entriesDao.updateEntries(entries.toFrameworkEntries())
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun updateEntriesSelection(catnums: List<Int>, isSelected: Boolean) {
|
|
||||||
entriesDao.updateEntriesSelection(catnums, isSelected)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun updateSources(sources: List<String>) {
|
|
||||||
sourcesDao.updateSources(sources.map { sourceUrl -> Source(sourceUrl) })
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun updateTransmitters(transmitters: List<Transmitter>) {
|
|
||||||
transmittersDao.updateTransmitters(transmitters.toFramework())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
/*
|
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
|
||||||
* Copyright (C) 2019-2021 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.framework.local
|
|
||||||
|
|
||||||
import androidx.room.Database
|
|
||||||
import androidx.room.RoomDatabase
|
|
||||||
import androidx.room.migration.Migration
|
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
|
||||||
import com.rtbishop.look4sat.framework.model.Source
|
|
||||||
import com.rtbishop.look4sat.framework.model.SatEntry
|
|
||||||
import com.rtbishop.look4sat.framework.model.Transmitter
|
|
||||||
|
|
||||||
@Database(
|
|
||||||
entities = [SatEntry::class, Source::class, Transmitter::class],
|
|
||||||
version = 4,
|
|
||||||
exportSchema = true
|
|
||||||
)
|
|
||||||
abstract class SatelliteDb : RoomDatabase() {
|
|
||||||
|
|
||||||
abstract fun entriesDao(): EntriesDao
|
|
||||||
|
|
||||||
abstract fun sourcesDao(): SourcesDao
|
|
||||||
|
|
||||||
abstract fun transmittersDao(): TransmittersDao
|
|
||||||
}
|
|
||||||
|
|
||||||
val MIGRATION_1_2 = object : Migration(1, 2) {
|
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
|
||||||
database.execSQL("CREATE TABLE trans_backup (uuid TEXT NOT NULL, info TEXT NOT NULL, isAlive INTEGER NOT NULL, downlink INTEGER, uplink INTEGER, mode TEXT, isInverted INTEGER NOT NULL, catNum INTEGER, PRIMARY KEY(uuid))")
|
|
||||||
database.execSQL("INSERT INTO trans_backup (uuid, info, isAlive, downlink, uplink, mode, isInverted, catNum) SELECT uuid, info, isAlive, downlink, uplink, mode, isInverted, catNum FROM transmitters")
|
|
||||||
database.execSQL("DROP TABLE transmitters")
|
|
||||||
database.execSQL("ALTER TABLE trans_backup RENAME TO transmitters")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val MIGRATION_2_3 = object : Migration(2, 3) {
|
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
|
||||||
database.execSQL("CREATE TABLE sources (sourceUrl TEXT NOT NULL, PRIMARY KEY(sourceUrl))")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val MIGRATION_3_4 = object : Migration(3, 4) {
|
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
|
||||||
database.execSQL("CREATE TABLE entries_backup (isSelected INTEGER NOT NULL, name TEXT NOT NULL, epoch REAL NOT NULL, meanmo REAL NOT NULL, eccn REAL NOT NULL, incl REAL NOT NULL, raan REAL NOT NULL, argper REAL NOT NULL, meanan REAL NOT NULL, catnum INTEGER NOT NULL, bstar REAL NOT NULL, xincl REAL NOT NULL, xnodeo REAL NOT NULL, omegao REAL NOT NULL, xmo REAL NOT NULL, xno REAL NOT NULL, isDeepspace INTEGER NOT NULL, PRIMARY KEY(catnum))")
|
|
||||||
database.execSQL("INSERT INTO entries_backup (isSelected, name, epoch, meanmo, eccn, incl, raan, argper, meanan, catnum, bstar, xincl, xnodeo, omegao, xmo, xno, isDeepspace) SELECT isSelected, 'name', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, catNum, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0 FROM entries")
|
|
||||||
database.execSQL("DROP TABLE entries")
|
|
||||||
database.execSQL("ALTER TABLE entries_backup RENAME TO entries")
|
|
||||||
database.execSQL("CREATE TABLE trans_backup (uuid TEXT NOT NULL, info TEXT NOT NULL, isAlive INTEGER NOT NULL, downlink INTEGER, uplink INTEGER, mode TEXT, isInverted INTEGER NOT NULL, catnum INTEGER, PRIMARY KEY(uuid))")
|
|
||||||
database.execSQL("INSERT INTO trans_backup (uuid, info, isAlive, downlink, uplink, mode, isInverted, catnum) SELECT uuid, info, isAlive, downlink, uplink, mode, isInverted, catNum FROM transmitters")
|
|
||||||
database.execSQL("DROP TABLE transmitters")
|
|
||||||
database.execSQL("ALTER TABLE trans_backup RENAME TO transmitters")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-6
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -17,8 +17,4 @@
|
|||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.framework.model
|
package com.rtbishop.look4sat.framework.model
|
||||||
|
|
||||||
import androidx.room.Entity
|
data class DataSource(var url: String = String())
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
|
|
||||||
@Entity(tableName = "sources")
|
|
||||||
data class Source(@PrimaryKey var sourceUrl: String = String())
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,11 +19,7 @@ package com.rtbishop.look4sat.framework.model
|
|||||||
|
|
||||||
import androidx.room.Embedded
|
import androidx.room.Embedded
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import com.rtbishop.look4sat.domain.predict.TLE
|
import com.rtbishop.look4sat.domain.predict.OrbitalData
|
||||||
|
|
||||||
@Entity(tableName = "entries", primaryKeys = ["catnum"])
|
@Entity(tableName = "entries", primaryKeys = ["catnum"])
|
||||||
data class SatEntry(
|
data class SatEntry(@Embedded val data: OrbitalData, var comment: String? = null)
|
||||||
@Embedded
|
|
||||||
val tle: TLE,
|
|
||||||
var isSelected: Boolean = false
|
|
||||||
)
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -22,10 +22,9 @@ import androidx.room.Relation
|
|||||||
data class SatItem(
|
data class SatItem(
|
||||||
val catnum: Int,
|
val catnum: Int,
|
||||||
val name: String,
|
val name: String,
|
||||||
var isSelected: Boolean,
|
|
||||||
@Relation(
|
@Relation(
|
||||||
parentColumn = "catnum",
|
parentColumn = "catnum",
|
||||||
entity = Transmitter::class,
|
entity = SatRadio::class,
|
||||||
entityColumn = "catnum",
|
entityColumn = "catnum",
|
||||||
projection = ["mode"]
|
projection = ["mode"]
|
||||||
)
|
)
|
||||||
|
|||||||
+5
-4
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,8 +20,8 @@ package com.rtbishop.look4sat.framework.model
|
|||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
@Entity(tableName = "transmitters")
|
@Entity(tableName = "radios")
|
||||||
data class Transmitter(
|
data class SatRadio(
|
||||||
@PrimaryKey val uuid: String,
|
@PrimaryKey val uuid: String,
|
||||||
val info: String,
|
val info: String,
|
||||||
val isAlive: Boolean,
|
val isAlive: Boolean,
|
||||||
@@ -29,5 +29,6 @@ data class Transmitter(
|
|||||||
var uplink: Long?,
|
var uplink: Long?,
|
||||||
val mode: String?,
|
val mode: String?,
|
||||||
val isInverted: Boolean,
|
val isInverted: Boolean,
|
||||||
val catnum: Int?
|
val catnum: Int?,
|
||||||
|
var comment: String? = null
|
||||||
)
|
)
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package com.rtbishop.look4sat.framework.remote
|
|
||||||
|
|
||||||
import com.rtbishop.look4sat.data.RemoteDataSource
|
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.net.URL
|
|
||||||
|
|
||||||
class RemoteSource(private val ioDispatcher: CoroutineDispatcher) : RemoteDataSource {
|
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
|
||||||
override suspend fun fetchFileStream(url: String): InputStream? {
|
|
||||||
return try {
|
|
||||||
withContext(ioDispatcher) { URL(url).openStream() }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -17,12 +17,11 @@
|
|||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.injection
|
package com.rtbishop.look4sat.injection
|
||||||
|
|
||||||
import android.content.ContentResolver
|
import android.bluetooth.BluetoothManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.hardware.SensorManager
|
import android.hardware.SensorManager
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
@@ -36,8 +35,8 @@ object AppModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideContentResolver(@ApplicationContext context: Context): ContentResolver {
|
fun provideBluetoothManager(@ApplicationContext context: Context): BluetoothManager {
|
||||||
return context.contentResolver
|
return context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -55,6 +54,6 @@ object AppModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences {
|
fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences {
|
||||||
return PreferenceManager.getDefaultSharedPreferences(context)
|
return context.getSharedPreferences("default", Context.MODE_PRIVATE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* 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.injection
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothManager
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.room.Room
|
||||||
|
import com.rtbishop.look4sat.domain.IDataRepository
|
||||||
|
import com.rtbishop.look4sat.domain.ILocationManager
|
||||||
|
import com.rtbishop.look4sat.domain.ISatelliteManager
|
||||||
|
import com.rtbishop.look4sat.domain.ISettingsManager
|
||||||
|
import com.rtbishop.look4sat.domain.data.DataRepository
|
||||||
|
import com.rtbishop.look4sat.domain.predict.SatelliteManager
|
||||||
|
import com.rtbishop.look4sat.framework.LocationManager
|
||||||
|
import com.rtbishop.look4sat.framework.SettingsManager
|
||||||
|
import com.rtbishop.look4sat.framework.data.FileDataSource
|
||||||
|
import com.rtbishop.look4sat.framework.data.LocalDatabase
|
||||||
|
import com.rtbishop.look4sat.framework.data.LocalEntrySource
|
||||||
|
import com.rtbishop.look4sat.framework.data.LocalRadioSource
|
||||||
|
import com.rtbishop.look4sat.framework.data.MIGRATION_1_2
|
||||||
|
import com.rtbishop.look4sat.framework.data.RemoteDataSource
|
||||||
|
import com.rtbishop.look4sat.presentation.radarScreen.BTReporter
|
||||||
|
import com.rtbishop.look4sat.utility.DataParser
|
||||||
|
import com.rtbishop.look4sat.utility.DataReporter
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
object BaseModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideDataRepository(
|
||||||
|
@ApplicationContext context: Context,
|
||||||
|
settings: ISettingsManager
|
||||||
|
): IDataRepository {
|
||||||
|
val db = Room.databaseBuilder(context, LocalDatabase::class.java, "Look4SatDb")
|
||||||
|
.addMigrations(MIGRATION_1_2).fallbackToDestructiveMigration().build()
|
||||||
|
val parser = DataParser(Dispatchers.Default)
|
||||||
|
val fileSource = FileDataSource(context.contentResolver, Dispatchers.IO)
|
||||||
|
val entries = LocalEntrySource(db.entriesDao())
|
||||||
|
val radios = LocalRadioSource(db.radiosDao())
|
||||||
|
val remoteSource = RemoteDataSource(OkHttpClient.Builder().build(), Dispatchers.IO)
|
||||||
|
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||||
|
return DataRepository(parser, fileSource, entries, radios, remoteSource, scope, settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideDataReporter(): DataReporter = DataReporter(CoroutineScope(Dispatchers.IO))
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideBTReporter(manager: BluetoothManager): BTReporter {
|
||||||
|
return BTReporter(manager, CoroutineScope(Dispatchers.IO))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideLocationManager(manager: LocationManager): ILocationManager = manager
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideSatelliteManager(): ISatelliteManager = SatelliteManager(Dispatchers.Default)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideSettingsManager(manager: SettingsManager): ISettingsManager = manager
|
||||||
|
}
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
/*
|
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
|
||||||
* Copyright (C) 2019-2021 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.injection
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.room.Room
|
|
||||||
import com.rtbishop.look4sat.data.DefaultRepository
|
|
||||||
import com.rtbishop.look4sat.domain.DataParser
|
|
||||||
import com.rtbishop.look4sat.domain.DataRepository
|
|
||||||
import com.rtbishop.look4sat.domain.DataReporter
|
|
||||||
import com.rtbishop.look4sat.domain.predict.Predictor
|
|
||||||
import com.rtbishop.look4sat.framework.local.*
|
|
||||||
import com.rtbishop.look4sat.framework.remote.RemoteSource
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Module
|
|
||||||
@InstallIn(SingletonComponent::class)
|
|
||||||
object CoreModule {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideSatelliteRepo(
|
|
||||||
@ApplicationContext context: Context,
|
|
||||||
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
|
||||||
@DefaultDispatcher defaultDispatcher: CoroutineDispatcher
|
|
||||||
): DataRepository {
|
|
||||||
val dataParser = DataParser(defaultDispatcher)
|
|
||||||
val db = Room.databaseBuilder(context, SatelliteDb::class.java, "SatelliteDb")
|
|
||||||
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4).build()
|
|
||||||
val localSource = LocalSource(db.entriesDao(), db.sourcesDao(), db.transmittersDao())
|
|
||||||
val remoteSource = RemoteSource(ioDispatcher)
|
|
||||||
return DefaultRepository(dataParser, localSource, remoteSource)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun providePredictor(@DefaultDispatcher dispatcher: CoroutineDispatcher): Predictor {
|
|
||||||
return Predictor(dispatcher)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideDataReporter(@IoDispatcher dispatcher: CoroutineDispatcher): DataReporter {
|
|
||||||
return DataReporter(dispatcher)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
|
||||||
* Copyright (C) 2019-2021 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.injection
|
|
||||||
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import javax.inject.Qualifier
|
|
||||||
|
|
||||||
@Module
|
|
||||||
@InstallIn(SingletonComponent::class)
|
|
||||||
object KotlinModule {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@DefaultDispatcher
|
|
||||||
fun provideDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@IoDispatcher
|
|
||||||
fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@MainDispatcher
|
|
||||||
fun provideMainDispatcher(): CoroutineDispatcher = Dispatchers.Main
|
|
||||||
}
|
|
||||||
|
|
||||||
@Qualifier
|
|
||||||
@Retention(AnnotationRetention.BINARY)
|
|
||||||
annotation class DefaultDispatcher
|
|
||||||
|
|
||||||
@Qualifier
|
|
||||||
@Retention(AnnotationRetention.BINARY)
|
|
||||||
annotation class IoDispatcher
|
|
||||||
|
|
||||||
@Qualifier
|
|
||||||
@Retention(AnnotationRetention.BINARY)
|
|
||||||
annotation class MainDispatcher
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
/*
|
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
|
||||||
* Copyright (C) 2019-2021 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
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.annotation.IdRes
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.LifecycleEventObserver
|
|
||||||
import androidx.navigation.NavController
|
|
||||||
import androidx.navigation.NavOptions
|
|
||||||
import androidx.navigation.Navigator
|
|
||||||
import androidx.navigation.fragment.findNavController
|
|
||||||
import java.security.MessageDigest
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import java.util.regex.Pattern
|
|
||||||
|
|
||||||
fun Context.toast(text: String, duration: Int = Toast.LENGTH_SHORT) {
|
|
||||||
Toast.makeText(this, text, duration).apply { show() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun SharedPreferences.getDouble(key: String, default: Double): Double {
|
|
||||||
return Double.fromBits(getLong(key, default.toRawBits()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun SharedPreferences.Editor.putDouble(key: String, double: Double) {
|
|
||||||
putLong(key, double.toRawBits())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun NavController.navigateSafe(
|
|
||||||
@IdRes resId: Int,
|
|
||||||
args: Bundle? = null,
|
|
||||||
options: NavOptions? = null,
|
|
||||||
extras: Navigator.Extras? = null
|
|
||||||
) {
|
|
||||||
val action = currentDestination?.getAction(resId) ?: graph.getAction(resId)
|
|
||||||
if (action != null && currentDestination?.id != action.destinationId) {
|
|
||||||
navigate(resId, args, options, extras)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> Fragment.getNavResult(@IdRes id: Int, key: String, onResult: (result: T) -> Unit) {
|
|
||||||
val backStackEntry = findNavController().getBackStackEntry(id)
|
|
||||||
val observer = LifecycleEventObserver { _, event ->
|
|
||||||
if (event == Lifecycle.Event.ON_RESUME && backStackEntry.savedStateHandle.contains(key)) {
|
|
||||||
backStackEntry.savedStateHandle.get<T>(key)?.let(onResult)
|
|
||||||
backStackEntry.savedStateHandle.remove<T>(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
backStackEntry.lifecycle.addObserver(observer)
|
|
||||||
viewLifecycleOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
|
|
||||||
if (event == Lifecycle.Event.ON_DESTROY) backStackEntry.lifecycle.removeObserver(observer)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> Fragment.setNavResult(key: String, value: T) {
|
|
||||||
findNavController().previousBackStackEntry?.savedStateHandle?.set(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Long.toTimerString(): String {
|
|
||||||
val format = "%02d:%02d:%02d"
|
|
||||||
val hours = TimeUnit.MILLISECONDS.toHours(this)
|
|
||||||
val minutes = TimeUnit.MILLISECONDS.toMinutes(this) % 60
|
|
||||||
val seconds = TimeUnit.MILLISECONDS.toSeconds(this) % 60
|
|
||||||
return String.format(format, hours, minutes, seconds)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Double.round(decimals: Int): Double {
|
|
||||||
var multiplier = 1.0
|
|
||||||
repeat(decimals) { multiplier *= 10 }
|
|
||||||
return kotlin.math.round(this * multiplier) / multiplier
|
|
||||||
}
|
|
||||||
|
|
||||||
fun String.getHash(type: String = "SHA-256"): String {
|
|
||||||
val hexChars = "0123456789ABCDEF"
|
|
||||||
val bytes = MessageDigest.getInstance(type).digest(this.toByteArray())
|
|
||||||
val result = StringBuilder(bytes.size * 2)
|
|
||||||
bytes.forEach {
|
|
||||||
val i = it.toInt()
|
|
||||||
result.append(hexChars[i shr 4 and 0x0f])
|
|
||||||
result.append(hexChars[i and 0x0f])
|
|
||||||
}
|
|
||||||
return result.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun String.isEmailValid(): Boolean {
|
|
||||||
val expression = "^[\\w.-]+@([\\w\\-]+\\.)+[A-Z]{2,8}$"
|
|
||||||
val pattern = Pattern.compile(expression, Pattern.CASE_INSENSITIVE)
|
|
||||||
return pattern.matcher(this).matches()
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
|
||||||
* Copyright (C) 2019-2021 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
|
|
||||||
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
|
|
||||||
class ItemDivider(private val resId: Int) : RecyclerView.ItemDecoration() {
|
|
||||||
|
|
||||||
override fun onDrawOver(canvas: Canvas, parent: RecyclerView) {
|
|
||||||
ContextCompat.getDrawable(parent.context, resId)?.let { drawable ->
|
|
||||||
val leftPos = parent.paddingLeft
|
|
||||||
val rightPos = parent.width - parent.paddingRight
|
|
||||||
parent.adapter?.itemCount?.let { childCount ->
|
|
||||||
for (i in 0 until childCount) {
|
|
||||||
if (i == (childCount - 1)) continue
|
|
||||||
parent.getChildAt(i)?.let { view ->
|
|
||||||
val params = view.layoutParams as RecyclerView.LayoutParams
|
|
||||||
val topPos = view.bottom + params.bottomMargin
|
|
||||||
val bottomPos = topPos + drawable.intrinsicHeight
|
|
||||||
drawable.setBounds(leftPos, topPos, rightPos, bottomPos)
|
|
||||||
drawable.draw(canvas)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
|
||||||
* Copyright (C) 2019-2021 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
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.pm.ActivityInfo
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
|
||||||
import androidx.navigation.ui.setupWithNavController
|
|
||||||
import com.rtbishop.look4sat.R
|
|
||||||
import com.rtbishop.look4sat.databinding.ActivityMainBinding
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class Look4SatActivity : AppCompatActivity() {
|
|
||||||
|
|
||||||
@SuppressLint("SourceLockedOrientationActivity")
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
installSplashScreen()
|
|
||||||
val binding = ActivityMainBinding.inflate(layoutInflater)
|
|
||||||
setContentView(binding.root)
|
|
||||||
val navHost = supportFragmentManager.findFragmentById(R.id.main_nav_host) as NavHostFragment
|
|
||||||
binding.mainNavBottom.setupWithNavController(navHost.navController)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
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.annotation.IdRes
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import com.rtbishop.look4sat.databinding.ActivityMainBinding
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
@SuppressLint("SourceLockedOrientationActivity")
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||||
|
installSplashScreen()
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(ActivityMainBinding.inflate(layoutInflater).root)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun attachBaseContext(newBase: Context?) {
|
||||||
|
val newConfig = Configuration(newBase?.resources?.configuration)
|
||||||
|
newConfig.fontScale = 1.0f
|
||||||
|
applyOverrideConfiguration(newConfig)
|
||||||
|
super.attachBaseContext(newBase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> Fragment.getNavResult(@IdRes id: Int, key: String, onResult: (result: T) -> Unit) {
|
||||||
|
val backStackEntry = findNavController().getBackStackEntry(id)
|
||||||
|
val observer = LifecycleEventObserver { _, event ->
|
||||||
|
if (event == Lifecycle.Event.ON_RESUME && backStackEntry.savedStateHandle.contains(key)) {
|
||||||
|
backStackEntry.savedStateHandle.get<T>(key)?.let(onResult)
|
||||||
|
backStackEntry.savedStateHandle.remove<T>(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
backStackEntry.lifecycle.addObserver(observer)
|
||||||
|
viewLifecycleOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
|
||||||
|
if (event == Lifecycle.Event.ON_DESTROY) backStackEntry.lifecycle.removeObserver(observer)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> Fragment.setNavResult(key: String, value: T) {
|
||||||
|
findNavController().previousBackStackEntry?.savedStateHandle?.set(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun View.clickWithDebounce(debounceTime: Long = 875L, action: () -> Unit) {
|
||||||
|
this.setOnClickListener(object : View.OnClickListener {
|
||||||
|
private var lastClickTime: Long = 0
|
||||||
|
|
||||||
|
override fun onClick(v: View) {
|
||||||
|
if (SystemClock.elapsedRealtime() - lastClickTime < debounceTime) return
|
||||||
|
else action()
|
||||||
|
lastClickTime = SystemClock.elapsedRealtime()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.util.Log
|
||||||
|
import com.rtbishop.look4sat.domain.IDataRepository
|
||||||
|
import com.rtbishop.look4sat.framework.SettingsManager
|
||||||
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltAndroidApp
|
||||||
|
class MainApplication : Application() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var repository: IDataRepository
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var settingsManager: SettingsManager
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
checkAutoUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkAutoUpdate() {
|
||||||
|
if (settingsManager.getAutoUpdateEnabled()) {
|
||||||
|
val timeDelta = System.currentTimeMillis() - settingsManager.getLastUpdateTime()
|
||||||
|
if (timeDelta > 172800000) { // 48 hours in ms
|
||||||
|
val sdf = SimpleDateFormat("d MMM yyyy - HH:mm:ss", Locale.getDefault())
|
||||||
|
Log.d("AutoUpdate", "Started periodic data update on ${sdf.format(Date())}")
|
||||||
|
repository.updateFromWeb()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -22,8 +22,8 @@ import android.view.ViewGroup
|
|||||||
import androidx.recyclerview.widget.AsyncListDiffer
|
import androidx.recyclerview.widget.AsyncListDiffer
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.rtbishop.look4sat.domain.model.SatItem
|
|
||||||
import com.rtbishop.look4sat.databinding.ItemEntryBinding
|
import com.rtbishop.look4sat.databinding.ItemEntryBinding
|
||||||
|
import com.rtbishop.look4sat.domain.model.SatItem
|
||||||
|
|
||||||
class EntriesAdapter(private val clickListener: EntriesClickListener) :
|
class EntriesAdapter(private val clickListener: EntriesClickListener) :
|
||||||
RecyclerView.Adapter<EntriesAdapter.SatItemHolder>() {
|
RecyclerView.Adapter<EntriesAdapter.SatItemHolder>() {
|
||||||
@@ -59,7 +59,7 @@ class EntriesAdapter(private val clickListener: EntriesClickListener) :
|
|||||||
fun bind(item: SatItem, listener: EntriesClickListener) {
|
fun bind(item: SatItem, listener: EntriesClickListener) {
|
||||||
binding.entryCheckbox.text = item.name
|
binding.entryCheckbox.text = item.name
|
||||||
binding.entryCheckbox.isChecked = item.isSelected
|
binding.entryCheckbox.isChecked = item.isSelected
|
||||||
itemView.setOnClickListener {
|
binding.entryCheckbox.setOnClickListener {
|
||||||
listener.updateSelection(listOf(item.catnum), item.isSelected.not())
|
listener.updateSelection(listOf(item.catnum), item.isSelected.not())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-67
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,117 +19,99 @@ package com.rtbishop.look4sat.presentation.entriesScreen
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.inputmethod.EditorInfo
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||||
import com.google.android.material.snackbar.Snackbar
|
|
||||||
import com.rtbishop.look4sat.R
|
import com.rtbishop.look4sat.R
|
||||||
import com.rtbishop.look4sat.databinding.FragmentEntriesBinding
|
import com.rtbishop.look4sat.databinding.FragmentEntriesBinding
|
||||||
import com.rtbishop.look4sat.domain.model.SatItem
|
|
||||||
import com.rtbishop.look4sat.domain.model.DataState
|
import com.rtbishop.look4sat.domain.model.DataState
|
||||||
import com.rtbishop.look4sat.presentation.ItemDivider
|
import com.rtbishop.look4sat.domain.model.SatItem
|
||||||
|
import com.rtbishop.look4sat.presentation.clickWithDebounce
|
||||||
import com.rtbishop.look4sat.presentation.getNavResult
|
import com.rtbishop.look4sat.presentation.getNavResult
|
||||||
import com.rtbishop.look4sat.presentation.navigateSafe
|
import com.rtbishop.look4sat.presentation.setNavResult
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class EntriesFragment : Fragment(R.layout.fragment_entries) {
|
class EntriesFragment : Fragment(R.layout.fragment_entries) {
|
||||||
|
|
||||||
private val viewModel: EntriesViewModel by viewModels()
|
private val viewModel: EntriesViewModel by viewModels()
|
||||||
private val contentContract = ActivityResultContracts.GetContent()
|
private lateinit var binding: FragmentEntriesBinding
|
||||||
private val filePicker = registerForActivityResult(contentContract) { uri ->
|
|
||||||
uri?.let { viewModel.updateDataFromFile(uri) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
setupComponents(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupComponents(view: View) {
|
|
||||||
val entriesAdapter = EntriesAdapter(viewModel)
|
val entriesAdapter = EntriesAdapter(viewModel)
|
||||||
val binding = FragmentEntriesBinding.bind(view).apply {
|
binding = FragmentEntriesBinding.bind(view).apply {
|
||||||
entriesRecycler.apply {
|
entriesRecycler.apply {
|
||||||
setHasFixedSize(true)
|
setHasFixedSize(true)
|
||||||
adapter = entriesAdapter
|
adapter = entriesAdapter
|
||||||
layoutManager = GridLayoutManager(requireContext(), 2)
|
layoutManager = GridLayoutManager(requireContext(), 2)
|
||||||
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||||
addItemDecoration(ItemDivider(R.drawable.rec_divider_light))
|
addItemDecoration(DividerItemDecoration(requireContext(), 1))
|
||||||
|
addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
if (dy > 0 && entriesFab.visibility == View.VISIBLE) entriesFab.hide()
|
||||||
|
else if (dy < 0 && entriesFab.visibility != View.VISIBLE) entriesFab.show()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
entriesImportWeb.setOnClickListener {
|
entriesBtnBack.clickWithDebounce { findNavController().navigateUp() }
|
||||||
findNavController().navigateSafe(R.id.action_entries_to_sources)
|
entriesSearch.doOnTextChanged { text, _, _, _ -> viewModel.setQuery(text.toString()) }
|
||||||
|
entriesBtnModes.clickWithDebounce {
|
||||||
|
val direction = EntriesFragmentDirections.entriesToModes()
|
||||||
|
findNavController().navigate(direction)
|
||||||
}
|
}
|
||||||
entriesImportFile.setOnClickListener { filePicker.launch("*/*") }
|
entriesBtnSelect.clickWithDebounce { viewModel.selectCurrentItems(true) }
|
||||||
entriesSelectMode.setOnClickListener { showModesDialog() }
|
entriesBtnClear.clickWithDebounce { viewModel.selectCurrentItems(false) }
|
||||||
entriesSelectAll.setOnClickListener { viewModel.selectCurrentItems() }
|
val typeMessageFormat = requireContext().getString(R.string.types_message)
|
||||||
entriesSearchBar.setOnQueryTextListener(viewModel)
|
val type = if (viewModel.getSatType().isNullOrBlank()) "All" else viewModel.getSatType()
|
||||||
entriesSearchBar.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
|
entriesTypeMessage.text = String.format(typeMessageFormat, type)
|
||||||
|
entriesTypeCard.setOnClickListener { showSelectTypeDialog() }
|
||||||
}
|
}
|
||||||
viewModel.satData.observe(viewLifecycleOwner, { satData ->
|
viewModel.satData.observe(viewLifecycleOwner) { satData ->
|
||||||
handleSatData(satData, binding, entriesAdapter)
|
handleSatData(satData, entriesAdapter)
|
||||||
})
|
binding.entriesFab.clickWithDebounce {
|
||||||
getNavResult<List<String>>(R.id.nav_entries, "sources") { sources ->
|
setNavResult("selection", viewModel.saveSelection())
|
||||||
viewModel.updateDataFromWeb(sources)
|
findNavController().popBackStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getNavResult<List<String>>(R.id.nav_entries, "modes") { modes ->
|
||||||
|
viewModel.saveSelectedModes(modes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSatData(
|
private fun handleSatData(dataState: DataState<List<SatItem>>, entriesAdapter: EntriesAdapter) {
|
||||||
dataState: DataState<List<SatItem>>,
|
|
||||||
binding: FragmentEntriesBinding,
|
|
||||||
entriesAdapter: EntriesAdapter
|
|
||||||
) {
|
|
||||||
when (dataState) {
|
when (dataState) {
|
||||||
is DataState.Success -> {
|
is DataState.Success -> {
|
||||||
entriesAdapter.submitList(dataState.data)
|
entriesAdapter.submitList(dataState.data)
|
||||||
binding.entriesProgress.visibility = View.INVISIBLE
|
binding.entriesProgress.visibility = View.INVISIBLE
|
||||||
binding.entriesRecycler.visibility = View.VISIBLE
|
|
||||||
}
|
}
|
||||||
is DataState.Loading -> {
|
is DataState.Loading -> {
|
||||||
binding.entriesProgress.visibility = View.VISIBLE
|
binding.entriesProgress.visibility = View.VISIBLE
|
||||||
binding.entriesRecycler.visibility = View.INVISIBLE
|
|
||||||
}
|
|
||||||
is DataState.Error -> {
|
|
||||||
binding.entriesProgress.visibility = View.INVISIBLE
|
|
||||||
binding.entriesRecycler.visibility = View.VISIBLE
|
|
||||||
val message = getString(R.string.entries_update_error)
|
|
||||||
Snackbar.make(requireView(), message, Snackbar.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
}
|
}
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showModesDialog() {
|
private fun showSelectTypeDialog() {
|
||||||
val modes = arrayOf(
|
val satelliteTypes = viewModel.satTypes.toTypedArray()
|
||||||
"AFSK", "AFSK S-Net", "AFSK SALSAT", "AHRPT", "AM", "APT", "BPSK", "BPSK PMT-A3",
|
val selectedValue = satelliteTypes.indexOf(viewModel.getSatType())
|
||||||
"CERTO", "CW", "DQPSK", "DSTAR", "DUV", "FFSK", "FM", "FMN", "FSK", "FSK AX.100 Mode 5",
|
val typeFormat = requireContext().getString(R.string.types_message)
|
||||||
"FSK AX.100 Mode 6", "FSK AX.25 G3RUH", "GFSK", "GFSK Rktr", "GMSK", "HRPT", "LoRa",
|
|
||||||
"LRPT", "LSB", "MFSK", "MSK", "MSK AX.100 Mode 5", "MSK AX.100 Mode 6", "OFDM", "OQPSK",
|
|
||||||
"PSK", "PSK31", "PSK63", "QPSK", "QPSK31", "QPSK63", "SSTV", "USB", "WSJT"
|
|
||||||
)
|
|
||||||
val savedModes = BooleanArray(modes.size)
|
|
||||||
val selectedModes = viewModel.loadSelectedModes().toMutableList()
|
|
||||||
selectedModes.forEach { savedModes[modes.indexOf(it)] = true }
|
|
||||||
AlertDialog.Builder(requireContext()).apply {
|
AlertDialog.Builder(requireContext()).apply {
|
||||||
setTitle(context.getString(R.string.modes_title))
|
setTitle(R.string.types_title)
|
||||||
setMultiChoiceItems(modes, savedModes) { _, which, isChecked ->
|
setSingleChoiceItems(satelliteTypes, selectedValue) { dialog, index ->
|
||||||
when {
|
val selectedItem = satelliteTypes[index]
|
||||||
isChecked -> selectedModes.add(modes[which])
|
binding.entriesTypeMessage.text = String.format(typeFormat, selectedItem)
|
||||||
selectedModes.contains(modes[which]) -> selectedModes.remove(modes[which])
|
viewModel.setSatType(selectedItem)
|
||||||
}
|
|
||||||
}
|
|
||||||
setPositiveButton(context.getString(android.R.string.ok)) { _, _ ->
|
|
||||||
viewModel.saveSelectedModes(selectedModes)
|
|
||||||
}
|
|
||||||
setNeutralButton(context.getString(android.R.string.cancel)) { dialog, _ ->
|
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
create().show()
|
create()
|
||||||
|
show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+56
-61
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -17,93 +17,88 @@
|
|||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.presentation.entriesScreen
|
package com.rtbishop.look4sat.presentation.entriesScreen
|
||||||
|
|
||||||
import android.content.ContentResolver
|
|
||||||
import android.net.Uri
|
|
||||||
import android.widget.SearchView
|
|
||||||
import androidx.lifecycle.*
|
import androidx.lifecycle.*
|
||||||
import com.rtbishop.look4sat.domain.model.SatItem
|
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.DataState
|
||||||
import com.rtbishop.look4sat.domain.DataRepository
|
import com.rtbishop.look4sat.domain.model.SatItem
|
||||||
import com.rtbishop.look4sat.framework.PreferencesSource
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import timber.log.Timber
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class EntriesViewModel @Inject constructor(
|
class EntriesViewModel @Inject constructor(
|
||||||
private val preferences: PreferencesSource,
|
private val repository: IDataRepository,
|
||||||
private val resolver: ContentResolver,
|
private val settings: ISettingsManager
|
||||||
private val dataRepository: DataRepository
|
) : ViewModel(), EntriesAdapter.EntriesClickListener {
|
||||||
) : ViewModel(), SearchView.OnQueryTextListener, EntriesAdapter.EntriesClickListener {
|
|
||||||
|
|
||||||
private val coroutineHandler = CoroutineExceptionHandler { _, throwable ->
|
private val satType = MutableLiveData(String())
|
||||||
Timber.d(throwable)
|
private val transModes = MutableLiveData(settings.loadModesSelection())
|
||||||
_satData.value = DataState.Error(throwable)
|
|
||||||
}
|
|
||||||
private val transModes = MutableLiveData(preferences.loadModesSelection())
|
|
||||||
private val currentQuery = MutableLiveData(String())
|
private val currentQuery = MutableLiveData(String())
|
||||||
|
private val itemsFromRepo = liveData {
|
||||||
|
delay(125)
|
||||||
|
emit(loadEntriesWithSelection())
|
||||||
|
} as MutableLiveData
|
||||||
|
private val itemsWithType = satType.switchMap { type ->
|
||||||
|
itemsFromRepo.map { items -> filterByType(items, type) }
|
||||||
|
}
|
||||||
private val itemsWithModes = transModes.switchMap { modes ->
|
private val itemsWithModes = transModes.switchMap { modes ->
|
||||||
liveData { dataRepository.getSatelliteItems().collect { emit(filterByModes(it, modes)) } }
|
itemsWithType.map { items -> filterByModes(items, modes) }
|
||||||
}
|
}
|
||||||
private val itemsWithQuery = currentQuery.switchMap { query ->
|
private val itemsWithQuery = currentQuery.switchMap { query ->
|
||||||
itemsWithModes.map { items -> DataState.Success(filterByQuery(items, query)) }
|
itemsWithModes.map { items -> filterByQuery(items, query) }
|
||||||
}
|
}
|
||||||
private val _satData = MediatorLiveData<DataState<List<SatItem>>>().apply {
|
val satData = itemsWithQuery.map { items -> DataState.Success(items) }
|
||||||
addSource(itemsWithQuery) { value -> this.value = value }
|
val satTypes: List<String> = settings.sourcesMap.keys.sorted()
|
||||||
}
|
|
||||||
private var shouldSelectAll = true
|
|
||||||
val satData: LiveData<DataState<List<SatItem>>> = _satData
|
|
||||||
|
|
||||||
fun updateDataFromFile(uri: Uri) {
|
fun getSatType(): String? = satType.value
|
||||||
viewModelScope.launch(coroutineHandler) {
|
|
||||||
_satData.value = DataState.Loading
|
fun setSatType(type: String) {
|
||||||
runCatching {
|
satType.value = type
|
||||||
resolver.openInputStream(uri)?.use { stream ->
|
}
|
||||||
dataRepository.updateDataFromFile(stream)
|
|
||||||
}
|
fun selectCurrentItems(selectAll: Boolean) {
|
||||||
}.onFailure { _satData.value = DataState.Error(it) }
|
itemsWithQuery.value?.let { itemsWithQuery ->
|
||||||
|
updateSelection(itemsWithQuery.map { item -> item.catnum }, selectAll)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateDataFromWeb(sources: List<String>) {
|
|
||||||
viewModelScope.launch(coroutineHandler) {
|
|
||||||
_satData.value = DataState.Loading
|
|
||||||
dataRepository.updateDataFromWeb(sources)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun selectCurrentItems() {
|
|
||||||
val currentValue = _satData.value
|
|
||||||
if (currentValue is DataState.Success) {
|
|
||||||
updateSelection(currentValue.data.map { it.catnum }, shouldSelectAll)
|
|
||||||
shouldSelectAll = shouldSelectAll.not()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadSelectedModes(): List<String> {
|
|
||||||
return preferences.loadModesSelection()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveSelectedModes(modes: List<String>) {
|
fun saveSelectedModes(modes: List<String>) {
|
||||||
transModes.value = modes
|
transModes.value = modes
|
||||||
preferences.saveModesSelection(modes)
|
settings.saveModesSelection(modes)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onQueryTextSubmit(query: String): Boolean {
|
fun setQuery(query: String) {
|
||||||
return true
|
currentQuery.value = query
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onQueryTextChange(newText: String): Boolean {
|
fun saveSelection(): List<Int> {
|
||||||
currentQuery.value = newText
|
return itemsFromRepo.value?.let { itemsAll ->
|
||||||
return true
|
itemsAll.filter { item -> item.isSelected }.map { item -> item.catnum }
|
||||||
|
} ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateSelection(catNums: List<Int>, isSelected: Boolean) {
|
override fun updateSelection(catNums: List<Int>, isSelected: Boolean) {
|
||||||
viewModelScope.launch { dataRepository.updateSelection(catNums, isSelected) }
|
itemsFromRepo.value?.let { itemsAll ->
|
||||||
|
val copiedList = itemsAll.map { item -> item.copy() }
|
||||||
|
catNums.forEach { catnum ->
|
||||||
|
copiedList.find { item -> item.catnum == catnum }?.isSelected = isSelected
|
||||||
|
}
|
||||||
|
itemsFromRepo.value = copiedList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun loadEntriesWithSelection(): List<SatItem> {
|
||||||
|
val selectedIds = settings.loadEntriesSelection()
|
||||||
|
return repository.getEntriesWithModes().onEach { it.isSelected = it.catnum in selectedIds }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun filterByType(items: List<SatItem>, type: String): List<SatItem> {
|
||||||
|
if (type == "All") return items
|
||||||
|
val catnums = settings.loadSatType(type)
|
||||||
|
if (catnums.isEmpty()) return items
|
||||||
|
return items.filter { item -> item.catnum in catnums }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun filterByModes(items: List<SatItem>, modes: List<String>): List<SatItem> {
|
private fun filterByModes(items: List<SatItem>, modes: List<String>): List<SatItem> {
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* 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.entriesScreen
|
||||||
|
|
||||||
|
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.databinding.ItemModeBinding
|
||||||
|
|
||||||
|
class ModesAdapter(private val clickListener: ModesClickListener) :
|
||||||
|
RecyclerView.Adapter<ModesAdapter.ModeHolder>() {
|
||||||
|
|
||||||
|
private val diffCallback = object : DiffUtil.ItemCallback<String>() {
|
||||||
|
override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val differ = AsyncListDiffer(this, diffCallback)
|
||||||
|
private var selectedModes = listOf<String>()
|
||||||
|
|
||||||
|
fun submitModes(items: List<String>, selected: List<String>) {
|
||||||
|
differ.submitList(items)
|
||||||
|
selectedModes = selected
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = differ.currentList.size
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ModeHolder {
|
||||||
|
return ModeHolder.from(parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ModeHolder, position: Int) {
|
||||||
|
val currentItem = differ.currentList[position]
|
||||||
|
holder.bind(currentItem, selectedModes.contains(currentItem), clickListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModeHolder(private val binding: ItemModeBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
|
fun bind(mode: String, isSelected: Boolean, listener: ModesClickListener) {
|
||||||
|
binding.modeCheckbox.text = mode
|
||||||
|
binding.modeCheckbox.isChecked = isSelected
|
||||||
|
binding.modeCheckbox.setOnClickListener {
|
||||||
|
listener.onModeClicked(mode, isSelected.not())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun from(parent: ViewGroup): ModeHolder {
|
||||||
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
|
return ModeHolder(ItemModeBinding.inflate(inflater, parent, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ModesClickListener {
|
||||||
|
fun onModeClicked(mode: String, isSelected: Boolean)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* 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.entriesScreen
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.app.AppCompatDialogFragment
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||||
|
import com.rtbishop.look4sat.R
|
||||||
|
import com.rtbishop.look4sat.databinding.DialogModesBinding
|
||||||
|
import com.rtbishop.look4sat.domain.ISettingsManager
|
||||||
|
import com.rtbishop.look4sat.presentation.setNavResult
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class ModesDialog : AppCompatDialogFragment(), ModesAdapter.ModesClickListener {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var preferences: ISettingsManager
|
||||||
|
private lateinit var binding: DialogModesBinding
|
||||||
|
private val allModes = listOf(
|
||||||
|
"AFSK", "AFSK S-Net", "AFSK SALSAT", "AHRPT", "AM", "APT", "BPSK", "BPSK PMT-A3",
|
||||||
|
"CERTO", "CW", "DQPSK", "DSTAR", "DUV", "FFSK", "FM", "FMN", "FSK", "FSK AX.100 Mode 5",
|
||||||
|
"FSK AX.100 Mode 6", "FSK AX.25 G3RUH", "GFSK", "GFSK Rktr", "GMSK", "HRPT", "LoRa",
|
||||||
|
"LRPT", "LSB", "MFSK", "MSK", "MSK AX.100 Mode 5", "MSK AX.100 Mode 6", "OFDM", "OQPSK",
|
||||||
|
"PSK", "PSK31", "PSK63", "QPSK", "QPSK31", "QPSK63", "SSTV", "USB", "WSJT"
|
||||||
|
)
|
||||||
|
private val selectedModes = mutableListOf<String>()
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View {
|
||||||
|
binding = DialogModesBinding.inflate(inflater, group, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, state: Bundle?) {
|
||||||
|
super.onViewCreated(view, state)
|
||||||
|
dialog?.window?.setBackgroundDrawableResource(R.color.transparent)
|
||||||
|
dialog?.window?.setLayout(
|
||||||
|
(resources.displayMetrics.widthPixels * 0.94).toInt(),
|
||||||
|
(resources.displayMetrics.heightPixels * 0.76).toInt()
|
||||||
|
)
|
||||||
|
val modesAdapter = ModesAdapter(this@ModesDialog)
|
||||||
|
val layoutManager = GridLayoutManager(context, 2)
|
||||||
|
val itemDecoration = DividerItemDecoration(context, layoutManager.orientation)
|
||||||
|
selectedModes.addAll(preferences.loadModesSelection())
|
||||||
|
modesAdapter.submitModes(allModes, selectedModes)
|
||||||
|
binding.run {
|
||||||
|
modesRecycler.apply {
|
||||||
|
setHasFixedSize(true)
|
||||||
|
this.adapter = modesAdapter
|
||||||
|
this.layoutManager = layoutManager
|
||||||
|
addItemDecoration(itemDecoration)
|
||||||
|
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||||
|
}
|
||||||
|
modesBtnNeg.setOnClickListener { dismiss() }
|
||||||
|
modesBtnPos.setOnClickListener {
|
||||||
|
setNavResult("modes", selectedModes.toList())
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onModeClicked(mode: String, isSelected: Boolean) {
|
||||||
|
when {
|
||||||
|
isSelected -> selectedModes.add(mode)
|
||||||
|
selectedModes.contains(mode) -> selectedModes.remove(mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
|
||||||
* Copyright (C) 2019-2021 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.infoScreen
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.text.method.LinkMovementMethod
|
|
||||||
import android.view.View
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import com.rtbishop.look4sat.BuildConfig
|
|
||||||
import com.rtbishop.look4sat.R
|
|
||||||
import com.rtbishop.look4sat.databinding.FragmentInfoBinding
|
|
||||||
|
|
||||||
class InfoFragment : Fragment(R.layout.fragment_info) {
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
FragmentInfoBinding.bind(view).apply {
|
|
||||||
infoVersion.text =
|
|
||||||
String.format(getString(R.string.info_version), BuildConfig.VERSION_NAME)
|
|
||||||
infoBtnGithub.setOnClickListener {
|
|
||||||
gotoUrl("https://github.com/rt-bishop/Look4Sat/")
|
|
||||||
}
|
|
||||||
infoBtnFdroid.setOnClickListener {
|
|
||||||
gotoUrl("https://f-droid.org/en/packages/com.rtbishop.look4sat/")
|
|
||||||
}
|
|
||||||
infoBtnDonate.setOnClickListener {
|
|
||||||
gotoUrl("https://www.buymeacoffee.com/rtbishop")
|
|
||||||
}
|
|
||||||
val moveMethod = LinkMovementMethod.getInstance()
|
|
||||||
infoThanks.movementMethod = moveMethod
|
|
||||||
infoWarranty.movementMethod = moveMethod
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun gotoUrl(url: String) {
|
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -18,15 +18,19 @@
|
|||||||
package com.rtbishop.look4sat.presentation.mapScreen
|
package com.rtbishop.look4sat.presentation.mapScreen
|
||||||
|
|
||||||
import com.rtbishop.look4sat.domain.predict.GeoPos
|
import com.rtbishop.look4sat.domain.predict.GeoPos
|
||||||
import com.rtbishop.look4sat.domain.predict.Satellite
|
|
||||||
|
|
||||||
data class MapData(
|
data class MapData(
|
||||||
val pass: Satellite,
|
|
||||||
val catNum: Int,
|
val catNum: Int,
|
||||||
val name: String,
|
val name: String,
|
||||||
|
val aosTime: String,
|
||||||
|
val azimuth: Double,
|
||||||
|
val elevation: Double,
|
||||||
val range: Double,
|
val range: Double,
|
||||||
val altitude: Double,
|
val altitude: Double,
|
||||||
val velocity: Double,
|
val velocity: Double,
|
||||||
val qthLoc: String,
|
val qthLoc: String,
|
||||||
val osmPos: GeoPos
|
val osmPos: GeoPos,
|
||||||
|
val period: Double,
|
||||||
|
val phase: Double,
|
||||||
|
val eclipsed: Boolean
|
||||||
)
|
)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -17,56 +17,73 @@
|
|||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.presentation.mapScreen
|
package com.rtbishop.look4sat.presentation.mapScreen
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.*
|
||||||
import android.graphics.ColorMatrix
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import android.graphics.ColorMatrixColorFilter
|
import android.graphics.drawable.Drawable
|
||||||
import android.graphics.Paint
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import androidx.navigation.fragment.navArgs
|
||||||
import com.rtbishop.look4sat.R
|
import com.rtbishop.look4sat.R
|
||||||
import com.rtbishop.look4sat.databinding.FragmentMapBinding
|
import com.rtbishop.look4sat.databinding.FragmentMapBinding
|
||||||
import com.rtbishop.look4sat.domain.predict.GeoPos
|
import com.rtbishop.look4sat.domain.predict.GeoPos
|
||||||
|
import com.rtbishop.look4sat.domain.predict.SatPos
|
||||||
import com.rtbishop.look4sat.domain.predict.Satellite
|
import com.rtbishop.look4sat.domain.predict.Satellite
|
||||||
|
import com.rtbishop.look4sat.presentation.clickWithDebounce
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import org.osmdroid.config.Configuration
|
import org.osmdroid.config.Configuration
|
||||||
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
|
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
|
||||||
import org.osmdroid.util.GeoPoint
|
import org.osmdroid.util.GeoPoint
|
||||||
import org.osmdroid.views.CustomZoomButtonsController
|
import org.osmdroid.views.CustomZoomButtonsController
|
||||||
import org.osmdroid.views.MapView
|
import org.osmdroid.views.MapView
|
||||||
import org.osmdroid.views.overlay.*
|
import org.osmdroid.views.overlay.FolderOverlay
|
||||||
import timber.log.Timber
|
import org.osmdroid.views.overlay.Marker
|
||||||
import java.util.*
|
import org.osmdroid.views.overlay.Polyline
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MapFragment : Fragment(R.layout.fragment_map) {
|
class MapFragment : Fragment(R.layout.fragment_map) {
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var sharedPreferences: SharedPreferences
|
|
||||||
|
|
||||||
private val viewModel: MapViewModel by viewModels()
|
private val viewModel: MapViewModel by viewModels()
|
||||||
|
private val navArgs: MapFragmentArgs by navArgs()
|
||||||
private val minLat = MapView.getTileSystem().minLatitude
|
private val minLat = MapView.getTileSystem().minLatitude
|
||||||
private val maxLat = MapView.getTileSystem().maxLatitude
|
private val maxLat = MapView.getTileSystem().maxLatitude
|
||||||
private val trackPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
private val trackPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
strokeWidth = 2f
|
strokeWidth = 3f
|
||||||
style = Paint.Style.STROKE
|
style = Paint.Style.STROKE
|
||||||
color = Color.RED
|
color = Color.RED
|
||||||
strokeCap = Paint.Cap.ROUND
|
strokeCap = Paint.Cap.ROUND
|
||||||
strokeJoin = Paint.Join.ROUND
|
strokeJoin = Paint.Join.ROUND
|
||||||
}
|
}
|
||||||
private val footprintPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
private val footprintPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
|
strokeWidth = 3f
|
||||||
style = Paint.Style.FILL_AND_STROKE
|
style = Paint.Style.FILL_AND_STROKE
|
||||||
color = Color.parseColor("#26FFE082")
|
color = Color.parseColor("#FFE082")
|
||||||
}
|
}
|
||||||
|
private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
|
textSize = 36f
|
||||||
|
style = Paint.Style.FILL
|
||||||
|
color = Color.parseColor("#CCFFFFFF")
|
||||||
|
setShadowLayer(3f, 3f, 3f, Color.BLACK)
|
||||||
|
}
|
||||||
|
private val labelRect = Rect()
|
||||||
|
private lateinit var binding: FragmentMapBinding
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
Configuration.getInstance().load(requireContext(), sharedPreferences)
|
binding = FragmentMapBinding.bind(view)
|
||||||
val binding = FragmentMapBinding.bind(view).apply {
|
setupComponents()
|
||||||
|
setupObservers()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupComponents() {
|
||||||
|
val context = requireContext()
|
||||||
|
val preferences = context.getSharedPreferences("default", Context.MODE_PRIVATE)
|
||||||
|
Configuration.getInstance().load(context, preferences)
|
||||||
|
binding.run {
|
||||||
mapView.apply {
|
mapView.apply {
|
||||||
setMultiTouchControls(true)
|
setMultiTouchControls(true)
|
||||||
setTileSource(TileSourceFactory.WIKIMEDIA)
|
setTileSource(TileSourceFactory.WIKIMEDIA)
|
||||||
@@ -81,50 +98,46 @@ class MapFragment : Fragment(R.layout.fragment_map) {
|
|||||||
// add overlays: 0 - GSP, 1 - SatTrack, 2 - SatFootprint, 3 - SatIcons
|
// add overlays: 0 - GSP, 1 - SatTrack, 2 - SatFootprint, 3 - SatIcons
|
||||||
overlays.addAll(Array(4) { FolderOverlay() })
|
overlays.addAll(Array(4) { FolderOverlay() })
|
||||||
}
|
}
|
||||||
|
mapBtnBack.clickWithDebounce { findNavController().navigateUp() }
|
||||||
|
mapBtnPrev.setOnClickListener { viewModel.scrollSelection(true) }
|
||||||
|
mapBtnNext.setOnClickListener { viewModel.scrollSelection(false) }
|
||||||
}
|
}
|
||||||
binding.mapBtnPrev.setOnClickListener { viewModel.scrollSelection(true) }
|
|
||||||
binding.mapBtnNext.setOnClickListener { viewModel.scrollSelection(false) }
|
|
||||||
setupObservers(binding)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupObservers(binding: FragmentMapBinding) {
|
private fun setupObservers() {
|
||||||
viewModel.selectDefaultSatellite(arguments?.getInt("catNum"))
|
viewModel.selectDefaultSatellite(navArgs.catNum)
|
||||||
viewModel.stationPosLiveData.observe(viewLifecycleOwner, { renderStationPos(it, binding) })
|
viewModel.stationPos.observe(viewLifecycleOwner) { handleStationPos(it) }
|
||||||
viewModel.satPositions.observe(viewLifecycleOwner, { renderSatPositions(it, binding) })
|
viewModel.positions.observe(viewLifecycleOwner) { handlePositions(it) }
|
||||||
viewModel.satTrack.observe(viewLifecycleOwner, { renderSatTrack(it, binding) })
|
viewModel.track.observe(viewLifecycleOwner) { handleTrack(it) }
|
||||||
viewModel.satFootprint.observe(viewLifecycleOwner, { renderSatFootprint(it, binding) })
|
viewModel.footprint.observe(viewLifecycleOwner) { handleFootprint(it) }
|
||||||
viewModel.mapData.observe(viewLifecycleOwner, { renderSatData(it, binding) })
|
viewModel.mapData.observe(viewLifecycleOwner) { handleMapData(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderStationPos(stationPos: GeoPos, binding: FragmentMapBinding) {
|
private fun handleStationPos(stationPos: GeoPos) {
|
||||||
binding.apply {
|
binding.apply {
|
||||||
Marker(mapView).apply {
|
Marker(mapView).apply {
|
||||||
setInfoWindow(null)
|
setInfoWindow(null)
|
||||||
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
|
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
|
||||||
icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_map_pos)
|
icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_position)
|
||||||
position = GeoPoint(stationPos.latitude, stationPos.longitude)
|
position = GeoPoint(stationPos.lat, stationPos.lon)
|
||||||
mapView.overlays[0] = this
|
mapView.overlays[0] = this
|
||||||
mapView.invalidate()
|
mapView.invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderSatPositions(posMap: Map<Satellite, GeoPos>, binding: FragmentMapBinding) {
|
private fun handlePositions(posMap: Map<Satellite, GeoPos>) {
|
||||||
binding.apply {
|
binding.apply {
|
||||||
val markers = FolderOverlay()
|
val markers = FolderOverlay()
|
||||||
posMap.entries.forEach {
|
posMap.entries.forEach {
|
||||||
Marker(mapView).apply {
|
Marker(mapView).apply {
|
||||||
setInfoWindow(null)
|
setInfoWindow(null)
|
||||||
textLabelFontSize = 24
|
|
||||||
textLabelBackgroundColor = Color.TRANSPARENT
|
|
||||||
textLabelForegroundColor =
|
|
||||||
ContextCompat.getColor(requireContext(), R.color.themeLight)
|
|
||||||
setTextIcon(it.key.params.name)
|
|
||||||
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
|
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
|
||||||
|
icon = getCustomTextIcon(it.key.data.name)
|
||||||
try {
|
try {
|
||||||
position = GeoPoint(it.value.latitude, it.value.longitude)
|
position = GeoPoint(it.value.lat, it.value.lon)
|
||||||
} catch (exception: IllegalArgumentException) {
|
} catch (exception: IllegalArgumentException) {
|
||||||
Timber.d(exception)
|
println(exception.stackTraceToString())
|
||||||
}
|
}
|
||||||
setOnMarkerClickListener { _, _ ->
|
setOnMarkerClickListener { _, _ ->
|
||||||
viewModel.selectSatellite(it.key)
|
viewModel.selectSatellite(it.key)
|
||||||
@@ -138,49 +151,71 @@ class MapFragment : Fragment(R.layout.fragment_map) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderSatTrack(satTrack: List<List<GeoPos>>, binding: FragmentMapBinding) {
|
private fun getCustomTextIcon(textLabel: String): Drawable {
|
||||||
|
textPaint.getTextBounds(textLabel, 0, textLabel.length, labelRect)
|
||||||
|
val iconSize = 8f
|
||||||
|
val width = labelRect.width() + iconSize * 2f
|
||||||
|
val height = textPaint.textSize * 3f + iconSize * 2f
|
||||||
|
val bitmap = Bitmap.createBitmap(width.toInt(), height.toInt(), Bitmap.Config.ARGB_8888)
|
||||||
|
Canvas(bitmap).run {
|
||||||
|
drawCircle(width / 2f, height / 2f, iconSize, textPaint)
|
||||||
|
drawText(textLabel, iconSize / 2f, height - iconSize, textPaint)
|
||||||
|
}
|
||||||
|
return BitmapDrawable(binding.mapView.context.resources, bitmap)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleTrack(satTrack: List<List<GeoPos>>) {
|
||||||
|
val center = GeoPoint(satTrack[0][0].lat, satTrack[0][0].lon)
|
||||||
val trackOverlay = FolderOverlay()
|
val trackOverlay = FolderOverlay()
|
||||||
satTrack.forEach { track ->
|
satTrack.forEach { track ->
|
||||||
val trackPoints = track.map { GeoPoint(it.latitude, it.longitude) }
|
val trackPoints = track.map { GeoPoint(it.lat, it.lon) }
|
||||||
Polyline().apply {
|
Polyline().apply {
|
||||||
try {
|
try {
|
||||||
setPoints(trackPoints)
|
setPoints(trackPoints)
|
||||||
outlinePaint.set(trackPaint)
|
outlinePaint.set(trackPaint)
|
||||||
trackOverlay.add(this)
|
trackOverlay.add(this)
|
||||||
} catch (exception: IllegalArgumentException) {
|
} catch (exception: IllegalArgumentException) {
|
||||||
Timber.d(exception)
|
println(exception.stackTraceToString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.mapView.overlays[1] = trackOverlay
|
binding.mapView.overlays[1] = trackOverlay
|
||||||
|
binding.mapView.controller.animateTo(center)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderSatFootprint(satFootprint: List<GeoPos>, binding: FragmentMapBinding) {
|
private fun handleFootprint(satPos: SatPos) {
|
||||||
val footprintPoints = satFootprint.map { GeoPoint(it.latitude, it.longitude) }
|
val footprintPoints = satPos.getRangeCircle().map { GeoPoint(it.lat, it.lon) }
|
||||||
val footprintOverlay = Polygon().apply {
|
val footprintOverlay = Polyline().apply {
|
||||||
fillPaint.set(footprintPaint)
|
|
||||||
outlinePaint.set(footprintPaint)
|
outlinePaint.set(footprintPaint)
|
||||||
try {
|
try {
|
||||||
this.points = footprintPoints
|
setPoints(footprintPoints)
|
||||||
} catch (exception: IllegalArgumentException) {
|
} catch (exception: IllegalArgumentException) {
|
||||||
Timber.d(exception)
|
println(exception.stackTraceToString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.mapView.overlays[2] = footprintOverlay
|
binding.mapView.overlays[2] = footprintOverlay
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderSatData(mapData: MapData, binding: FragmentMapBinding) {
|
private fun handleMapData(mapData: MapData) {
|
||||||
binding.apply {
|
binding.apply {
|
||||||
mapDataName.text =
|
mapTimer.text = mapData.aosTime
|
||||||
String.format(getString(R.string.pat_osm_idName), mapData.catNum, mapData.name)
|
mapDataPeriod.text = String.format(getString(R.string.map_period, mapData.period))
|
||||||
mapDataQth.text = String.format(getString(R.string.map_qth), mapData.qthLoc)
|
mapDataPhase.text = String.format(getString(R.string.map_phase), mapData.phase)
|
||||||
mapDataAlt.text = String.format(getString(R.string.pat_altitude), mapData.altitude)
|
mapAzimuth.text = String.format(getString(R.string.map_azimuth), mapData.azimuth)
|
||||||
mapDataDst.text = String.format(getString(R.string.pat_distance), mapData.range)
|
mapElevation.text = String.format(getString(R.string.map_elevation), mapData.elevation)
|
||||||
mapDataVel.text = String.format(getString(R.string.pat_osm_vel), mapData.velocity)
|
mapDataAlt.text = String.format(getString(R.string.map_altitude), mapData.altitude)
|
||||||
|
mapDataDst.text = String.format(getString(R.string.map_distance), mapData.range)
|
||||||
mapDataLat.text =
|
mapDataLat.text =
|
||||||
String.format(getString(R.string.pat_osm_lat), mapData.osmPos.latitude)
|
String.format(getString(R.string.map_latitude), mapData.osmPos.lat)
|
||||||
mapDataLon.text =
|
mapDataLon.text =
|
||||||
String.format(getString(R.string.pat_osm_lon), mapData.osmPos.longitude)
|
String.format(getString(R.string.map_longitude), mapData.osmPos.lon)
|
||||||
|
mapDataQth.text = String.format(getString(R.string.map_qth), mapData.qthLoc)
|
||||||
|
mapDataVel.text = String.format(getString(R.string.map_velocity), mapData.velocity)
|
||||||
|
if (mapData.eclipsed) {
|
||||||
|
mapDataVisibility.text = getString(R.string.map_eclipsed)
|
||||||
|
} else {
|
||||||
|
mapDataVisibility.text = getString(R.string.map_visible)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
binding.mapView.invalidate()
|
binding.mapView.invalidate()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -18,12 +18,15 @@
|
|||||||
package com.rtbishop.look4sat.presentation.mapScreen
|
package com.rtbishop.look4sat.presentation.mapScreen
|
||||||
|
|
||||||
import androidx.lifecycle.*
|
import androidx.lifecycle.*
|
||||||
import com.rtbishop.look4sat.domain.predict.Predictor
|
import com.rtbishop.look4sat.domain.IDataRepository
|
||||||
import com.rtbishop.look4sat.domain.DataRepository
|
import com.rtbishop.look4sat.domain.ISatelliteManager
|
||||||
import com.rtbishop.look4sat.framework.PreferencesSource
|
import com.rtbishop.look4sat.domain.ISettingsManager
|
||||||
import com.rtbishop.look4sat.domain.predict.GeoPos
|
import com.rtbishop.look4sat.domain.predict.GeoPos
|
||||||
import com.rtbishop.look4sat.domain.QthConverter
|
import com.rtbishop.look4sat.domain.predict.SatPos
|
||||||
import com.rtbishop.look4sat.domain.predict.Satellite
|
import com.rtbishop.look4sat.domain.predict.Satellite
|
||||||
|
import com.rtbishop.look4sat.utility.QthConverter
|
||||||
|
import com.rtbishop.look4sat.utility.toDegrees
|
||||||
|
import com.rtbishop.look4sat.utility.toTimerString
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -33,78 +36,78 @@ import kotlin.math.min
|
|||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class MapViewModel @Inject constructor(
|
class MapViewModel @Inject constructor(
|
||||||
private val dataRepository: DataRepository,
|
private val satelliteManager: ISatelliteManager,
|
||||||
private val predictor: Predictor,
|
private val repository: IDataRepository,
|
||||||
private val preferences: PreferencesSource,
|
private val settings: ISettingsManager,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val stationPos = preferences.loadStationPosition()
|
private val stationPosition = settings.loadStationPosition()
|
||||||
|
private var allPasses = satelliteManager.getPasses()
|
||||||
|
private var allSatellites = listOf<Satellite>()
|
||||||
private var dataUpdateJob: Job? = null
|
private var dataUpdateJob: Job? = null
|
||||||
private var allSatList = listOf<Satellite>()
|
private var dataUpdateRate = 1000L
|
||||||
private lateinit var selectedSat: Satellite
|
private var selectedSatellite: Satellite? = null
|
||||||
|
|
||||||
val stationPosLiveData = liveData {
|
val stationPos = liveData {
|
||||||
val osmLat = clipLat(stationPos.latitude)
|
val osmLat = clipLat(stationPosition.lat)
|
||||||
val osmLon = clipLon(stationPos.longitude)
|
val osmLon = clipLon(stationPosition.lon)
|
||||||
emit(GeoPos(osmLat, osmLon))
|
emit(GeoPos(osmLat, osmLon))
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _satTrack = MutableLiveData<List<List<GeoPos>>>()
|
private val _track = MutableLiveData<List<List<GeoPos>>>()
|
||||||
val satTrack: LiveData<List<List<GeoPos>>> = _satTrack
|
val track: LiveData<List<List<GeoPos>>> = _track
|
||||||
|
|
||||||
private val _satFootprint = MutableLiveData<List<GeoPos>>()
|
private val _footprint = MutableLiveData<SatPos>()
|
||||||
val satFootprint: LiveData<List<GeoPos>> = _satFootprint
|
val footprint: LiveData<SatPos> = _footprint
|
||||||
|
|
||||||
private val _satData = MutableLiveData<MapData>()
|
private val _mapData = MutableLiveData<MapData>()
|
||||||
val mapData: LiveData<MapData> = this._satData
|
val mapData: LiveData<MapData> = this._mapData
|
||||||
|
|
||||||
private val _satPositions = MutableLiveData<Map<Satellite, GeoPos>>()
|
private val _positions = MutableLiveData<Map<Satellite, GeoPos>>()
|
||||||
val satPositions: LiveData<Map<Satellite, GeoPos>> = _satPositions
|
val positions: LiveData<Map<Satellite, GeoPos>> = _positions
|
||||||
|
|
||||||
fun shouldUseTextLabels(): Boolean {
|
|
||||||
return preferences.shouldUseTextLabels()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun scrollSelection(decrement: Boolean) {
|
fun scrollSelection(decrement: Boolean) {
|
||||||
if (allSatList.isNotEmpty()) {
|
if (allSatellites.isNotEmpty()) {
|
||||||
val index = allSatList.indexOf(selectedSat)
|
val index = allSatellites.indexOf(selectedSatellite)
|
||||||
if (decrement) {
|
if (decrement) {
|
||||||
if (index > 0) selectSatellite(allSatList[index - 1])
|
if (index > 0) selectSatellite(allSatellites[index - 1])
|
||||||
else selectSatellite(allSatList[allSatList.size - 1])
|
else selectSatellite(allSatellites[allSatellites.size - 1])
|
||||||
} else {
|
} else {
|
||||||
if (index < allSatList.size - 1) selectSatellite(allSatList[index + 1])
|
if (index < allSatellites.size - 1) selectSatellite(allSatellites[index + 1])
|
||||||
else selectSatellite(allSatList[0])
|
else selectSatellite(allSatellites[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectDefaultSatellite(catnum: Int?) {
|
fun selectDefaultSatellite(catnum: Int) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
dataRepository.getSelectedSatellites().also { satellites ->
|
val selectedIds = settings.loadEntriesSelection()
|
||||||
|
repository.getEntriesWithIds(selectedIds).also { satellites ->
|
||||||
if (satellites.isNotEmpty()) {
|
if (satellites.isNotEmpty()) {
|
||||||
allSatList = satellites
|
allSatellites = satellites
|
||||||
if (catnum == null) {
|
if (catnum == -1) {
|
||||||
selectSatellite(satellites.first())
|
allPasses.find { pass -> pass.progress < 100 && !pass.isDeepSpace }
|
||||||
|
?.let { pass -> selectSatellite(pass.satellite) }
|
||||||
} else {
|
} else {
|
||||||
satellites.find { it.params.catnum == catnum }?.let { selectSatellite(it) }
|
satellites.find { it.data.catnum == catnum }?.let { selectSatellite(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectSatellite(satellite: Satellite, updateFreq: Long = 2000) {
|
fun selectSatellite(satellite: Satellite, updateFreq: Long = dataUpdateRate) {
|
||||||
selectedSat = satellite
|
selectedSatellite = satellite
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
dataUpdateJob?.cancelAndJoin()
|
dataUpdateJob?.cancelAndJoin()
|
||||||
dataUpdateJob = launch {
|
dataUpdateJob = launch {
|
||||||
val dateNow = Date()
|
val dateNow = Date()
|
||||||
getSatTrack(selectedSat, stationPos, dateNow)
|
getSatTrack(satellite, stationPosition, dateNow)
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
dateNow.time = System.currentTimeMillis()
|
dateNow.time = System.currentTimeMillis()
|
||||||
getPositions(allSatList, stationPos, dateNow)
|
getPositions(allSatellites, stationPosition, dateNow)
|
||||||
getSatFootprint(selectedSat, stationPos, dateNow)
|
getSatFootprint(satellite, stationPosition, dateNow)
|
||||||
getSatData(selectedSat, stationPos, dateNow)
|
getSatData(satellite, stationPosition, dateNow)
|
||||||
delay(updateFreq)
|
delay(updateFreq)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,64 +117,77 @@ class MapViewModel @Inject constructor(
|
|||||||
private suspend fun getSatTrack(satellite: Satellite, pos: GeoPos, date: Date) {
|
private suspend fun getSatTrack(satellite: Satellite, pos: GeoPos, date: Date) {
|
||||||
val satTracks = mutableListOf<List<GeoPos>>()
|
val satTracks = mutableListOf<List<GeoPos>>()
|
||||||
val currentTrack = mutableListOf<GeoPos>()
|
val currentTrack = mutableListOf<GeoPos>()
|
||||||
val endDate = Date(date.time + (satellite.orbitalPeriod * 2.4 * 60000L).toLong())
|
val endDate = Date(date.time + (satellite.data.orbitalPeriod * 2.4 * 60000L).toLong())
|
||||||
var oldLongitude = 0.0
|
var oldLongitude = 0.0
|
||||||
predictor.getSatTrack(satellite, pos, date.time, endDate.time).forEach { satPos ->
|
satelliteManager.getTrack(satellite, pos, date.time, endDate.time).forEach { satPos ->
|
||||||
val osmLat = clipLat(Math.toDegrees(satPos.latitude))
|
val osmLat = clipLat(satPos.latitude.toDegrees())
|
||||||
val osmLon = clipLon(Math.toDegrees(satPos.longitude))
|
val osmLon = clipLon(satPos.longitude.toDegrees())
|
||||||
val currentPosition = GeoPos(osmLat, osmLon)
|
val currentPosition = GeoPos(osmLat, osmLon)
|
||||||
if (oldLongitude < -170.0 && currentPosition.longitude > 170.0) {
|
if (oldLongitude < -170.0 && currentPosition.lon > 170.0) {
|
||||||
// adding left terminal position
|
// adding left terminal position
|
||||||
currentTrack.add(GeoPos(osmLat, -180.0))
|
currentTrack.add(GeoPos(osmLat, -180.0))
|
||||||
val finishedTrack = mutableListOf<GeoPos>().apply { addAll(currentTrack) }
|
val finishedTrack = mutableListOf<GeoPos>().apply { addAll(currentTrack) }
|
||||||
satTracks.add(finishedTrack)
|
satTracks.add(finishedTrack)
|
||||||
currentTrack.clear()
|
currentTrack.clear()
|
||||||
} else if (oldLongitude > 170.0 && currentPosition.longitude < -170.0) {
|
} else if (oldLongitude > 170.0 && currentPosition.lon < -170.0) {
|
||||||
// adding right terminal position
|
// adding right terminal position
|
||||||
currentTrack.add(GeoPos(osmLat, 180.0))
|
currentTrack.add(GeoPos(osmLat, 180.0))
|
||||||
val finishedTrack = mutableListOf<GeoPos>().apply { addAll(currentTrack) }
|
val finishedTrack = mutableListOf<GeoPos>().apply { addAll(currentTrack) }
|
||||||
satTracks.add(finishedTrack)
|
satTracks.add(finishedTrack)
|
||||||
currentTrack.clear()
|
currentTrack.clear()
|
||||||
}
|
}
|
||||||
oldLongitude = currentPosition.longitude
|
oldLongitude = currentPosition.lon
|
||||||
currentTrack.add(currentPosition)
|
currentTrack.add(currentPosition)
|
||||||
}
|
}
|
||||||
satTracks.add(currentTrack)
|
satTracks.add(currentTrack)
|
||||||
_satTrack.postValue(satTracks)
|
_track.postValue(satTracks)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getPositions(satellites: List<Satellite>, pos: GeoPos, date: Date) {
|
private suspend fun getPositions(satellites: List<Satellite>, pos: GeoPos, date: Date) {
|
||||||
val positions = mutableMapOf<Satellite, GeoPos>()
|
val positions = mutableMapOf<Satellite, GeoPos>()
|
||||||
satellites.forEach { satellite ->
|
satellites.forEach { satellite ->
|
||||||
val satPos = predictor.getSatPos(satellite, pos, date.time)
|
val satPos = satelliteManager.getPosition(satellite, pos, date.time)
|
||||||
val osmLat = clipLat(Math.toDegrees(satPos.latitude))
|
val osmLat = clipLat(satPos.latitude.toDegrees())
|
||||||
val osmLon = clipLon(Math.toDegrees(satPos.longitude))
|
val osmLon = clipLon(satPos.longitude.toDegrees())
|
||||||
positions[satellite] = GeoPos(osmLat, osmLon)
|
positions[satellite] = GeoPos(osmLat, osmLon)
|
||||||
}
|
}
|
||||||
_satPositions.postValue(positions)
|
_positions.postValue(positions)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getSatFootprint(satellite: Satellite, pos: GeoPos, date: Date) {
|
private suspend fun getSatFootprint(satellite: Satellite, pos: GeoPos, date: Date) {
|
||||||
val satPos = predictor.getSatPos(satellite, pos, date.time)
|
val satPos = satelliteManager.getPosition(satellite, pos, date.time)
|
||||||
val satFootprint = satPos.getRangeCircle().map { rangePos ->
|
_footprint.postValue(satPos)
|
||||||
val osmLat = clipLat(rangePos.latitude)
|
|
||||||
val osmLon = clipLon(rangePos.longitude)
|
|
||||||
GeoPos(osmLat, osmLon)
|
|
||||||
}
|
|
||||||
_satFootprint.postValue(satFootprint)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getSatData(satellite: Satellite, pos: GeoPos, date: Date) {
|
private suspend fun getSatData(sat: Satellite, pos: GeoPos, date: Date) {
|
||||||
val satPos = predictor.getSatPos(satellite, pos, date.time)
|
var aosTime = 0L.toTimerString()
|
||||||
val osmLat = clipLat(Math.toDegrees(satPos.latitude))
|
allPasses.find { pass -> pass.catNum == sat.data.catnum && pass.progress < 100 }
|
||||||
val osmLon = clipLon(Math.toDegrees(satPos.longitude))
|
?.let { satPass ->
|
||||||
|
if (!satPass.isDeepSpace) {
|
||||||
|
aosTime = if (date.time < satPass.aosTime) {
|
||||||
|
val millisBeforeStart = satPass.aosTime.minus(date.time)
|
||||||
|
millisBeforeStart.toTimerString()
|
||||||
|
} else {
|
||||||
|
val millisBeforeEnd = satPass.losTime.minus(date.time)
|
||||||
|
millisBeforeEnd.toTimerString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val satPos = satelliteManager.getPosition(sat, pos, date.time)
|
||||||
|
val azimuth = satPos.azimuth.toDegrees()
|
||||||
|
val elevation = satPos.elevation.toDegrees()
|
||||||
|
val osmLat = clipLat(satPos.latitude.toDegrees())
|
||||||
|
val osmLon = clipLon(satPos.longitude.toDegrees())
|
||||||
val osmPos = GeoPos(osmLat, osmLon)
|
val osmPos = GeoPos(osmLat, osmLon)
|
||||||
val qthLoc = QthConverter.positionToQth(osmPos.latitude, osmPos.longitude) ?: "-- --"
|
val qthLoc = QthConverter.positionToQth(osmPos.lat, osmPos.lon) ?: "-- --"
|
||||||
|
val velocity = satPos.getOrbitalVelocity()
|
||||||
|
val phase = satPos.phase.toDegrees()
|
||||||
|
val visibility = satPos.eclipsed
|
||||||
val satData = MapData(
|
val satData = MapData(
|
||||||
satellite, satellite.params.catnum, satellite.params.name, satPos.range,
|
sat.data.catnum, sat.data.name, aosTime, azimuth, elevation, satPos.distance,
|
||||||
satPos.altitude, satPos.getOrbitalVelocity(), qthLoc, osmPos
|
satPos.altitude, velocity, qthLoc, osmPos, sat.data.orbitalPeriod, phase, visibility
|
||||||
)
|
)
|
||||||
_satData.postValue(satData)
|
_mapData.postValue(satData)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clipLat(latitude: Double): Double {
|
private fun clipLat(latitude: Double): Double {
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* 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.passesScreen
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.WindowManager
|
||||||
|
import androidx.appcompat.app.AppCompatDialogFragment
|
||||||
|
import com.rtbishop.look4sat.R
|
||||||
|
import com.rtbishop.look4sat.databinding.DialogFilterBinding
|
||||||
|
import com.rtbishop.look4sat.domain.ISettingsManager
|
||||||
|
import com.rtbishop.look4sat.presentation.setNavResult
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class FilterDialog : AppCompatDialogFragment() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var preferences: ISettingsManager
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? {
|
||||||
|
return inflater.inflate(R.layout.dialog_filter, group, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, state: Bundle?) {
|
||||||
|
super.onViewCreated(view, state)
|
||||||
|
DialogFilterBinding.bind(view).apply {
|
||||||
|
dialog?.window?.setBackgroundDrawableResource(R.color.transparent)
|
||||||
|
dialog?.window?.setLayout(
|
||||||
|
(resources.displayMetrics.widthPixels * 0.94).toInt(),
|
||||||
|
WindowManager.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
filterHoursEdit.setText(preferences.getHoursAhead().toString())
|
||||||
|
filterElevEdit.setText(preferences.getMinElevation().toString())
|
||||||
|
filterBtnPos.setOnClickListener {
|
||||||
|
val hoursAhead = try {
|
||||||
|
filterHoursEdit.text.toString().toInt()
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
24
|
||||||
|
}
|
||||||
|
val minElevation = try {
|
||||||
|
filterElevEdit.text.toString().toDouble()
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
16.0
|
||||||
|
}
|
||||||
|
setNavResult("prefs", Pair(hoursAhead, minElevation))
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
filterBtnNeg.setOnClickListener { dismiss() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
-67
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,13 +23,12 @@ import androidx.recyclerview.widget.AsyncListDiffer
|
|||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.rtbishop.look4sat.R
|
import com.rtbishop.look4sat.R
|
||||||
import com.rtbishop.look4sat.databinding.ItemPassGeoBinding
|
import com.rtbishop.look4sat.databinding.ItemPassBinding
|
||||||
import com.rtbishop.look4sat.databinding.ItemPassLeoBinding
|
|
||||||
import com.rtbishop.look4sat.domain.predict.SatPass
|
import com.rtbishop.look4sat.domain.predict.SatPass
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class PassesAdapter(private val isUTC: Boolean, private val clickListener: PassesClickListener) :
|
class PassesAdapter(private val clickListener: PassesClickListener) :
|
||||||
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
private val diffCallback = object : DiffUtil.ItemCallback<SatPass>() {
|
private val diffCallback = object : DiffUtil.ItemCallback<SatPass>() {
|
||||||
@@ -42,6 +41,7 @@ class PassesAdapter(private val isUTC: Boolean, private val clickListener: Passe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
private val differ = AsyncListDiffer(this, diffCallback)
|
private val differ = AsyncListDiffer(this, diffCallback)
|
||||||
|
private var isUTC: Boolean = false
|
||||||
|
|
||||||
interface PassesClickListener {
|
interface PassesClickListener {
|
||||||
fun navigateToPass(satPass: SatPass)
|
fun navigateToPass(satPass: SatPass)
|
||||||
@@ -51,58 +51,62 @@ class PassesAdapter(private val isUTC: Boolean, private val clickListener: Passe
|
|||||||
differ.submitList(passes)
|
differ.submitList(passes)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = differ.currentList.size
|
fun setUTC(isUTC: Boolean) {
|
||||||
|
this.isUTC = isUTC
|
||||||
override fun getItemViewType(position: Int): Int {
|
|
||||||
return if (differ.currentList[position].isDeepSpace) 1
|
|
||||||
else 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = differ.currentList.size
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return if (viewType == 0) {
|
return PassHolder.from(parent)
|
||||||
SatPassLeoHolder.from(parent)
|
|
||||||
} else {
|
|
||||||
SatPassGeoHolder.from(parent)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
if (holder.itemViewType == 0) {
|
(holder as PassHolder).bind(differ.currentList[position], clickListener, isUTC)
|
||||||
(holder as SatPassLeoHolder).bind(differ.currentList[position], clickListener, isUTC)
|
|
||||||
} else {
|
|
||||||
(holder as SatPassGeoHolder).bind(differ.currentList[position], clickListener)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class SatPassLeoHolder private constructor(private val binding: ItemPassLeoBinding) :
|
class PassHolder private constructor(private val binding: ItemPassBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
private val satIdFormat = itemView.context.getString(R.string.pass_satId)
|
private val satIdFormat = itemView.context.getString(R.string.pass_satId)
|
||||||
private val tcaAzFormat = itemView.context.getString(R.string.pass_tcaAz)
|
|
||||||
private val aosAzFormat = itemView.context.getString(R.string.pass_aosAz)
|
private val aosAzFormat = itemView.context.getString(R.string.pass_aosAz)
|
||||||
private val maxElFormat = itemView.context.getString(R.string.pass_maxEl)
|
private val altFormat = itemView.context.getString(R.string.pass_altitude)
|
||||||
private val losAzFormat = itemView.context.getString(R.string.pass_losAz)
|
private val losAzFormat = itemView.context.getString(R.string.pass_los)
|
||||||
private val startTimeFormat = itemView.context.getString(R.string.pass_startTime)
|
private val startTimeFormat = itemView.context.getString(R.string.pass_startTime)
|
||||||
|
private val elevFormat = itemView.context.getString(R.string.pass_elevation)
|
||||||
private val endTimeFormat = itemView.context.getString(R.string.pass_endTime)
|
private val endTimeFormat = itemView.context.getString(R.string.pass_endTime)
|
||||||
|
private val placeholder = itemView.context.getString(R.string.pass_placeholder)
|
||||||
private val timeZoneUTC = TimeZone.getTimeZone("UTC")
|
private val timeZoneUTC = TimeZone.getTimeZone("UTC")
|
||||||
private val startFormat = SimpleDateFormat(startTimeFormat, Locale.getDefault())
|
private val startFormat = SimpleDateFormat(startTimeFormat, Locale.getDefault())
|
||||||
private val endFormat = SimpleDateFormat(endTimeFormat, Locale.getDefault())
|
private val endFormat = SimpleDateFormat(endTimeFormat, Locale.getDefault())
|
||||||
|
|
||||||
fun bind(satPass: SatPass, listener: PassesClickListener, shouldUseUTC: Boolean) {
|
fun bind(satPass: SatPass, listener: PassesClickListener, shouldUseUTC: Boolean) {
|
||||||
binding.apply {
|
binding.apply {
|
||||||
if (shouldUseUTC) {
|
if (satPass.isDeepSpace) {
|
||||||
startFormat.timeZone = timeZoneUTC
|
passName.text = satPass.name
|
||||||
endFormat.timeZone = timeZoneUTC
|
passId.text = String.format(satIdFormat, satPass.catNum)
|
||||||
|
passAos.text = String.format(aosAzFormat, satPass.aosAzimuth)
|
||||||
|
passAltitude.text = String.format(altFormat, satPass.altitude)
|
||||||
|
passLos.text = String.format(losAzFormat, satPass.losAzimuth)
|
||||||
|
passStart.text = placeholder
|
||||||
|
passElev.text = String.format(elevFormat, satPass.maxElevation)
|
||||||
|
passEnd.text = placeholder
|
||||||
|
passProgress.progress = 100
|
||||||
|
} else {
|
||||||
|
if (shouldUseUTC) {
|
||||||
|
startFormat.timeZone = timeZoneUTC
|
||||||
|
endFormat.timeZone = timeZoneUTC
|
||||||
|
}
|
||||||
|
passName.text = satPass.name
|
||||||
|
passId.text = String.format(satIdFormat, satPass.catNum)
|
||||||
|
passAos.text = String.format(aosAzFormat, satPass.aosAzimuth)
|
||||||
|
passAltitude.text = String.format(altFormat, satPass.altitude)
|
||||||
|
passLos.text = String.format(losAzFormat, satPass.losAzimuth)
|
||||||
|
passStart.text = startFormat.format(Date(satPass.aosTime))
|
||||||
|
passElev.text = String.format(elevFormat, satPass.maxElevation)
|
||||||
|
passEnd.text = endFormat.format(Date(satPass.losTime))
|
||||||
|
passProgress.progress = satPass.progress
|
||||||
}
|
}
|
||||||
passLeoName.text = satPass.name
|
|
||||||
passLeoId.text = String.format(satIdFormat, satPass.catNum)
|
|
||||||
passLeoAos.text = String.format(aosAzFormat, satPass.aosAzimuth)
|
|
||||||
passLeoElev.text = String.format(maxElFormat, satPass.maxElevation)
|
|
||||||
passLeoLos.text = String.format(losAzFormat, satPass.losAzimuth)
|
|
||||||
passLeoStart.text = startFormat.format(Date(satPass.aosTime))
|
|
||||||
passLeoTca.text = String.format(tcaAzFormat, satPass.tcaAzimuth)
|
|
||||||
passLeoEnd.text = endFormat.format(Date(satPass.losTime))
|
|
||||||
passLeoProgress.progress = satPass.progress
|
|
||||||
}
|
}
|
||||||
|
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
@@ -111,39 +115,9 @@ class PassesAdapter(private val isUTC: Boolean, private val clickListener: Passe
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun from(parent: ViewGroup): SatPassLeoHolder {
|
fun from(parent: ViewGroup): PassHolder {
|
||||||
val inflater = LayoutInflater.from(parent.context)
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
return SatPassLeoHolder(ItemPassLeoBinding.inflate(inflater, parent, false))
|
return PassHolder(ItemPassBinding.inflate(inflater, parent, false))
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SatPassGeoHolder private constructor(private val binding: ItemPassGeoBinding) :
|
|
||||||
RecyclerView.ViewHolder(binding.root) {
|
|
||||||
|
|
||||||
private val satIdFormat = itemView.context.getString(R.string.pass_satId)
|
|
||||||
private val azFormat = itemView.context.getString(R.string.pat_azimuth)
|
|
||||||
private val altFormat = itemView.context.getString(R.string.pat_altitude)
|
|
||||||
private val elevFormat = itemView.context.getString(R.string.pat_elevation)
|
|
||||||
|
|
||||||
fun bind(satPass: SatPass, listener: PassesClickListener) {
|
|
||||||
binding.apply {
|
|
||||||
passGeoName.text = satPass.name
|
|
||||||
passGeoId.text = String.format(satIdFormat, satPass.catNum)
|
|
||||||
passGeoAz.text = String.format(azFormat, satPass.tcaAzimuth)
|
|
||||||
passGeoAlt.text = String.format(altFormat, satPass.altitude)
|
|
||||||
passGeoEl.text = String.format(elevFormat, satPass.maxElevation)
|
|
||||||
}
|
|
||||||
|
|
||||||
itemView.setOnClickListener {
|
|
||||||
listener.navigateToPass(satPass)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun from(parent: ViewGroup): SatPassGeoHolder {
|
|
||||||
val inflater = LayoutInflater.from(parent.context)
|
|
||||||
return SatPassGeoHolder(ItemPassGeoBinding.inflate(inflater, parent, false))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+116
-86
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -17,121 +17,151 @@
|
|||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.presentation.passesScreen
|
package com.rtbishop.look4sat.presentation.passesScreen
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import android.widget.Toast
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||||
import com.rtbishop.look4sat.R
|
import com.rtbishop.look4sat.R
|
||||||
import com.rtbishop.look4sat.databinding.FragmentPassesBinding
|
import com.rtbishop.look4sat.databinding.FragmentPassesBinding
|
||||||
import com.rtbishop.look4sat.domain.predict.SatPass
|
|
||||||
import com.rtbishop.look4sat.domain.model.DataState
|
import com.rtbishop.look4sat.domain.model.DataState
|
||||||
import com.rtbishop.look4sat.presentation.ItemDivider
|
import com.rtbishop.look4sat.domain.predict.SatPass
|
||||||
import com.rtbishop.look4sat.presentation.navigateSafe
|
import com.rtbishop.look4sat.presentation.clickWithDebounce
|
||||||
import com.rtbishop.look4sat.presentation.toTimerString
|
import com.rtbishop.look4sat.presentation.getNavResult
|
||||||
|
import com.rtbishop.look4sat.utility.toTimerString
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class PassesFragment : Fragment(R.layout.fragment_passes), PassesAdapter.PassesClickListener {
|
class PassesFragment : Fragment(R.layout.fragment_passes), PassesAdapter.PassesClickListener {
|
||||||
|
|
||||||
private val passesViewModel: PassesViewModel by viewModels()
|
private val viewModel: PassesViewModel by viewModels()
|
||||||
private val permReqContract = ActivityResultContracts.RequestMultiplePermissions()
|
private val passesAdapter = PassesAdapter(this)
|
||||||
private val locPermFine = Manifest.permission.ACCESS_FINE_LOCATION
|
private var binding: FragmentPassesBinding? = null
|
||||||
private val locPermCoarse = Manifest.permission.ACCESS_COARSE_LOCATION
|
|
||||||
private val locPermReq = registerForActivityResult(permReqContract) { permissions ->
|
|
||||||
when {
|
|
||||||
permissions[locPermFine] == true -> passesViewModel.triggerInitialSetup()
|
|
||||||
permissions[locPermCoarse] == true -> passesViewModel.triggerInitialSetup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
setupComponents(view)
|
binding = FragmentPassesBinding.bind(view).apply {
|
||||||
}
|
passesAdapter.setUTC(viewModel.shouldUseUTC())
|
||||||
|
|
||||||
private fun setupComponents(view: View) {
|
|
||||||
val passesAdapter = PassesAdapter(passesViewModel.shouldUseUTC(), this)
|
|
||||||
val binding = FragmentPassesBinding.bind(view).apply {
|
|
||||||
passesRecycler.apply {
|
passesRecycler.apply {
|
||||||
setHasFixedSize(true)
|
setHasFixedSize(true)
|
||||||
adapter = passesAdapter
|
adapter = passesAdapter
|
||||||
isVerticalScrollBarEnabled = false
|
layoutManager = LinearLayoutManager(requireContext())
|
||||||
layoutManager = LinearLayoutManager(context)
|
|
||||||
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||||
addItemDecoration(ItemDivider(R.drawable.rec_divider_dark))
|
addItemDecoration(DividerItemDecoration(requireContext(), 1))
|
||||||
|
addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
if (dy > 0 && passesFab.visibility == View.VISIBLE) passesFab.hide()
|
||||||
|
else if (dy < 0 && passesFab.visibility != View.VISIBLE) passesFab.show()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
passesRefresh.setOnClickListener { passesViewModel.forceCalculation() }
|
passesBtnRefresh.clickWithDebounce { viewModel.calculatePasses() }
|
||||||
}
|
passesBtnMap.clickWithDebounce {
|
||||||
passesViewModel.passes.observe(viewLifecycleOwner, { passesResult ->
|
val dir = PassesFragmentDirections.globalToMap()
|
||||||
handleNewPasses(passesResult, passesAdapter, binding)
|
findNavController().navigate(dir)
|
||||||
})
|
|
||||||
passesViewModel.isFirstLaunchDone.observe(viewLifecycleOwner, { setupDone ->
|
|
||||||
if (!setupDone) locPermReq.launch(arrayOf(locPermFine, locPermCoarse))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleNewPasses(
|
|
||||||
dataState: DataState<List<SatPass>>,
|
|
||||||
passesAdapter: PassesAdapter,
|
|
||||||
binding: FragmentPassesBinding
|
|
||||||
) {
|
|
||||||
when (dataState) {
|
|
||||||
is DataState.Success -> {
|
|
||||||
passesAdapter.submitList(dataState.data)
|
|
||||||
binding.apply {
|
|
||||||
passesError.visibility = View.INVISIBLE
|
|
||||||
passesProgress.visibility = View.INVISIBLE
|
|
||||||
passesRecycler.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
tickMainTimer(dataState.data, binding)
|
|
||||||
}
|
}
|
||||||
is DataState.Loading -> {
|
passesBtnFilter.clickWithDebounce {
|
||||||
binding.apply {
|
val dir = PassesFragmentDirections.passesToFilter()
|
||||||
passesTimer.text = 0L.toTimerString()
|
findNavController().navigate(dir)
|
||||||
passesError.visibility = View.INVISIBLE
|
|
||||||
passesRecycler.visibility = View.INVISIBLE
|
|
||||||
passesProgress.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else -> {
|
passesBtnSettings.clickWithDebounce {
|
||||||
binding.apply {
|
val dir = PassesFragmentDirections.globalToSettings()
|
||||||
passesTimer.text = 0L.toTimerString()
|
findNavController().navigate(dir)
|
||||||
passesProgress.visibility = View.INVISIBLE
|
|
||||||
passesRecycler.visibility = View.INVISIBLE
|
|
||||||
passesError.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
setupObservers()
|
||||||
}
|
|
||||||
|
|
||||||
private fun tickMainTimer(passes: List<SatPass>, binding: FragmentPassesBinding) {
|
|
||||||
if (passes.isNotEmpty()) {
|
|
||||||
val timeNow = System.currentTimeMillis()
|
|
||||||
try {
|
|
||||||
val nextPass = passes.first { it.aosTime.minus(timeNow) > 0 }
|
|
||||||
val millisBeforeStart = nextPass.aosTime.minus(timeNow)
|
|
||||||
binding.passesTimer.text = millisBeforeStart.toTimerString()
|
|
||||||
} catch (e: NoSuchElementException) {
|
|
||||||
val lastPass = passes.last()
|
|
||||||
val millisBeforeEnd = lastPass.losTime.minus(timeNow)
|
|
||||||
binding.passesTimer.text = millisBeforeEnd.toTimerString()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
binding.passesTimer.text = 0L.toTimerString()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun navigateToPass(satPass: SatPass) {
|
override fun navigateToPass(satPass: SatPass) {
|
||||||
if (satPass.progress < 100) {
|
if (satPass.progress < 100) satPass.run {
|
||||||
val bundle = bundleOf("catNum" to satPass.catNum, "aosTime" to satPass.aosTime)
|
val dir = PassesFragmentDirections.globalToRadar(this.catNum, this.aosTime)
|
||||||
findNavController().navigateSafe(R.id.action_passes_to_radar, bundle)
|
findNavController().navigate(dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
binding?.passesRecycler?.adapter = null
|
||||||
|
binding = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupObservers() {
|
||||||
|
viewModel.entriesTotal.observe(viewLifecycleOwner) { number ->
|
||||||
|
handleEntriesTotal(number)
|
||||||
|
}
|
||||||
|
viewModel.passes.observe(viewLifecycleOwner) { passesResult ->
|
||||||
|
handleNewPasses(passesResult)
|
||||||
|
}
|
||||||
|
getNavResult<Pair<Int, Double>>(R.id.nav_passes, "prefs") { prefs ->
|
||||||
|
viewModel.calculatePasses(prefs.first, prefs.second)
|
||||||
|
}
|
||||||
|
getNavResult<List<Int>>(R.id.nav_passes, "selection") { items ->
|
||||||
|
viewModel.calculatePasses(selection = items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleEntriesTotal(number: Int) {
|
||||||
|
binding?.run {
|
||||||
|
if (number > 0) {
|
||||||
|
passesFab.clickWithDebounce {
|
||||||
|
val direction = PassesFragmentDirections.globalToEntries()
|
||||||
|
findNavController().navigate(direction)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
passesFab.clickWithDebounce {
|
||||||
|
val errorMessage = getString(R.string.passes_empty_db)
|
||||||
|
Toast.makeText(requireContext(), errorMessage, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleNewPasses(state: DataState<List<SatPass>>) {
|
||||||
|
binding?.run {
|
||||||
|
when (state) {
|
||||||
|
is DataState.Success -> {
|
||||||
|
passesAdapter.submitList(state.data)
|
||||||
|
tickMainTimer(state.data)
|
||||||
|
if (state.data.isNotEmpty()) { // show new passes list
|
||||||
|
passesEmpty.visibility = View.INVISIBLE
|
||||||
|
passesProgress.visibility = View.INVISIBLE
|
||||||
|
} else { // show no passes message
|
||||||
|
passesEmpty.visibility = View.VISIBLE
|
||||||
|
passesProgress.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is DataState.Loading -> {
|
||||||
|
passesEmpty.visibility = View.INVISIBLE
|
||||||
|
passesProgress.visibility = View.VISIBLE
|
||||||
|
passesTimer.text = 0L.toTimerString()
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tickMainTimer(passes: List<SatPass>) {
|
||||||
|
binding?.run {
|
||||||
|
if (passes.isNotEmpty()) {
|
||||||
|
val timeNow = System.currentTimeMillis()
|
||||||
|
try {
|
||||||
|
val nextPass = passes.first { it.aosTime.minus(timeNow) > 0 }
|
||||||
|
val millisBeforeStart = nextPass.aosTime.minus(timeNow)
|
||||||
|
passesTimer.text = millisBeforeStart.toTimerString()
|
||||||
|
} catch (e: NoSuchElementException) {
|
||||||
|
val lastPass = passes.last()
|
||||||
|
val millisBeforeEnd = lastPass.losTime.minus(timeNow)
|
||||||
|
passesTimer.text = millisBeforeEnd.toTimerString()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
passesTimer.text = 0L.toTimerString()
|
||||||
|
}
|
||||||
|
passesBtnRefresh.isEnabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+45
-83
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -17,106 +17,68 @@
|
|||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.presentation.passesScreen
|
package com.rtbishop.look4sat.presentation.passesScreen
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.*
|
||||||
import androidx.lifecycle.MutableLiveData
|
import com.rtbishop.look4sat.domain.IDataRepository
|
||||||
import androidx.lifecycle.ViewModel
|
import com.rtbishop.look4sat.domain.ISatelliteManager
|
||||||
import androidx.lifecycle.viewModelScope
|
import com.rtbishop.look4sat.domain.ISettingsManager
|
||||||
import com.rtbishop.look4sat.domain.predict.Predictor
|
|
||||||
import com.rtbishop.look4sat.domain.predict.SatPass
|
|
||||||
import com.rtbishop.look4sat.domain.model.DataState
|
import com.rtbishop.look4sat.domain.model.DataState
|
||||||
import com.rtbishop.look4sat.domain.DataRepository
|
import com.rtbishop.look4sat.domain.predict.SatPass
|
||||||
import com.rtbishop.look4sat.framework.PreferencesSource
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import java.util.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class PassesViewModel @Inject constructor(
|
class PassesViewModel @Inject constructor(
|
||||||
private val dataRepository: DataRepository,
|
private val satelliteManager: ISatelliteManager,
|
||||||
private val predictor: Predictor,
|
private val repository: IDataRepository,
|
||||||
private val preferences: PreferencesSource
|
private val settings: ISettingsManager
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _passes = MutableLiveData<DataState<List<SatPass>>>(DataState.Loading)
|
|
||||||
private val _isFirstLaunchDone = MutableLiveData<Boolean>()
|
|
||||||
private var passesProcessing: Job? = null
|
private var passesProcessing: Job? = null
|
||||||
|
private val _passes = MutableLiveData<DataState<List<SatPass>>>(DataState.Loading)
|
||||||
val passes: LiveData<DataState<List<SatPass>>> = _passes
|
val passes: LiveData<DataState<List<SatPass>>> = _passes
|
||||||
val isFirstLaunchDone: LiveData<Boolean> = _isFirstLaunchDone
|
val entriesTotal: LiveData<Int> = repository.getEntriesTotal().asLiveData()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (preferences.isSetupDone()) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
_passes.postValue(DataState.Loading)
|
|
||||||
val timeNow = System.currentTimeMillis()
|
|
||||||
val satellites = dataRepository.getSelectedSatellites()
|
|
||||||
val stationPos = preferences.loadStationPosition()
|
|
||||||
val hoursAhead = preferences.getHoursAhead()
|
|
||||||
val minElev = preferences.getMinElevation()
|
|
||||||
predictor.triggerCalculation(satellites, stationPos, timeNow, hoursAhead, minElev)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_isFirstLaunchDone.value = false
|
|
||||||
}
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
predictor.passes.collect { passes ->
|
satelliteManager.calculatedPasses.collect { passes ->
|
||||||
passesProcessing?.cancelAndJoin()
|
passesProcessing?.cancelAndJoin()
|
||||||
passesProcessing = viewModelScope.launch { tickPasses(passes) }
|
passesProcessing = viewModelScope.launch {
|
||||||
}
|
while (isActive) {
|
||||||
}
|
val time = System.currentTimeMillis()
|
||||||
}
|
val newPasses = satelliteManager.processPasses(passes, time)
|
||||||
|
_passes.postValue(DataState.Success(newPasses))
|
||||||
fun triggerInitialSetup() {
|
delay(1000)
|
||||||
preferences.updatePositionFromGPS()
|
|
||||||
viewModelScope.launch {
|
|
||||||
_passes.postValue(DataState.Loading)
|
|
||||||
val satellites = dataRepository.getSelectedSatellites()
|
|
||||||
val stationPos = preferences.loadStationPosition()
|
|
||||||
val hoursAhead = preferences.getHoursAhead()
|
|
||||||
val minElev = preferences.getMinElevation()
|
|
||||||
dataRepository.updateDataFromWeb()
|
|
||||||
dataRepository.updateSelection(isSelected = true)
|
|
||||||
predictor.forceCalculation(satellites, stationPos, Date().time, hoursAhead, minElev)
|
|
||||||
preferences.setSetupDone()
|
|
||||||
_isFirstLaunchDone.value = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun forceCalculation() {
|
|
||||||
viewModelScope.launch {
|
|
||||||
_passes.postValue(DataState.Loading)
|
|
||||||
passesProcessing?.cancelAndJoin()
|
|
||||||
val timeNow = System.currentTimeMillis()
|
|
||||||
val satellites = dataRepository.getSelectedSatellites()
|
|
||||||
val stationPos = preferences.loadStationPosition()
|
|
||||||
val hoursAhead = preferences.getHoursAhead()
|
|
||||||
val minElev = preferences.getMinElevation()
|
|
||||||
predictor.forceCalculation(satellites, stationPos, timeNow, hoursAhead, minElev)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shouldUseUTC(): Boolean {
|
|
||||||
return preferences.shouldUseUTC()
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun tickPasses(passes: List<SatPass>) = withContext(Dispatchers.Default) {
|
|
||||||
var currentPasses = passes
|
|
||||||
while (isActive) {
|
|
||||||
val timeNow = System.currentTimeMillis()
|
|
||||||
currentPasses.forEach { pass ->
|
|
||||||
if (!pass.isDeepSpace) {
|
|
||||||
val timeStart = pass.aosTime
|
|
||||||
if (timeNow > timeStart) {
|
|
||||||
val deltaNow = timeNow.minus(timeStart).toFloat()
|
|
||||||
val deltaTotal = pass.losTime.minus(timeStart).toFloat()
|
|
||||||
pass.progress = ((deltaNow / deltaTotal) * 100).toInt()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
currentPasses = currentPasses.filter { it.progress < 100 }
|
}
|
||||||
_passes.postValue(DataState.Success(currentPasses.map { it.copy() }))
|
viewModelScope.launch {
|
||||||
delay(1000)
|
repository.updateState.collect { updateState ->
|
||||||
|
if (updateState is DataState.Success) calculatePasses()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
calculatePasses()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shouldUseUTC() = settings.getUseUTC()
|
||||||
|
|
||||||
|
fun calculatePasses(
|
||||||
|
hoursAhead: Int = settings.getHoursAhead(),
|
||||||
|
minElevation: Double = settings.getMinElevation(),
|
||||||
|
timeRef: Long = System.currentTimeMillis(),
|
||||||
|
selection: List<Int>? = null
|
||||||
|
) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_passes.postValue(DataState.Loading)
|
||||||
|
passesProcessing?.cancelAndJoin()
|
||||||
|
selection?.let { items -> settings.saveEntriesSelection(items) }
|
||||||
|
settings.setHoursAhead(hoursAhead)
|
||||||
|
settings.setMinElevation(minElevation)
|
||||||
|
val stationPos = settings.loadStationPosition()
|
||||||
|
val selectedIds = settings.loadEntriesSelection()
|
||||||
|
val satellites = repository.getEntriesWithIds(selectedIds)
|
||||||
|
satelliteManager.calculatePasses(satellites, stationPos, timeRef, hoursAhead, minElevation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package com.rtbishop.look4sat.presentation.radarScreen
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothManager
|
||||||
|
import android.util.Log
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
class BTReporter(private val manager: BluetoothManager, private val reporterScope: CoroutineScope) {
|
||||||
|
|
||||||
|
private val tag = "BTReporter"
|
||||||
|
private val sppid: UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb")
|
||||||
|
private lateinit var outputStream: OutputStream
|
||||||
|
private var rotationConnectionJob: Job? = null
|
||||||
|
private var rotationReportingJob: Job? = null
|
||||||
|
private var connected = false
|
||||||
|
private var connecting = false
|
||||||
|
|
||||||
|
fun isConnected(): Boolean = connected
|
||||||
|
|
||||||
|
fun isConnecting(): Boolean = connecting
|
||||||
|
|
||||||
|
fun connectBTDevice(deviceId: String) {
|
||||||
|
if (!connected) {
|
||||||
|
rotationConnectionJob = reporterScope.launch {
|
||||||
|
try {
|
||||||
|
manager.adapter.getRemoteDevice(deviceId)?.let { device ->
|
||||||
|
device.createInsecureRfcommSocketToServiceRecord(sppid)?.let { socket ->
|
||||||
|
connecting = true
|
||||||
|
socket.connect()
|
||||||
|
outputStream = socket.outputStream
|
||||||
|
connected = true
|
||||||
|
connecting = false
|
||||||
|
Log.i(tag, "$tag: Connected!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: SecurityException) {
|
||||||
|
Log.e(tag, "$tag: ${e.message}")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(tag, "$tag: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reportRotation(format: String, azimuth: Int, elevation: Int) {
|
||||||
|
if (connected) {
|
||||||
|
rotationReportingJob = reporterScope.launch {
|
||||||
|
val newElevation = if (elevation > 0) elevation else 0
|
||||||
|
try {
|
||||||
|
val azimuthString = intToStringWithLeadingZeroes(azimuth)
|
||||||
|
val elevationString = intToStringWithLeadingZeroes(newElevation)
|
||||||
|
val crChar = '\r'
|
||||||
|
val nlChar = '\n'
|
||||||
|
val tbChar = '\t'
|
||||||
|
var buffer = format.replace("\$AZ", azimuthString)
|
||||||
|
buffer = buffer.replace("\$EL", elevationString)
|
||||||
|
buffer = buffer.replace("\\r", crChar.toString())
|
||||||
|
buffer = buffer.replace("\\n", nlChar.toString())
|
||||||
|
buffer = buffer.replace("\\t", tbChar.toString())
|
||||||
|
Log.i(tag, "$tag: Sending $buffer")
|
||||||
|
if (connected) outputStream.write(buffer.toByteArray())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(tag, "$tag: ${e.message}")
|
||||||
|
connected = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun intToStringWithLeadingZeroes(value: Int): String {
|
||||||
|
return if (value > 0) {
|
||||||
|
if (value < 10) "00$value" else if (value < 100) "0$value" else "$value"
|
||||||
|
} else {
|
||||||
|
val absValue = abs(value)
|
||||||
|
if (value > -10) "-00$absValue" else if (value > -100) "-0$absValue" else "-$absValue"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
+96
-104
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -17,46 +17,46 @@
|
|||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.presentation.radarScreen
|
package com.rtbishop.look4sat.presentation.radarScreen
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.provider.CalendarContract
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import androidx.navigation.fragment.navArgs
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||||
import com.rtbishop.look4sat.R
|
import com.rtbishop.look4sat.R
|
||||||
import com.rtbishop.look4sat.databinding.FragmentRadarBinding
|
import com.rtbishop.look4sat.databinding.FragmentRadarBinding
|
||||||
import com.rtbishop.look4sat.framework.PreferencesSource
|
|
||||||
import com.rtbishop.look4sat.domain.predict.SatPass
|
import com.rtbishop.look4sat.domain.predict.SatPass
|
||||||
import com.rtbishop.look4sat.domain.predict.SatPos
|
import com.rtbishop.look4sat.domain.predict.SatPos
|
||||||
import com.rtbishop.look4sat.presentation.ItemDivider
|
import com.rtbishop.look4sat.presentation.clickWithDebounce
|
||||||
import com.rtbishop.look4sat.presentation.navigateSafe
|
import com.rtbishop.look4sat.utility.toDegrees
|
||||||
import com.rtbishop.look4sat.presentation.toTimerString
|
import com.rtbishop.look4sat.utility.toTimerString
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class RadarFragment : Fragment(R.layout.fragment_radar) {
|
class RadarFragment : Fragment(R.layout.fragment_radar) {
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var preferences: PreferencesSource
|
|
||||||
private val viewModel: RadarViewModel by viewModels()
|
private val viewModel: RadarViewModel by viewModels()
|
||||||
|
private val navArgs: RadarFragmentArgs by navArgs()
|
||||||
|
private val radioAdapter = RadioAdapter()
|
||||||
|
private var binding: FragmentRadarBinding? = null
|
||||||
private var radarView: RadarView? = null
|
private var radarView: RadarView? = null
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
FragmentRadarBinding.bind(view).apply {
|
binding = FragmentRadarBinding.bind(view).apply {
|
||||||
val transAdapter = TransmittersAdapter()
|
|
||||||
radarRecycler.apply {
|
radarRecycler.apply {
|
||||||
setHasFixedSize(true)
|
setHasFixedSize(true)
|
||||||
adapter = transAdapter
|
this.adapter = radioAdapter
|
||||||
isVerticalScrollBarEnabled = false
|
this.layoutManager = LinearLayoutManager(requireContext())
|
||||||
layoutManager = LinearLayoutManager(context)
|
|
||||||
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||||
addItemDecoration(ItemDivider(R.drawable.rec_divider_dark))
|
addItemDecoration(DividerItemDecoration(requireContext(), 1))
|
||||||
}
|
}
|
||||||
setupObservers(transAdapter, this)
|
setupObservers()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,98 +70,90 @@ class RadarFragment : Fragment(R.layout.fragment_radar) {
|
|||||||
viewModel.disableSensor()
|
viewModel.disableSensor()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupObservers(
|
override fun onDestroyView() {
|
||||||
transmittersAdapter: TransmittersAdapter,
|
binding?.radarRecycler?.adapter = null
|
||||||
binding: FragmentRadarBinding
|
radarView = null
|
||||||
) {
|
binding = null
|
||||||
val catNum = requireArguments().getInt("catNum")
|
super.onDestroyView()
|
||||||
val aosTime = requireArguments().getLong("aosTime")
|
|
||||||
viewModel.getPass(catNum, aosTime).observe(viewLifecycleOwner) { pass ->
|
|
||||||
radarView = RadarView(requireContext()).apply {
|
|
||||||
setShowAim(preferences.shouldUseCompass())
|
|
||||||
setScanning(preferences.shouldShowSweep())
|
|
||||||
}
|
|
||||||
binding.radarFrame.addView(radarView)
|
|
||||||
viewModel.radarData.observe(viewLifecycleOwner, { passData ->
|
|
||||||
radarView?.setPosition(passData.satPos)
|
|
||||||
radarView?.setPositions(passData.satTrack)
|
|
||||||
setPassText(pass, passData.satPos, binding)
|
|
||||||
})
|
|
||||||
viewModel.transmitters.observe(viewLifecycleOwner, { list ->
|
|
||||||
if (list.isNotEmpty()) {
|
|
||||||
transmittersAdapter.submitList(list)
|
|
||||||
binding.radarRecycler.visibility = View.VISIBLE
|
|
||||||
binding.radarRecyclerMsg.visibility = View.INVISIBLE
|
|
||||||
} else {
|
|
||||||
binding.radarRecycler.visibility = View.INVISIBLE
|
|
||||||
binding.radarRecyclerMsg.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
radarView?.invalidate()
|
|
||||||
})
|
|
||||||
viewModel.orientation.observe(viewLifecycleOwner, { orientation ->
|
|
||||||
radarView?.setOrientation(
|
|
||||||
orientation.first,
|
|
||||||
orientation.second,
|
|
||||||
orientation.third
|
|
||||||
)
|
|
||||||
})
|
|
||||||
binding.radarToMapButton.setOnClickListener {
|
|
||||||
val bundle = bundleOf("catNum" to pass.catNum)
|
|
||||||
findNavController().navigateSafe(R.id.action_radar_to_map, bundle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// binding.radarNotifyButton.setOnClickListener {
|
|
||||||
// this.context?.let { context ->
|
|
||||||
//
|
|
||||||
// val choicesList = arrayOf(
|
|
||||||
// getString(R.string.notification_on_start_pass),
|
|
||||||
// getString(R.string.notification_5_min_before),
|
|
||||||
// getString(R.string.notification_10_min_before),
|
|
||||||
// getString(R.string.notification_15_min_before),
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// MaterialAlertDialogBuilder(context)
|
|
||||||
// .setTitle(getString(R.string.notification_title))
|
|
||||||
// .setSingleChoiceItems(choicesList, -1) { dialog, which ->
|
|
||||||
// // TODO in this place should set a notification by NotificationBuilder
|
|
||||||
// Toast.makeText(context, "Reminder was saved", Toast.LENGTH_SHORT).show()
|
|
||||||
// dialog.dismiss()
|
|
||||||
// }
|
|
||||||
// .setNegativeButton(getString(R.string.btn_cancel)) { _, _ ->
|
|
||||||
// // noop
|
|
||||||
// }
|
|
||||||
// .show()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setPassText(satPass: SatPass, satPos: SatPos, binding: FragmentRadarBinding) {
|
private fun setupObservers() {
|
||||||
val timeNow = System.currentTimeMillis()
|
viewModel.getPass(navArgs.catNum, navArgs.aosTime).observe(viewLifecycleOwner) { pass ->
|
||||||
val polarAz = getString(R.string.pat_azimuth)
|
binding?.run {
|
||||||
val polarEl = getString(R.string.pat_elevation)
|
radarView = RadarView(requireContext()).apply {
|
||||||
val polarRng = getString(R.string.pat_distance)
|
setShowAim(viewModel.getUseCompass())
|
||||||
val polarAlt = getString(R.string.pat_altitude)
|
setScanning(viewModel.getShowSweep())
|
||||||
binding.radarAz.text = String.format(polarAz, Math.toDegrees(satPos.azimuth))
|
}
|
||||||
binding.radarEl.text = String.format(polarEl, Math.toDegrees(satPos.elevation))
|
radarCard.addView(radarView)
|
||||||
binding.radarDst.text = String.format(polarRng, satPos.range)
|
viewModel.radarData.observe(viewLifecycleOwner) { passData ->
|
||||||
binding.radarAlt.text = String.format(polarAlt, satPos.altitude)
|
radarView?.setPosition(passData.satPos)
|
||||||
binding.radarName.text = satPass.name
|
radarView?.setPositions(passData.satTrack)
|
||||||
|
setPassText(pass, passData.satPos)
|
||||||
if (!satPass.isDeepSpace) {
|
}
|
||||||
if (timeNow < satPass.aosTime) {
|
viewModel.transmitters.observe(viewLifecycleOwner) { list ->
|
||||||
val millisBeforeStart = satPass.aosTime.minus(timeNow)
|
if (list.isNotEmpty()) {
|
||||||
binding.radarTimer.text = millisBeforeStart.toTimerString()
|
radioAdapter.submitList(list)
|
||||||
} else {
|
radarProgress.visibility = View.INVISIBLE
|
||||||
val millisBeforeEnd = satPass.losTime.minus(timeNow)
|
} else {
|
||||||
binding.radarTimer.text = millisBeforeEnd.toTimerString()
|
radarProgress.visibility = View.INVISIBLE
|
||||||
if (timeNow > satPass.losTime) {
|
radarEmptyLayout.visibility = View.VISIBLE
|
||||||
binding.radarTimer.text = 0L.toTimerString()
|
}
|
||||||
findNavController().navigateSafe(R.id.action_radar_to_passes)
|
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.clickWithDebounce {
|
||||||
|
val intent = Intent(Intent.ACTION_INSERT)
|
||||||
|
.setData(CalendarContract.Events.CONTENT_URI)
|
||||||
|
.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, pass.aosTime)
|
||||||
|
.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, pass.losTime)
|
||||||
|
.putExtra(CalendarContract.Events.TITLE, pass.name)
|
||||||
|
.putExtra(CalendarContract.Events.DESCRIPTION, "Look4Sat")
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
radarBtnSettings.clickWithDebounce {
|
||||||
|
val direction = RadarFragmentDirections.globalToSettings()
|
||||||
|
findNavController().navigate(direction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
binding.radarTimer.text = 0L.toTimerString()
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -22,23 +22,23 @@ import android.graphics.*
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.rtbishop.look4sat.R
|
import com.rtbishop.look4sat.R
|
||||||
|
import com.rtbishop.look4sat.domain.predict.PI_2
|
||||||
import com.rtbishop.look4sat.domain.predict.SatPos
|
import com.rtbishop.look4sat.domain.predict.SatPos
|
||||||
|
import com.rtbishop.look4sat.domain.predict.TWO_PI
|
||||||
|
import com.rtbishop.look4sat.utility.toRadians
|
||||||
import kotlin.math.cos
|
import kotlin.math.cos
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.sin
|
import kotlin.math.sin
|
||||||
|
|
||||||
class RadarView(context: Context) : View(context) {
|
class RadarView(context: Context) : View(context) {
|
||||||
|
|
||||||
private val defaultColor = ContextCompat.getColor(context, R.color.themeLight)
|
private val defaultColor = ContextCompat.getColor(context, R.color.accent)
|
||||||
private val scale = resources.displayMetrics.density
|
private val scale = resources.displayMetrics.density
|
||||||
private val radarWidth = resources.displayMetrics.widthPixels
|
|
||||||
private val radarRadius = radarWidth * 0.48f
|
|
||||||
private val piDiv2 = Math.PI / 2.0
|
|
||||||
private val strokeSize = scale * 2f
|
private val strokeSize = scale * 2f
|
||||||
private var position: SatPos? = null
|
private var position: SatPos? = null
|
||||||
private var positions: List<SatPos> = emptyList()
|
private var positions: List<SatPos> = emptyList()
|
||||||
|
|
||||||
private var radarColor = ContextCompat.getColor(context, R.color.greyLight)
|
private var radarColor = ContextCompat.getColor(context, R.color.textMain)
|
||||||
private var radarCircleNum = 3
|
private var radarCircleNum = 3
|
||||||
private var radarPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
private var radarPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
color = radarColor
|
color = radarColor
|
||||||
@@ -49,7 +49,7 @@ class RadarView(context: Context) : View(context) {
|
|||||||
private var shouldShowSweep = true
|
private var shouldShowSweep = true
|
||||||
private var sweepColor = defaultColor
|
private var sweepColor = defaultColor
|
||||||
private var sweepDegrees = 0f
|
private var sweepDegrees = 0f
|
||||||
private var sweepSpeed = 30.0f
|
private var sweepSpeed = 15.0f
|
||||||
private var sweepPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
private var sweepPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
color = sweepColor
|
color = sweepColor
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,7 @@ class RadarView(context: Context) : View(context) {
|
|||||||
|
|
||||||
private val arrowPath = Path()
|
private val arrowPath = Path()
|
||||||
private var arrowPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
private var arrowPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
color = ContextCompat.getColor(context, R.color.themeLight)
|
color = ContextCompat.getColor(context, R.color.accent)
|
||||||
style = Paint.Style.FILL
|
style = Paint.Style.FILL
|
||||||
strokeWidth = strokeSize
|
strokeWidth = strokeSize
|
||||||
}
|
}
|
||||||
@@ -125,17 +125,17 @@ class RadarView(context: Context) : View(context) {
|
|||||||
override fun onDraw(canvas: Canvas) {
|
override fun onDraw(canvas: Canvas) {
|
||||||
val radarWidth = width - paddingLeft - paddingRight
|
val radarWidth = width - paddingLeft - paddingRight
|
||||||
val radarHeight = height - paddingTop - paddingBottom
|
val radarHeight = height - paddingTop - paddingBottom
|
||||||
val radarRadius = min(width, height) * 0.49f
|
val radarRadius = min(width, height) * 0.48f
|
||||||
val cx = paddingLeft + radarWidth / 2f
|
val cx = paddingLeft + radarWidth / 2f
|
||||||
val cy = paddingTop + radarHeight / 2f
|
val cy = paddingTop + radarHeight / 2f
|
||||||
|
|
||||||
if (positions.isNotEmpty() && !isTrackCreated) {
|
if (positions.isNotEmpty() && !isTrackCreated) {
|
||||||
createPassTrajectory()
|
createPassTrajectory(radarRadius)
|
||||||
createPassTrajectoryArrow()
|
createPassTrajectoryArrow()
|
||||||
isTrackCreated = true
|
isTrackCreated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.drawColor(ContextCompat.getColor(context, R.color.greyDark))
|
canvas.drawColor(ContextCompat.getColor(context, R.color.cardRegular))
|
||||||
drawRadarCircle(canvas, cx, cy, radarRadius)
|
drawRadarCircle(canvas, cx, cy, radarRadius)
|
||||||
drawRadarCross(canvas, cx, cy, radarRadius)
|
drawRadarCross(canvas, cx, cy, radarRadius)
|
||||||
drawRadarText(canvas, cx, radarRadius)
|
drawRadarText(canvas, cx, radarRadius)
|
||||||
@@ -146,10 +146,10 @@ class RadarView(context: Context) : View(context) {
|
|||||||
canvas.drawPath(trackPath, arrowPaint)
|
canvas.drawPath(trackPath, arrowPaint)
|
||||||
}
|
}
|
||||||
if (shouldShowBeacons) {
|
if (shouldShowBeacons) {
|
||||||
drawSatellite(canvas)
|
drawSatellite(canvas, radarRadius)
|
||||||
}
|
}
|
||||||
if (shouldShowAim) {
|
if (shouldShowAim) {
|
||||||
drawCrosshair(canvas, azimuth, pitch)
|
drawCrosshair(canvas, azimuth, pitch, radarRadius)
|
||||||
}
|
}
|
||||||
if (shouldShowSweep) {
|
if (shouldShowSweep) {
|
||||||
canvas.translate(-cx, -cy)
|
canvas.translate(-cx, -cy)
|
||||||
@@ -177,7 +177,7 @@ class RadarView(context: Context) : View(context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun drawSatellite(canvas: Canvas) {
|
private fun drawSatellite(canvas: Canvas, radarRadius: Float) {
|
||||||
position?.let { satPos ->
|
position?.let { satPos ->
|
||||||
if (satPos.elevation > 0) {
|
if (satPos.elevation > 0) {
|
||||||
val satX = sph2CartX(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
|
val satX = sph2CartX(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
|
||||||
@@ -187,9 +187,9 @@ class RadarView(context: Context) : View(context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun drawCrosshair(canvas: Canvas, azimuth: Float, pitch: Float) {
|
private fun drawCrosshair(canvas: Canvas, azimuth: Float, pitch: Float, radarRadius: Float) {
|
||||||
val azimuthRad = Math.toRadians(azimuth.toDouble())
|
val azimuthRad = azimuth.toDouble().toRadians()
|
||||||
val tmpElevation = Math.toRadians(pitch.toDouble())
|
val tmpElevation = pitch.toDouble().toRadians()
|
||||||
val elevationRad = if (tmpElevation > 0.0) 0.0 else tmpElevation
|
val elevationRad = if (tmpElevation > 0.0) 0.0 else tmpElevation
|
||||||
val crossX = sph2CartX(azimuthRad, -elevationRad, radarRadius.toDouble())
|
val crossX = sph2CartX(azimuthRad, -elevationRad, radarRadius.toDouble())
|
||||||
val crossY = sph2CartY(azimuthRad, -elevationRad, radarRadius.toDouble())
|
val crossY = sph2CartY(azimuthRad, -elevationRad, radarRadius.toDouble())
|
||||||
@@ -199,16 +199,16 @@ class RadarView(context: Context) : View(context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun sph2CartX(azimuth: Double, elevation: Double, r: Double): Float {
|
private fun sph2CartX(azimuth: Double, elevation: Double, r: Double): Float {
|
||||||
val radius = r * (piDiv2 - elevation) / piDiv2
|
val radius = r * (PI_2 - elevation) / PI_2
|
||||||
return (radius * cos(piDiv2 - azimuth)).toFloat()
|
return (radius * cos(PI_2 - azimuth)).toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sph2CartY(azimuth: Double, elevation: Double, r: Double): Float {
|
private fun sph2CartY(azimuth: Double, elevation: Double, r: Double): Float {
|
||||||
val radius = r * (piDiv2 - elevation) / piDiv2
|
val radius = r * (PI_2 - elevation) / PI_2
|
||||||
return (radius * sin(piDiv2 - azimuth)).toFloat()
|
return (radius * sin(PI_2 - azimuth)).toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createPassTrajectory() {
|
private fun createPassTrajectory(radarRadius: Float) {
|
||||||
positions.forEachIndexed { index, satPos ->
|
positions.forEachIndexed { index, satPos ->
|
||||||
val passX = sph2CartX(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
|
val passX = sph2CartX(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
|
||||||
val passY = sph2CartY(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
|
val passY = sph2CartY(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
|
||||||
@@ -223,7 +223,7 @@ class RadarView(context: Context) : View(context) {
|
|||||||
private fun createPassTrajectoryArrow() {
|
private fun createPassTrajectoryArrow() {
|
||||||
val radius = beaconSize
|
val radius = beaconSize
|
||||||
val sides = 3
|
val sides = 3
|
||||||
val angle = 2.0 * Math.PI / sides
|
val angle = TWO_PI / sides
|
||||||
arrowPath.moveTo((radius * cos(angle)).toFloat(), (radius * sin(angle)).toFloat())
|
arrowPath.moveTo((radius * cos(angle)).toFloat(), (radius * sin(angle)).toFloat())
|
||||||
for (i in 1 until sides) {
|
for (i in 1 until sides) {
|
||||||
val x = (radius * cos(angle - angle * i)).toFloat()
|
val x = (radius * cos(angle - angle * i)).toFloat()
|
||||||
|
|||||||
+85
-59
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -17,104 +17,130 @@
|
|||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.presentation.radarScreen
|
package com.rtbishop.look4sat.presentation.radarScreen
|
||||||
|
|
||||||
import androidx.lifecycle.*
|
import android.hardware.GeomagneticField
|
||||||
import com.rtbishop.look4sat.domain.DataReporter
|
import android.util.Log
|
||||||
import com.rtbishop.look4sat.domain.DataRepository
|
import androidx.lifecycle.LiveData
|
||||||
import com.rtbishop.look4sat.domain.predict.Predictor
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.liveData
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.rtbishop.look4sat.domain.IDataRepository
|
||||||
|
import com.rtbishop.look4sat.domain.ISatelliteManager
|
||||||
|
import com.rtbishop.look4sat.domain.ISettingsManager
|
||||||
|
import com.rtbishop.look4sat.domain.model.SatRadio
|
||||||
|
import com.rtbishop.look4sat.domain.predict.GeoPos
|
||||||
import com.rtbishop.look4sat.domain.predict.SatPass
|
import com.rtbishop.look4sat.domain.predict.SatPass
|
||||||
import com.rtbishop.look4sat.domain.predict.SatPos
|
import com.rtbishop.look4sat.domain.predict.SatPos
|
||||||
import com.rtbishop.look4sat.domain.model.Transmitter
|
import com.rtbishop.look4sat.domain.predict.Satellite
|
||||||
import com.rtbishop.look4sat.framework.OrientationSource
|
import com.rtbishop.look4sat.framework.OrientationManager
|
||||||
import com.rtbishop.look4sat.framework.PreferencesSource
|
import com.rtbishop.look4sat.utility.DataReporter
|
||||||
import com.rtbishop.look4sat.presentation.round
|
import com.rtbishop.look4sat.utility.round
|
||||||
|
import com.rtbishop.look4sat.utility.toDegrees
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.*
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class RadarViewModel @Inject constructor(
|
class RadarViewModel @Inject constructor(
|
||||||
private val orientationSource: OrientationSource,
|
private val orientationManager: OrientationManager,
|
||||||
private val preferences: PreferencesSource,
|
private val reporter: DataReporter,
|
||||||
private val predictor: Predictor,
|
private val btReporter: BTReporter,
|
||||||
private val dataRepository: DataRepository,
|
private val satManager: ISatelliteManager,
|
||||||
private val dataReporter: DataReporter
|
private val repository: IDataRepository,
|
||||||
) : ViewModel(), OrientationSource.OrientationListener {
|
private val settings: ISettingsManager
|
||||||
|
) : ViewModel(), OrientationManager.OrientationListener {
|
||||||
|
|
||||||
private val stationPos = preferences.loadStationPosition()
|
private val stationPos = settings.loadStationPosition()
|
||||||
private val _passData = MutableLiveData<RadarData>()
|
private val _passData = MutableLiveData<RadarData>()
|
||||||
private val _transmitters = MutableLiveData<List<Transmitter>>()
|
private val _transmitters = MutableLiveData<List<SatRadio>>()
|
||||||
private val _orientation = MutableLiveData<Triple<Float, Float, Float>>()
|
private val _orientation = MutableLiveData<Triple<Float, Float, Float>>()
|
||||||
val radarData: LiveData<RadarData> = _passData
|
val radarData: LiveData<RadarData> = _passData
|
||||||
val transmitters: LiveData<List<Transmitter>> = _transmitters
|
val transmitters: LiveData<List<SatRadio>> = _transmitters
|
||||||
val orientation: LiveData<Triple<Float, Float, Float>> = _orientation
|
val orientation: LiveData<Triple<Float, Float, Float>> = _orientation
|
||||||
|
|
||||||
fun getPass(catNum: Int, aosTime: Long) = liveData {
|
fun getPass(catNum: Int, aosTime: Long) = liveData {
|
||||||
predictor.passes.collect { passes ->
|
satManager.calculatedPasses.collect { passes ->
|
||||||
val pass = passes.find { it.catNum == catNum && it.aosTime == aosTime }
|
val pass = passes.find { pass -> pass.catNum == catNum && pass.aosTime == aosTime }
|
||||||
pass?.let { satPass ->
|
pass?.let { satPass ->
|
||||||
emit(satPass)
|
emit(satPass)
|
||||||
sendPassData(satPass)
|
val transmitters = repository.getRadiosWithId(satPass.catNum)
|
||||||
processTransmitters(satPass)
|
viewModelScope.launch {
|
||||||
|
while (isActive) {
|
||||||
|
val timeNow = System.currentTimeMillis()
|
||||||
|
val satPos = satManager.getPosition(satPass.satellite, stationPos, timeNow)
|
||||||
|
sendPassData(satPass, satPos, satPass.satellite)
|
||||||
|
sendPassDataBT(satPos)
|
||||||
|
processRadios(transmitters, satPass.satellite, timeNow)
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun enableSensor() {
|
fun enableSensor() {
|
||||||
if (preferences.shouldUseCompass()) orientationSource.startListening(this)
|
if (settings.getUseCompass()) orientationManager.startListening(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun disableSensor() {
|
fun disableSensor() {
|
||||||
if (preferences.shouldUseCompass()) orientationSource.stopListening()
|
if (settings.getUseCompass()) orientationManager.stopListening()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getUseCompass(): Boolean = settings.getUseCompass()
|
||||||
|
|
||||||
|
fun getShowSweep(): Boolean = settings.getShowSweep()
|
||||||
|
|
||||||
override fun onOrientationChanged(azimuth: Float, pitch: Float, roll: Float) {
|
override fun onOrientationChanged(azimuth: Float, pitch: Float, roll: Float) {
|
||||||
_orientation.value = Triple(azimuth + preferences.getMagDeclination(), pitch, roll)
|
_orientation.value = Triple(azimuth + getMagDeclination(stationPos), pitch, roll)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendPassData(satPass: SatPass) {
|
private fun getMagDeclination(geoPos: GeoPos, time: Long = System.currentTimeMillis()): Float {
|
||||||
|
val latitude = geoPos.lat.toFloat()
|
||||||
|
val longitude = geoPos.lon.toFloat()
|
||||||
|
return GeomagneticField(latitude, longitude, 0f, time).declination
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendPassData(satPass: SatPass, satPos: SatPos, satellite: Satellite) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
var satTrack: List<SatPos> = emptyList()
|
var track: List<SatPos> = emptyList()
|
||||||
if (!satPass.isDeepSpace) {
|
if (!satPass.isDeepSpace) {
|
||||||
val startDate = satPass.aosTime
|
track = satManager.getTrack(satellite, stationPos, satPass.aosTime, satPass.losTime)
|
||||||
val endDate = satPass.losTime
|
|
||||||
satTrack = predictor.getSatTrack(satPass.satellite, stationPos, startDate, endDate)
|
|
||||||
}
|
}
|
||||||
while (isActive) {
|
if (settings.getRotatorEnabled()) {
|
||||||
val satPos = predictor.getSatPos(satPass.satellite, stationPos, Date().time)
|
val server = settings.getRotatorServer()
|
||||||
if (preferences.isRotatorEnabled()) {
|
val port = settings.getRotatorPort().toInt()
|
||||||
val server = preferences.getRotatorServer().first
|
val azimuth = satPos.azimuth.toDegrees().round(2)
|
||||||
val port = preferences.getRotatorServer().second
|
val elevation = satPos.elevation.toDegrees().round(2)
|
||||||
val azimuth = Math.toDegrees(satPos.azimuth).round(1)
|
reporter.reportRotation(server, port, azimuth, elevation)
|
||||||
val elevation = Math.toDegrees(satPos.elevation).round(1)
|
}
|
||||||
dataReporter.reportRotation(server, port, azimuth, elevation)
|
_passData.postValue(RadarData(satPos, track))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendPassDataBT(satPos: SatPos) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
if (settings.getBTEnabled()) {
|
||||||
|
val btDevice = settings.getBTDeviceAddr()
|
||||||
|
if (btReporter.isConnected()) {
|
||||||
|
val format = settings.getBTFormat()
|
||||||
|
val azimuth = satPos.azimuth.toDegrees().round(0).toInt()
|
||||||
|
val elevation = satPos.elevation.toDegrees().round(0).toInt()
|
||||||
|
btReporter.reportRotation(format, azimuth, elevation)
|
||||||
|
} else if (!btReporter.isConnecting()) {
|
||||||
|
Log.i("BTReporter", "BTReporter: Attempting to connect...")
|
||||||
|
btReporter.connectBTDevice(btDevice)
|
||||||
}
|
}
|
||||||
_passData.postValue(RadarData(satPos, satTrack))
|
|
||||||
delay(1000)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processTransmitters(satPass: SatPass) {
|
private fun processRadios(radios: List<SatRadio>, satellite: Satellite, time: Long) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val transmitters = dataRepository.getTransmitters(satPass.catNum)
|
delay(125)
|
||||||
while (isActive) {
|
val list = satManager.processRadios(satellite, stationPos, radios, time)
|
||||||
val satPos = predictor.getSatPos(satPass.satellite, stationPos, Date().time)
|
_transmitters.postValue(list)
|
||||||
val copiedList = transmitters.map { it.copy() }
|
|
||||||
copiedList.forEach { transmitter ->
|
|
||||||
transmitter.downlink?.let {
|
|
||||||
transmitter.downlink = satPos.getDownlinkFreq(it)
|
|
||||||
}
|
|
||||||
transmitter.uplink?.let {
|
|
||||||
transmitter.uplink = satPos.getUplinkFreq(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_transmitters.postValue(copiedList.map { it.copy() })
|
|
||||||
delay(1000)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* 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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-110
@@ -1,110 +0,0 @@
|
|||||||
/*
|
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
|
||||||
* Copyright (C) 2019-2021 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.ItemTransBinding
|
|
||||||
import com.rtbishop.look4sat.domain.model.Transmitter
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class TransmittersAdapter : RecyclerView.Adapter<TransmittersAdapter.TransHolder>() {
|
|
||||||
|
|
||||||
private val diffCallback = object : DiffUtil.ItemCallback<Transmitter>() {
|
|
||||||
override fun areItemsTheSame(oldItem: Transmitter, newItem: Transmitter): Boolean {
|
|
||||||
return oldItem.uuid == newItem.uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun areContentsTheSame(oldItem: Transmitter, newItem: Transmitter): Boolean {
|
|
||||||
return oldItem.downlink == newItem.downlink
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private val differ = AsyncListDiffer(this, diffCallback)
|
|
||||||
|
|
||||||
fun submitList(transmitters: List<Transmitter>) {
|
|
||||||
differ.submitList(transmitters)
|
|
||||||
}
|
|
||||||
|
|
||||||
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: ItemTransBinding) :
|
|
||||||
RecyclerView.ViewHolder(binding.root) {
|
|
||||||
|
|
||||||
private val divider = 1000000f
|
|
||||||
private val strNo = itemView.context.getString(R.string.no)
|
|
||||||
private val strYes = itemView.context.getString(R.string.yes)
|
|
||||||
private val mode = itemView.context.getString(R.string.trans_mode)
|
|
||||||
private val formatLink = itemView.context.getString(R.string.trans_link_low)
|
|
||||||
private val formatLinkNull = itemView.context.getString(R.string.trans_no_link)
|
|
||||||
private val isInverted = itemView.context.getString(R.string.trans_inverted)
|
|
||||||
|
|
||||||
fun bind(transmitter: Transmitter) {
|
|
||||||
binding.transDesc.text = transmitter.info
|
|
||||||
|
|
||||||
transmitter.downlink.let { downlink ->
|
|
||||||
if (downlink != null) {
|
|
||||||
val downlinkFreq = downlink / divider
|
|
||||||
binding.transDownlink.text =
|
|
||||||
String.format(Locale.ENGLISH, formatLink, downlinkFreq)
|
|
||||||
} else {
|
|
||||||
binding.transDownlink.text = formatLinkNull
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
transmitter.uplink.let { uplink ->
|
|
||||||
if (uplink != null) {
|
|
||||||
val uplinkFreq = uplink / divider
|
|
||||||
binding.transUplink.text = String.format(Locale.ENGLISH, formatLink, uplinkFreq)
|
|
||||||
} else {
|
|
||||||
binding.transUplink.text = formatLinkNull
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transmitter.mode != null) {
|
|
||||||
binding.transMode.text = String.format(mode, transmitter.mode)
|
|
||||||
} else {
|
|
||||||
binding.transMode.text = String.format(mode, strNo)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transmitter.isInverted) {
|
|
||||||
binding.transInverted.text = String.format(isInverted, strYes)
|
|
||||||
} else {
|
|
||||||
binding.transInverted.text = String.format(isInverted, strNo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun from(parent: ViewGroup): TransHolder {
|
|
||||||
val inflater = LayoutInflater.from(parent.context)
|
|
||||||
return TransHolder(ItemTransBinding.inflate(inflater, parent, false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* 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.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.WindowManager
|
||||||
|
import androidx.appcompat.app.AppCompatDialogFragment
|
||||||
|
import com.rtbishop.look4sat.R
|
||||||
|
import com.rtbishop.look4sat.databinding.DialogLocatorBinding
|
||||||
|
import com.rtbishop.look4sat.domain.ISettingsManager
|
||||||
|
import com.rtbishop.look4sat.presentation.setNavResult
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class LocatorDialog : AppCompatDialogFragment() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var preferences: ISettingsManager
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? {
|
||||||
|
return inflater.inflate(R.layout.dialog_locator, group, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, state: Bundle?) {
|
||||||
|
super.onViewCreated(view, state)
|
||||||
|
DialogLocatorBinding.bind(view).run {
|
||||||
|
dialog?.window?.setBackgroundDrawableResource(R.color.transparent)
|
||||||
|
dialog?.window?.setLayout(
|
||||||
|
(resources.displayMetrics.widthPixels * 0.94).toInt(),
|
||||||
|
WindowManager.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
locatorEdit.setText(preferences.loadStationLocator())
|
||||||
|
locatorBtnPos.setOnClickListener {
|
||||||
|
setNavResult("locator", locatorEdit.text.toString())
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
locatorBtnNeg.setOnClickListener { dismiss() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* 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.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.WindowManager
|
||||||
|
import androidx.appcompat.app.AppCompatDialogFragment
|
||||||
|
import com.rtbishop.look4sat.R
|
||||||
|
import com.rtbishop.look4sat.databinding.DialogPositionBinding
|
||||||
|
import com.rtbishop.look4sat.domain.ISettingsManager
|
||||||
|
import com.rtbishop.look4sat.presentation.setNavResult
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class PositionDialog : AppCompatDialogFragment() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var preferences: ISettingsManager
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? {
|
||||||
|
return inflater.inflate(R.layout.dialog_position, group, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, state: Bundle?) {
|
||||||
|
super.onViewCreated(view, state)
|
||||||
|
DialogPositionBinding.bind(view).run {
|
||||||
|
dialog?.window?.setBackgroundDrawableResource(R.color.transparent)
|
||||||
|
dialog?.window?.setLayout(
|
||||||
|
(resources.displayMetrics.widthPixels * 0.94).toInt(),
|
||||||
|
WindowManager.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
val location = preferences.loadStationPosition()
|
||||||
|
positionLatEdit.setText(location.lat.toString())
|
||||||
|
positionLonEdit.setText(location.lon.toString())
|
||||||
|
positionBtnPos.setOnClickListener {
|
||||||
|
val latitude = try {
|
||||||
|
positionLatEdit.text.toString().toDouble()
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
180.0
|
||||||
|
}
|
||||||
|
val longitude = try {
|
||||||
|
positionLonEdit.text.toString().toDouble()
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
400.0
|
||||||
|
}
|
||||||
|
setNavResult("position", Pair(latitude, longitude))
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
positionBtnNeg.setOnClickListener { dismiss() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+257
-74
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
* Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com)
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -18,107 +18,290 @@
|
|||||||
package com.rtbishop.look4sat.presentation.settingsScreen
|
package com.rtbishop.look4sat.presentation.settingsScreen
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.pm.PackageManager
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.InputType
|
import android.text.method.LinkMovementMethod
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.widget.NestedScrollView
|
||||||
import androidx.preference.EditTextPreference
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.preference.Preference
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.fragment.app.viewModels
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import androidx.lifecycle.asLiveData
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import com.rtbishop.look4sat.BuildConfig
|
||||||
import com.rtbishop.look4sat.R
|
import com.rtbishop.look4sat.R
|
||||||
import com.rtbishop.look4sat.framework.PreferencesSource
|
import com.rtbishop.look4sat.databinding.FragmentSettingsBinding
|
||||||
|
import com.rtbishop.look4sat.domain.model.DataState
|
||||||
|
import com.rtbishop.look4sat.domain.predict.GeoPos
|
||||||
|
import com.rtbishop.look4sat.presentation.clickWithDebounce
|
||||||
|
import com.rtbishop.look4sat.presentation.getNavResult
|
||||||
|
import com.rtbishop.look4sat.utility.isValidIPv4
|
||||||
|
import com.rtbishop.look4sat.utility.isValidPort
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import javax.inject.Inject
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class SettingsFragment : PreferenceFragmentCompat() {
|
class SettingsFragment : Fragment(R.layout.fragment_settings) {
|
||||||
|
|
||||||
@Inject
|
private val viewModel: SettingsViewModel by viewModels()
|
||||||
lateinit var preferences: PreferencesSource
|
private val bluetooth = when {
|
||||||
|
Build.VERSION.SDK_INT < Build.VERSION_CODES.S -> Manifest.permission.BLUETOOTH
|
||||||
private val permReqContract = ActivityResultContracts.RequestMultiplePermissions()
|
else -> Manifest.permission.BLUETOOTH_CONNECT
|
||||||
private val locPermFine = Manifest.permission.ACCESS_FINE_LOCATION
|
}
|
||||||
private val locPermCoarse = Manifest.permission.ACCESS_COARSE_LOCATION
|
private val bluetoothContract = ActivityResultContracts.RequestPermission()
|
||||||
private val locPermReq = registerForActivityResult(permReqContract) { permissions ->
|
private val bluetoothRequest = registerForActivityResult(bluetoothContract) { isGranted ->
|
||||||
when {
|
if (!isGranted) {
|
||||||
permissions[locPermFine] == true -> updatePositionFromGPS()
|
showToast(getString(R.string.BTremote_perm_error))
|
||||||
permissions[locPermCoarse] == true -> updatePositionFromGPS()
|
toggleBTstate(isGranted)
|
||||||
else -> showSnack(getString(R.string.pref_pos_gps_error))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private val locationFine = Manifest.permission.ACCESS_FINE_LOCATION
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
private val locationCoarse = Manifest.permission.ACCESS_COARSE_LOCATION
|
||||||
setPreferencesFromResource(R.xml.preference, rootKey)
|
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?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
binding = FragmentSettingsBinding.bind(view).apply {
|
||||||
findPreference<Preference>(PreferencesSource.keyPositionGPS)?.apply {
|
settingsBtnBack.clickWithDebounce { findNavController().navigateUp() }
|
||||||
setOnPreferenceClickListener {
|
settingsScroll.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, y, _, newY ->
|
||||||
updatePositionFromGPS()
|
if (y > newY) settingsFab.hide() else settingsFab.show()
|
||||||
return@setOnPreferenceClickListener true
|
})
|
||||||
|
settingsAbout.aboutVersion.text =
|
||||||
|
String.format(getString(R.string.app_version), BuildConfig.VERSION_NAME)
|
||||||
|
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()
|
||||||
findPreference<Preference>(PreferencesSource.keyPositionQTH)?.apply {
|
setupDataCard()
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
setupRemoteCard()
|
||||||
updatePositionFromQth(newValue.toString())
|
setupBTCard()
|
||||||
}
|
setupOtherCard()
|
||||||
|
setupOutroCard()
|
||||||
|
viewModel.stationPosition.asLiveData().observe(viewLifecycleOwner) { stationPos ->
|
||||||
|
stationPos?.let { handleStationPosition(stationPos) }
|
||||||
}
|
}
|
||||||
|
viewModel.getUpdateState().asLiveData().observe(viewLifecycleOwner) { updateState ->
|
||||||
findPreference<EditTextPreference>(PreferencesSource.keyRotatorAddress)?.apply {
|
updateState?.let { handleSatState(updateState) }
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
|
||||||
val ip4 = "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\\.(?!\$)|\$)){4}\$"
|
|
||||||
if (newValue.toString().matches(ip4.toRegex())) {
|
|
||||||
return@setOnPreferenceChangeListener true
|
|
||||||
} else {
|
|
||||||
showSnack(getString(R.string.tracking_rotator_address_invalid))
|
|
||||||
return@setOnPreferenceChangeListener false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
findPreference<EditTextPreference>(PreferencesSource.keyRotatorPort)?.apply {
|
private fun setupLocationCard() {
|
||||||
setOnBindEditTextListener { it.inputType = InputType.TYPE_CLASS_NUMBER }
|
binding.run {
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
setPositionText(viewModel.getStationPosition())
|
||||||
val portValue = newValue.toString()
|
settingsLocation.locationBtnGps.clickWithDebounce {
|
||||||
if (portValue.isNotEmpty() && portValue.toInt() in 1024..65535) {
|
locationRequest.launch(arrayOf(locationFine, locationCoarse))
|
||||||
return@setOnPreferenceChangeListener true
|
}
|
||||||
} else {
|
settingsLocation.locationBtnManual.clickWithDebounce {
|
||||||
showSnack(getString(R.string.tracking_rotator_port_invalid))
|
val action = SettingsFragmentDirections.globalToPosition()
|
||||||
return@setOnPreferenceChangeListener false
|
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 updatePositionFromQth(qthString: String): Boolean {
|
private fun setupDataCard() {
|
||||||
return if (preferences.updatePositionFromQTH(qthString)) {
|
binding.run {
|
||||||
showSnack(getString(R.string.pref_pos_success))
|
setUpdateTime(viewModel.getLastUpdateTime())
|
||||||
true
|
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 setupOutroCard() {
|
||||||
|
binding.run {
|
||||||
|
settingsOutro.outroThanks.movementMethod = LinkMovementMethod.getInstance()
|
||||||
|
settingsOutro.outroLicense.movementMethod = LinkMovementMethod.getInstance()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
} else {
|
||||||
showSnack(getString(R.string.pref_pos_qth_error))
|
SimpleDateFormat("d MMM yyyy - HH:mm:ss", Locale.getDefault()).format(Date(updateTime))
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
binding.settingsData.dataUpdate.text = String.format(updatePattern, updateDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updatePositionFromGPS() {
|
private fun showToast(message: String) {
|
||||||
val locPermString = Manifest.permission.ACCESS_FINE_LOCATION
|
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
|
||||||
val locPermResult = ContextCompat.checkSelfPermission(requireContext(), locPermString)
|
|
||||||
if (locPermResult == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
if (preferences.updatePositionFromGPS()) {
|
|
||||||
showSnack(getString(R.string.pref_pos_success))
|
|
||||||
} else {
|
|
||||||
showSnack(getString(R.string.pref_pos_gps_null))
|
|
||||||
}
|
|
||||||
} else locPermReq.launch(arrayOf(locPermFine, locPermCoarse))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showSnack(message: String) {
|
private fun gotoUrl(url: String) {
|
||||||
Snackbar.make(requireView(), message, Snackbar.LENGTH_SHORT).show()
|
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* 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 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
|
||||||
|
import com.rtbishop.look4sat.domain.model.DataState
|
||||||
|
import com.rtbishop.look4sat.domain.predict.GeoPos
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class SettingsViewModel @Inject constructor(
|
||||||
|
private val locationManager: ILocationManager,
|
||||||
|
private val repository: IDataRepository,
|
||||||
|
private val settings: ISettingsManager
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
val entriesTotal = repository.getEntriesTotal().asLiveData()
|
||||||
|
val radiosTotal = repository.getRadiosTotal().asLiveData()
|
||||||
|
|
||||||
|
fun updateFromFile(uri: String) = repository.updateFromFile(uri)
|
||||||
|
|
||||||
|
fun updateFromWeb() = repository.updateFromWeb()
|
||||||
|
|
||||||
|
fun clearAllData() = repository.clearAllData()
|
||||||
|
|
||||||
|
fun getUseUTC(): Boolean = settings.getUseUTC()
|
||||||
|
|
||||||
|
fun setUseUTC(value: Boolean) = settings.setUseUTC(value)
|
||||||
|
|
||||||
|
fun getLastUpdateTime(): Long = settings.getLastUpdateTime()
|
||||||
|
|
||||||
|
fun getAutoUpdateEnabled(): Boolean = settings.getAutoUpdateEnabled()
|
||||||
|
|
||||||
|
fun setAutoUpdateEnabled(value: Boolean) = settings.setAutoUpdateEnabled(value)
|
||||||
|
|
||||||
|
fun getUseCompass(): Boolean = settings.getUseCompass()
|
||||||
|
|
||||||
|
fun setUseCompass(value: Boolean) = settings.setUseCompass(value)
|
||||||
|
|
||||||
|
fun getShowSweep(): Boolean = settings.getShowSweep()
|
||||||
|
|
||||||
|
fun setShowSweep(value: Boolean) = settings.setShowSweep(value)
|
||||||
|
|
||||||
|
fun getRotatorEnabled(): Boolean = settings.getRotatorEnabled()
|
||||||
|
|
||||||
|
fun setRotatorEnabled(value: Boolean) = settings.setRotatorEnabled(value)
|
||||||
|
|
||||||
|
fun getRotatorServer(): String = settings.getRotatorServer()
|
||||||
|
|
||||||
|
fun setRotatorServer(value: String) = settings.setRotatorServer(value)
|
||||||
|
|
||||||
|
fun getRotatorPort(): String = settings.getRotatorPort()
|
||||||
|
|
||||||
|
fun setRotatorPort(value: String) = settings.setRotatorPort(value)
|
||||||
|
|
||||||
|
fun getBTEnabled(): Boolean = settings.getBTEnabled()
|
||||||
|
|
||||||
|
fun setBTEnabled(value: Boolean) = settings.setBTEnabled(value)
|
||||||
|
|
||||||
|
fun getBTFormat(): String = settings.getBTFormat()
|
||||||
|
|
||||||
|
fun setBTFormat(value: String) = settings.setBTFormat(value)
|
||||||
|
|
||||||
|
// fun getBTDeviceName(): String = settings.getBTDeviceName()
|
||||||
|
|
||||||
|
// fun setBTDeviceName(value: String) = settings.setBTDeviceName(value)
|
||||||
|
|
||||||
|
fun getBTDeviceAddr(): String = settings.getBTDeviceAddr()
|
||||||
|
|
||||||
|
fun setBTDeviceAddr(value: String) = settings.setBTDeviceAddr(value)
|
||||||
|
|
||||||
|
fun getUpdateState() = repository.updateState
|
||||||
|
|
||||||
|
fun setUpdateHandled() = repository.setUpdateStateHandled()
|
||||||
|
|
||||||
|
val stationPosition: SharedFlow<DataState<GeoPos>> = locationManager.stationPosition
|
||||||
|
|
||||||
|
fun getStationPosition(): GeoPos = locationManager.getStationPosition()
|
||||||
|
|
||||||
|
fun setStationPosition(lat: Double, lon: Double) = locationManager.setStationPosition(lat, lon)
|
||||||
|
|
||||||
|
fun setPositionFromGps() = locationManager.setPositionFromGps()
|
||||||
|
|
||||||
|
fun setPositionFromNet() = locationManager.setPositionFromNet()
|
||||||
|
|
||||||
|
fun setPositionFromQth(qthString: String) = locationManager.setPositionFromQth(qthString)
|
||||||
|
|
||||||
|
fun setPositionHandled() = locationManager.setPositionHandled()
|
||||||
|
}
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
|
||||||
* Copyright (C) 2019-2021 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.sourcesScreen
|
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.core.widget.doOnTextChanged
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.rtbishop.look4sat.databinding.ItemSourceBinding
|
|
||||||
import com.rtbishop.look4sat.framework.model.Source
|
|
||||||
|
|
||||||
class SourcesAdapter(private val sources: MutableList<Source> = mutableListOf()) :
|
|
||||||
RecyclerView.Adapter<SourcesAdapter.TleSourceHolder>() {
|
|
||||||
|
|
||||||
fun getSources(): List<Source> {
|
|
||||||
return sources.filter { it.sourceUrl.contains("https://") }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSources(list: List<Source>) {
|
|
||||||
sources.clear()
|
|
||||||
sources.addAll(list)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addSource() {
|
|
||||||
sources.add(Source())
|
|
||||||
notifyItemInserted(itemCount - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TleSourceHolder {
|
|
||||||
val binding = ItemSourceBinding
|
|
||||||
.inflate(LayoutInflater.from(parent.context), parent, false)
|
|
||||||
return TleSourceHolder(binding)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: TleSourceHolder, position: Int) {
|
|
||||||
holder.bind(sources[position])
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
|
||||||
return sources.size
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class TleSourceHolder(private val binding: ItemSourceBinding) :
|
|
||||||
RecyclerView.ViewHolder(binding.root) {
|
|
||||||
|
|
||||||
fun bind(source: Source) {
|
|
||||||
binding.sourceUrl.setText(source.sourceUrl)
|
|
||||||
binding.sourceUrl.doOnTextChanged { txt, _, _, _ -> source.sourceUrl = txt.toString() }
|
|
||||||
binding.sourceBtn.setOnClickListener {
|
|
||||||
sources.remove(source)
|
|
||||||
notifyItemRemoved(adapterPosition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
/*
|
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
|
||||||
* Copyright (C) 2019-2021 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.sourcesScreen
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.view.WindowManager
|
|
||||||
import androidx.appcompat.app.AppCompatDialogFragment
|
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import com.rtbishop.look4sat.R
|
|
||||||
import com.rtbishop.look4sat.databinding.DialogSourcesBinding
|
|
||||||
import com.rtbishop.look4sat.framework.model.Source
|
|
||||||
import com.rtbishop.look4sat.presentation.setNavResult
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class SourcesDialog : AppCompatDialogFragment() {
|
|
||||||
|
|
||||||
private val viewModel: SourcesViewModel by viewModels()
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? {
|
|
||||||
return inflater.inflate(R.layout.dialog_sources, group, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, state: Bundle?) {
|
|
||||||
super.onViewCreated(view, state)
|
|
||||||
viewModel.sources.observe(viewLifecycleOwner, { sources ->
|
|
||||||
val adapter = SourcesAdapter().apply { setSources(sources.map { Source(it) }) }
|
|
||||||
DialogSourcesBinding.bind(view).apply {
|
|
||||||
dialog?.window?.setLayout(
|
|
||||||
WindowManager.LayoutParams.MATCH_PARENT,
|
|
||||||
WindowManager.LayoutParams.WRAP_CONTENT
|
|
||||||
)
|
|
||||||
sourcesRecycler.apply {
|
|
||||||
this.adapter = adapter
|
|
||||||
layoutManager = LinearLayoutManager(requireContext())
|
|
||||||
}
|
|
||||||
sourcesBtnAdd.setOnClickListener {
|
|
||||||
adapter.addSource()
|
|
||||||
}
|
|
||||||
sourcesBtnPos.setOnClickListener {
|
|
||||||
setNavResult("sources", adapter.getSources().map { it.sourceUrl })
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
sourcesBtnNeg.setOnClickListener { dismiss() }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-15
@@ -1,15 +0,0 @@
|
|||||||
package com.rtbishop.look4sat.presentation.sourcesScreen
|
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.liveData
|
|
||||||
import com.rtbishop.look4sat.domain.DataRepository
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
class SourcesViewModel @Inject constructor(dataRepository: DataRepository) : ViewModel() {
|
|
||||||
|
|
||||||
val sources = liveData {
|
|
||||||
emit(dataRepository.getWebSources())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.0 KiB |
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@color/themeLight"
|
|
||||||
android:pathData="M8,5v14l11,-7z" />
|
|
||||||
</vector>
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:autoMirrored="true"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z" />
|
||||||
|
</vector>
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:width="16dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="233.76132"
|
|
||||||
android:viewportHeight="338.40268">
|
|
||||||
<path
|
|
||||||
android:pathData="m113.404,0c-10.877,-0.015 -21.753,0.458 -32.594,1.417 -12.911,1.175 -26.51,2.595 -38.779,7.061 -4.484,1.634 -9.105,3.597 -12.514,7.061 -4.184,4.257 -5.55,10.84 -2.495,16.148 2.172,3.77 5.85,6.432 9.752,8.194 5.082,2.27 10.389,3.998 15.834,5.154 15.16,3.351 30.861,4.666 46.349,5.227 17.166,0.693 34.36,0.131 51.444,-1.68 4.225,-0.464 8.442,-1.021 12.651,-1.671 4.958,-0.76 8.14,-7.243 6.679,-11.759 -1.748,-5.399 -6.446,-7.493 -11.759,-6.678 -0.783,0.123 -1.561,0.236 -2.345,0.35l-0.564,0.082c-1.8,0.228 -3.6,0.44 -5.4,0.638 -3.718,0.401 -7.445,0.728 -11.181,0.983 -8.368,0.583 -16.758,0.851 -25.144,0.865 -8.24,0 -16.485,-0.232 -24.707,-0.774 -3.751,-0.246 -7.493,-0.559 -11.226,-0.938 -1.698,-0.178 -3.392,-0.364 -5.085,-0.574l-1.612,-0.205 -0.35,-0.05 -1.671,-0.241c-3.414,-0.515 -6.829,-1.106 -10.207,-1.821 -0.341,-0.076 -0.646,-0.266 -0.865,-0.538 -0.219,-0.272 -0.337,-0.611 -0.337,-0.96 0,-0.349 0.119,-0.688 0.337,-0.96 0.219,-0.272 0.524,-0.462 0.865,-0.537l0.064,0c2.927,-0.624 5.877,-1.156 8.836,-1.621 0.987,-0.155 1.976,-0.307 2.968,-0.455l0.027,0c1.853,-0.123 3.715,-0.455 5.559,-0.674 16.042,-1.669 32.178,-2.238 48.298,-1.703 7.826,0.228 15.647,0.687 23.436,1.479 1.675,0.173 3.342,0.355 5.008,0.56 0.637,0.077 1.279,0.169 1.921,0.246l1.293,0.187c3.77,0.561 7.519,1.243 11.249,2.044 5.527,1.202 12.625,1.594 15.083,7.649 0.783,1.921 1.138,4.056 1.57,6.072l0.551,2.572c0.015,0.046 0.025,0.093 0.032,0.141 1.302,6.07 2.605,12.14 3.91,18.21 0.096,0.448 0.098,0.912 0.007,1.361 -0.091,0.45 -0.274,0.875 -0.537,1.251 -0.263,0.375 -0.601,0.693 -0.992,0.932 -0.391,0.239 -0.827,0.395 -1.282,0.458l-0.037,0l-0.797,0.109 -0.787,0.105c-2.495,0.325 -4.993,0.628 -7.494,0.911 -4.926,0.561 -9.86,1.047 -14.801,1.457 -9.818,0.816 -19.656,1.352 -29.514,1.607 -5.023,0.133 -10.044,0.196 -15.064,0.187 -19.981,-0.016 -39.945,-1.177 -59.793,-3.478 -2.149,-0.255 -4.298,-0.528 -6.447,-0.806 1.666,0.214 -1.211,-0.164 -1.793,-0.246 -1.366,-0.191 -2.732,-0.39 -4.098,-0.596 -4.584,-0.688 -9.142,-1.534 -13.717,-2.276 -5.531,-0.91 -10.821,-0.455 -15.824,2.276 -4.107,2.247 -7.431,5.694 -9.529,9.879 -2.158,4.461 -2.799,9.319 -3.765,14.113 -0.965,4.794 -2.468,9.952 -1.899,14.873 1.225,10.621 8.65,19.252 19.33,21.183 10.047,1.821 20.149,3.296 30.279,4.553 39.791,4.873 79.99,5.456 119.905,1.739 3.25,-0.303 6.496,-0.634 9.738,-0.992 1.013,-0.111 2.037,0.005 2.998,0.341 0.962,0.336 1.836,0.883 2.558,1.6 0.723,0.718 1.276,1.588 1.619,2.547 0.343,0.959 0.467,1.983 0.363,2.996l-1.011,9.824c-2.037,19.852 -4.073,39.703 -6.11,59.552 -2.124,20.845 -4.263,41.687 -6.415,62.527 -0.607,5.871 -1.214,11.74 -1.821,17.606 -0.583,5.778 -0.665,11.737 -1.762,17.446 -1.73,8.977 -7.807,14.491 -16.675,16.508 -8.125,1.849 -16.425,2.82 -24.757,2.894 -9.237,0.05 -18.469,-0.36 -27.706,-0.31 -9.861,0.056 -21.939,-0.854 -29.551,-8.194 -6.688,-6.445 -7.612,-16.539 -8.522,-25.268 -1.214,-11.554 -2.417,-23.107 -3.61,-34.658L52.284,188.281 47.954,146.722c-0.073,-0.687 -0.146,-1.366 -0.214,-2.058 -0.519,-4.957 -4.029,-9.81 -9.561,-9.56 -4.734,0.209 -10.116,4.234 -9.56,9.56l3.209,30.812 6.638,63.735c1.891,18.104 3.777,36.21 5.659,54.321 0.364,3.469 0.705,6.948 1.088,10.416 2.081,18.957 16.558,29.173 34.486,32.049 10.471,1.685 21.196,2.032 31.822,2.204 13.621,0.22 27.379,0.744 40.777,-1.725 19.854,-3.643 34.749,-16.899 36.875,-37.462 0.607,-5.937 1.214,-11.875 1.821,-17.814 2.018,-19.643 4.034,-39.288 6.046,-58.933l6.583,-64.19 3.018,-29.418c0.15,-1.459 0.766,-2.831 1.756,-3.912 0.99,-1.081 2.303,-1.816 3.742,-2.093 5.677,-1.106 11.104,-2.995 15.142,-7.316 6.428,-6.879 7.707,-15.847 5.435,-24.888l-1.862,-9.392C229.186,72.629 225.394,64.666 216.744,61.621 213.971,60.646 210.825,60.228 208.699,58.211c-2.126,-2.017 -2.754,-5.149 -3.246,-8.053 -0.911,-5.331 -1.766,-10.667 -2.7,-15.989 -0.806,-4.575 -1.443,-9.715 -3.542,-13.912 -2.732,-5.636 -8.4,-8.932 -14.036,-11.113 -2.888,-1.078 -5.835,-1.99 -8.827,-2.731 -14.081,-3.715 -28.886,-5.081 -43.373,-5.86 -6.52,-0.36 -13.047,-0.544 -19.573,-0.552zM29.029,78.272c6.185,0.029 12.996,2.347 18.907,3.243 8.134,1.241 16.298,2.235 24.492,2.982 34.973,3.187 70.533,2.691 105.351,-1.971 6.346,-0.853 12.67,-1.844 18.97,-2.973 5.613,-1.006 11.837,-2.895 15.229,2.918 2.326,3.961 2.635,9.26 2.276,13.735 -0.111,1.95 -0.963,3.783 -2.381,5.126l0.004,0c-2.039,1.939 -5.112,2.841 -8.149,3.292 -34.053,5.053 -68.602,7.612 -103.028,6.483 -24.638,-0.842 -49.017,-3.578 -73.409,-7.024 -2.39,-0.337 -4.981,-0.774 -6.624,-2.536C17.57,98.223 19.091,91.531 19.896,87.515 20.634,83.837 22.046,78.934 26.421,78.411c0.854,-0.1 1.725,-0.143 2.608,-0.138zM19.164,96.794c-0.127,0.059 -0.155,0.523 -0.141,1.047 0.068,-0.346 0.228,-1.088 0.141,-1.047zM19.736,101.053c0.059,0.016 0.179,0.18 0.302,0.371 -0.214,-0.201 -0.351,-0.351 -0.328,-0.369l0.005,0c0.006,-0.004 0.013,-0.004 0.021,-0.002zM21.34,102.652l0.005,0 0.027,0c0,0.032 0.05,0.064 0.068,0.096 -0.03,-0.035 -0.064,-0.068 -0.1,-0.096zM156.916,147.68c-11.645,-0.005 -21.028,4.006 -31.868,8.645 -12.155,5.204 -25.9Line truncated
|
|
||||||
android:fillColor="@color/themeLight"
|
|
||||||
tools:ignore="VectorPath" />
|
|
||||||
</vector>
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:autoMirrored="true"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:autoMirrored="true"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M18,7l-1.41,-1.41 -6.34,6.34 1.41,1.41L18,7zM22.24,5.59L11.66,16.17 7.48,12l-1.41,1.41L11.66,19l12,-12 -1.42,-1.41zM0.41,13.41L6,19l1.41,-1.41L1.83,12 0.41,13.41z" />
|
||||||
|
</vector>
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="48"
|
|
||||||
android:viewportHeight="48">
|
|
||||||
<path
|
|
||||||
android:fillColor="@color/themeLight"
|
|
||||||
android:pathData="M8,20.0449C7.7923,20.0449 7.5902,20.0656 7.3945,20.1055C6.8076,20.225 6.2836,20.5152 5.877,20.9219C5.4703,21.3286 5.18,21.8506 5.0605,22.4375C5.0207,22.6331 5,22.8372 5,23.0449L5,30.0449L5,42.2754L5,43.0449C5,43.6682 5.1889,44.246 5.5117,44.7246C6.0498,45.5224 6.9612,46.0449 8,46.0449L40,46.0449C41.0387,46.0449 41.9502,45.5224 42.4883,44.7246C42.5959,44.5651 42.6899,44.3944 42.7656,44.2148C42.917,43.8557 43,43.4604 43,43.0449L43,42.2754L43,36.0449L43,23.8125L43,23.0449C43,22.6294 42.917,22.2342 42.7656,21.875C42.6899,21.6954 42.5959,21.5248 42.4883,21.3652C41.9502,20.5675 41.0387,20.0449 40,20.0449L8,20.0449zM24,22.5254A10.5182,10.5182 0,0 1,34.5176 33.0449A10.5182,10.5182 0,0 1,24 43.5625A10.5182,10.5182 0,0 1,13.4824 33.0449A10.5182,10.5182 0,0 1,24 22.5254zM24,24.4492A8.5958,8.5958 0,0 0,15.4043 33.0449A8.5958,8.5958 0,0 0,24 41.6406A8.5958,8.5958 0,0 0,32.5957 33.0449A8.5958,8.5958 0,0 0,24 24.4492zM24,26.75C27.434,26.75 30.25,29.566 30.25,33C30.25,36.434 27.434,39.25 24,39.25C21.0479,39.25 18.5583,37.1661 17.916,34.4004L21.0703,34.4004C21.5888,35.4973 22.6926,36.25 24,36.25C25.8127,36.25 27.25,34.8127 27.25,33C27.25,31.1873 25.8127,29.75 24,29.75C22.7748,29.75 21.734,30.415 21.1797,31.4004L17.9668,31.4004C18.6802,28.7347 21.1193,26.75 24,26.75z"
|
|
||||||
android:strokeWidth="2"
|
|
||||||
android:strokeColor="#00000000"
|
|
||||||
android:strokeLineCap="round"
|
|
||||||
android:strokeLineJoin="miter"
|
|
||||||
tools:ignore="VectorPath" />
|
|
||||||
<path
|
|
||||||
android:fillColor="@color/themeLight"
|
|
||||||
android:pathData="M2.3379,1.5254C1.8997,1.5254 1.378,1.6672 1.5137,2.3242C1.6007,2.7458 4.4144,6.2239 5.4434,7.4902C5.2677,7.7794 5.1304,8.0942 5.0605,8.4375C5.0207,8.6331 5,8.8372 5,9.0449L5,9.582L5,12.0449L5,13.0449L5,15.5059L5,16.0449C5,16.6682 5.1889,17.246 5.5117,17.7246C6.0498,18.5224 6.9612,19.0449 8,19.0449L40,19.0449C41.0387,19.0449 41.9502,18.5224 42.4883,17.7246C42.5959,17.5651 42.6899,17.3944 42.7656,17.2148C42.8413,17.0353 42.8996,16.846 42.9395,16.6504C42.9793,16.4547 43,16.2527 43,16.0449L43,15.5059L43,13.0449L43,12.0449L43,9.582L43,9.0449C43,8.8372 42.9793,8.6331 42.9395,8.4375C42.8696,8.0942 42.7323,7.7794 42.5566,7.4902C43.5856,6.2239 46.3993,2.7458 46.4863,2.3242C46.622,1.6672 46.1003,1.5254 45.6621,1.5254C45.1945,1.5396 44.8462,1.9932 44.5859,2.2832L41.3984,6.4023C40.9796,6.1808 40.5086,6.0449 40,6.0449L8,6.0449C7.7923,6.0449 7.5902,6.0656 7.3945,6.1055C7.1989,6.1453 7.0097,6.2036 6.8301,6.2793C6.7496,6.3132 6.6782,6.3618 6.6016,6.4023L3.4141,2.2832C3.1538,1.9932 2.8055,1.5396 2.3379,1.5254zM14.375,9.75A3.375,3.375 0,0 1,17.75 13.125A3.375,3.375 0,0 1,14.375 16.5A3.375,3.375 0,0 1,11 13.125A3.375,3.375 0,0 1,14.375 9.75zM33.875,9.75A3.375,3.375 0,0 1,37.25 13.125A3.375,3.375 0,0 1,33.875 16.5A3.375,3.375 0,0 1,30.5 13.125A3.375,3.375 0,0 1,33.875 9.75z"
|
|
||||||
android:strokeWidth="2"
|
|
||||||
android:strokeColor="#00000000"
|
|
||||||
android:strokeLineCap="round"
|
|
||||||
android:strokeLineJoin="miter"
|
|
||||||
tools:ignore="VectorPath" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@color/themeLight"
|
|
||||||
android:pathData="M12,0.297c-6.63,0 -12,5.373 -12,12 0,5.303 3.438,9.8 8.205,11.385 0.6,0.113 0.82,-0.258 0.82,-0.577 0,-0.285 -0.01,-1.04 -0.015,-2.04 -3.338,0.724 -4.042,-1.61 -4.042,-1.61C4.422,18.07 3.633,17.7 3.633,17.7c-1.087,-0.744 0.084,-0.729 0.084,-0.729 1.205,0.084 1.838,1.236 1.838,1.236 1.07,1.835 2.809,1.305 3.495,0.998 0.108,-0.776 0.417,-1.305 0.76,-1.605 -2.665,-0.3 -5.466,-1.332 -5.466,-5.93 0,-1.31 0.465,-2.38 1.235,-3.22 -0.135,-0.303 -0.54,-1.523 0.105,-3.176 0,0 1.005,-0.322 3.3,1.23 0.96,-0.267 1.98,-0.399 3,-0.405 1.02,0.006 2.04,0.138 3,0.405 2.28,-1.552 3.285,-1.23 3.285,-1.23 0.645,1.653 0.24,2.873 0.12,3.176 0.765,0.84 1.23,1.91 1.23,3.22 0,4.61 -2.805,5.625 -5.475,5.92 0.42,0.36 0.81,1.096 0.81,2.22 0,1.606 -0.015,2.896 -0.015,3.286 0,0.315 0.21,0.69 0.825,0.57C20.565,22.092 24,17.592 24,12.297c0,-6.627 -5.373,-12 -12,-12"
|
|
||||||
tools:ignore="VectorPath" />
|
|
||||||
</vector>
|
|
||||||
+1
-3
@@ -1,6 +1,4 @@
|
|||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:shape="rectangle">
|
android:shape="rectangle">
|
||||||
<gradient
|
<solid android:color="@color/background" />
|
||||||
android:endColor="@color/greyDark"
|
|
||||||
android:startColor="@color/greyDark" />
|
|
||||||
</shape>
|
</shape>
|
||||||
+4
-5
@@ -1,7 +1,7 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:width="108dp"
|
android:width="108dp"
|
||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
|
android:autoMirrored="true"
|
||||||
android:viewportWidth="271.2439"
|
android:viewportWidth="271.2439"
|
||||||
android:viewportHeight="271.2439">
|
android:viewportHeight="271.2439">
|
||||||
<group
|
<group
|
||||||
@@ -11,12 +11,11 @@
|
|||||||
android:translateX="70.26573"
|
android:translateX="70.26573"
|
||||||
android:translateY="70.26573">
|
android:translateY="70.26573">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/themeLight"
|
android:fillColor="@color/accent"
|
||||||
android:pathData="m96.642,31.504c7.911,7.997 17.143,13.584 25.365,15.581l1.754,-3.997C123.78,43.048 123.786,43.005 123.802,42.965 125.834,38.995 127,34.495 127,29.715 127,13.881 114.379,1 98.867,1c-5.125,0 -9.92,1.429 -14.064,3.884l-3.398,1.598c0.08,0.306 0.133,0.599 0.221,0.91 2.32,8.104 7.652,16.667 15.016,24.112z" />
|
android:pathData="m96.642,31.504c7.911,7.997 17.143,13.584 25.365,15.581l1.754,-3.997C123.78,43.048 123.786,43.005 123.802,42.965 125.834,38.995 127,34.495 127,29.715 127,13.881 114.379,1 98.867,1c-5.125,0 -9.92,1.429 -14.064,3.884l-3.398,1.598c0.08,0.306 0.133,0.599 0.221,0.91 2.32,8.104 7.652,16.667 15.016,24.112z" />
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/themeLight"
|
android:fillColor="@color/accent"
|
||||||
android:pathData="M93.876,34.3C86.053,26.391 80.369,17.225 77.87,8.49 77.84,8.385 77.818,8.285 77.788,8.181L2.629,43.513c-1.404,0.66 -2.019,2.358 -1.372,3.791 0.472,1.046 1.485,1.663 2.545,1.663 0.392,0 0.79,-0.084 1.169,-0.263L74.045,16.235c-1.873,3.578 -2.997,7.611 -3.228,11.885L35.566,51.732c-1.294,0.866 -1.653,2.638 -0.805,3.957 0.538,0.836 1.431,1.29 2.344,1.29 0.526,0 1.06,-0.151 1.533,-0.469L71.189,34.707c1.4,8.11 6.135,15.044 12.715,19.285l-54.945,68.337c-0.981,1.22 -0.808,3.021 0.388,4.023 0.521,0.437 1.149,0.649 1.775,0.649 0.809,0 1.611,-0.357 2.166,-1.045L89.056,56.595c3.06,1.168 6.357,1.838 9.811,1.838 4.932,0 9.566,-1.312 13.601,-3.597L84.602,118.323c-0.633,1.44 0,3.131 1.41,3.777 0.372,0.17 0.76,0.25 1.144,0.25 1.07,0 2.092,-0.629 2.556,-1.691L120.402,50.739C111.642,48.416 102.042,42.555 93.876,34.3Z"
|
android:pathData="M93.876,34.3C86.053,26.391 80.369,17.225 77.87,8.49 77.84,8.385 77.818,8.285 77.788,8.181L2.629,43.513c-1.404,0.66 -2.019,2.358 -1.372,3.791 0.472,1.046 1.485,1.663 2.545,1.663 0.392,0 0.79,-0.084 1.169,-0.263L74.045,16.235c-1.873,3.578 -2.997,7.611 -3.228,11.885L35.566,51.732c-1.294,0.866 -1.653,2.638 -0.805,3.957 0.538,0.836 1.431,1.29 2.344,1.29 0.526,0 1.06,-0.151 1.533,-0.469L71.189,34.707c1.4,8.11 6.135,15.044 12.715,19.285l-54.945,68.337c-0.981,1.22 -0.808,3.021 0.388,4.023 0.521,0.437 1.149,0.649 1.775,0.649 0.809,0 1.611,-0.357 2.166,-1.045L89.056,56.595c3.06,1.168 6.357,1.838 9.811,1.838 4.932,0 9.566,-1.312 13.601,-3.597L84.602,118.323c-0.633,1.44 0,3.131 1.41,3.777 0.372,0.17 0.76,0.25 1.144,0.25 1.07,0 2.092,-0.629 2.556,-1.691L120.402,50.739C111.642,48.416 102.042,42.555 93.876,34.3Z" />
|
||||||
tools:ignore="VectorPath" />
|
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
</vector>
|
</vector>
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
|
android:autoMirrored="true"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/themeLight"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M2,21h19v-3H2v3zM20,8H3c-0.55,0 -1,0.45 -1,1v6c0,0.55 0.45,1 1,1h17c0.55,0 1,-0.45 1,-1V9c0,-0.55 -0.45,-1 -1,-1zM2,3v3h19V3H2z" />
|
android:pathData="M2,21h19v-3H2v3zM20,8H3c-0.55,0 -1,0.45 -1,1v6c0,0.55 0.45,1 1,1h17c0.55,0 1,-0.45 1,-1V9c0,-0.55 -0.45,-1 -1,-1zM2,3v3h19V3H2z" />
|
||||||
</vector>
|
</vector>
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
|
android:autoMirrored="true"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/themeLight"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11,19.93c-3.95,-0.49 -7,-3.85 -7,-7.93 0,-0.62 0.08,-1.21 0.21,-1.79L9,15v1c0,1.1 0.9,2 2,2v1.93zM17.9,17.39c-0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1L8,12v-2h2c0.55,0 1,-0.45 1,-1L11,7h2c1.1,0 2,-0.9 2,-2v-0.41c2.93,1.19 5,4.06 5,7.41 0,2.08 -0.8,3.97 -2.1,5.39z" />
|
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11,19.93c-3.95,-0.49 -7,-3.85 -7,-7.93 0,-0.62 0.08,-1.21 0.21,-1.79L9,15v1c0,1.1 0.9,2 2,2v1.93zM17.9,17.39c-0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1L8,12v-2h2c0.55,0 1,-0.45 1,-1L11,7h2c1.1,0 2,-0.9 2,-2v-0.41c2.93,1.19 5,4.06 5,7.41 0,2.08 -0.8,3.97 -2.1,5.39z" />
|
||||||
</vector>
|
</vector>
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@color/themeLight"
|
|
||||||
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
|
|
||||||
</vector>
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:autoMirrored="true"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:viewportWidth="128"
|
||||||
|
android:viewportHeight="128">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="m105.138,114.121a2.864,2.864 90,0 0,1.701 -0.85l19.334,-19.331a2.864,2.864 90,0 0,-0 -4.027l-26.584,-26.625a2.864,2.864 90,0 0,-1.835 -0.805l-7.922,-0.626 5.773,-5.772a2.864,2.864 90,0 0,-0 -4.027l-19.602,-19.644a2.864,2.864 90,0 0,-4.073 -0l-5.773,5.772 -0.627,-7.92a2.864,2.864 90,0 0,-0.805 -1.835l-26.629,-26.58a2.864,2.864 90,0 0,-4.073 -0l-19.289,19.286a2.864,2.864 90,0 0,-0 4.072l26.584,26.625a2.864,2.864 90,0 0,1.835 0.805l7.922,0.626 -5.773,5.772a2.864,2.864 90,0 0,-0 4.072l19.603,19.599a2.864,2.864 90,0 0,4.073 -0l5.773,-5.772 0.626,7.92a2.864,2.864 90,0 0,0.806 1.835l26.629,26.58a2.864,2.864 90,0 0,2.059 0.85,2.864 2.864,90 0,0 0.269,-0zM104.825,107.23 L80.97,83.38 80.165,72.685 85.58,67.271 96.276,68.121 120.13,91.926zM55.326,47.85 L44.629,47 20.775,23.194 36.081,7.891 59.891,31.741 60.741,42.436z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="m62.653,115.806a2.864,2.864 90,1 0,-0 -5.727c-11.457,-0 -22.876,-4.351 -31.634,-13.107 -8.741,-8.74 -13.095,-20.174 -13.095,-31.614a2.864,2.864 90,1 0,-5.728 -0c-0,12.897 4.935,25.829 14.773,35.664 9.85,9.848 22.772,14.785 35.684,14.785z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="m62.653,127a2.864,2.864 90,1 0,-0 -5.727c-14.325,-0 -28.612,-5.441 -39.547,-16.375 -10.935,-10.933 -16.378,-25.233 -16.378,-39.541a2.864,2.864 90,1 0,-5.728 -0c-0,15.763 6.025,31.562 18.055,43.59 12.029,12.028 27.818,18.052 43.597,18.052z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="m62.653,104.698a2.864,2.864 90,1 0,-0 -5.727c-8.62,-0 -17.201,-3.263 -23.778,-9.839 -6.577,-6.576 -9.855,-15.174 -9.855,-23.774a2.864,2.864 90,1 0,-5.728 -0c-0,10.061 3.861,20.153 11.533,27.824 7.672,7.671 17.756,11.517 27.829,11.517z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="m62.653,93.475a2.864,2.864 90,1 0,-0 -5.727c-5.737,-0 -11.453,-2.174 -15.836,-6.557 -4.384,-4.383 -6.558,-10.098 -6.558,-15.834a2.864,2.864 90,1 0,-5.728 -0c-0,7.192 2.757,14.406 8.236,19.883 5.479,5.478 12.693,8.234 19.887,8.234z" />
|
||||||
|
</vector>
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="128"
|
|
||||||
android:viewportHeight="128">
|
|
||||||
<path
|
|
||||||
android:fillColor="@color/themeLight"
|
|
||||||
android:pathData="m96.642,31.504c7.911,7.997 17.143,13.584 25.365,15.581l1.754,-3.997C123.78,43.048 123.786,43.005 123.802,42.965 125.834,38.995 127,34.495 127,29.715 127,13.881 114.379,1 98.867,1c-5.125,0 -9.92,1.429 -14.064,3.884l-3.398,1.598c0.08,0.306 0.133,0.599 0.221,0.91 2.32,8.104 7.652,16.667 15.016,24.112z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="@color/themeLight"
|
|
||||||
android:pathData="M93.876,34.3C86.053,26.391 80.369,17.225 77.87,8.49 77.84,8.385 77.818,8.285 77.788,8.181L2.629,43.513c-1.404,0.66 -2.019,2.358 -1.372,3.791 0.472,1.046 1.485,1.663 2.545,1.663 0.392,0 0.79,-0.084 1.169,-0.263L74.045,16.235c-1.873,3.578 -2.997,7.611 -3.228,11.885L35.566,51.732c-1.294,0.866 -1.653,2.638 -0.805,3.957 0.538,0.836 1.431,1.29 2.344,1.29 0.526,0 1.06,-0.151 1.533,-0.469L71.189,34.707c1.4,8.11 6.135,15.044 12.715,19.285l-54.945,68.337c-0.981,1.22 -0.808,3.021 0.388,4.023 0.521,0.437 1.149,0.649 1.775,0.649 0.809,0 1.611,-0.357 2.166,-1.045L89.056,56.595c3.06,1.168 6.357,1.838 9.811,1.838 4.932,0 9.566,-1.312 13.601,-3.597L84.602,118.323c-0.633,1.44 0,3.131 1.41,3.777 0.372,0.17 0.76,0.25 1.144,0.25 1.07,0 2.092,-0.629 2.556,-1.691L120.402,50.739C111.642,48.416 102.042,42.555 93.876,34.3Z"
|
|
||||||
tools:ignore="VectorPath" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@color/themeLight"
|
|
||||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@color/themeLight"
|
|
||||||
android:pathData="M19.1,12.9a2.8,2.8 0,0 0,0.1 -0.9,2.8 2.8,0 0,0 -0.1,-0.9l2.1,-1.6a0.7,0.7 0,0 0,0.1 -0.6L19.4,5.5a0.7,0.7 0,0 0,-0.6 -0.2l-2.4,1a6.5,6.5 0,0 0,-1.6 -0.9l-0.4,-2.6a0.5,0.5 0,0 0,-0.5 -0.4H10.1a0.5,0.5 0,0 0,-0.5 0.4L9.3,5.4a5.6,5.6 0,0 0,-1.7 0.9l-2.4,-1a0.4,0.4 0,0 0,-0.5 0.2l-2,3.4c-0.1,0.2 0,0.4 0.2,0.6l2,1.6a2.8,2.8 0,0 0,-0.1 0.9,2.8 2.8,0 0,0 0.1,0.9L2.8,14.5a0.7,0.7 0,0 0,-0.1 0.6l1.9,3.4a0.7,0.7 0,0 0,0.6 0.2l2.4,-1a6.5,6.5 0,0 0,1.6 0.9l0.4,2.6a0.5,0.5 0,0 0,0.5 0.4h3.8a0.5,0.5 0,0 0,0.5 -0.4l0.3,-2.6a5.6,5.6 0,0 0,1.7 -0.9l2.4,1a0.4,0.4 0,0 0,0.5 -0.2l2,-3.4c0.1,-0.2 0,-0.4 -0.2,-0.6ZM12,15.6A3.6,3.6 0,1 1,15.6 12,3.6 3.6,0 0,1 12,15.6Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
|
android:autoMirrored="true"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/themeLight"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z" />
|
android:pathData="M8,6v14l11,-7z" />
|
||||||
</vector>
|
</vector>
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@color/themeLight"
|
|
||||||
android:pathData="M21,19V20H3V19L5,17V11C5,7.9 7.03,5.17 10,4.29C10,4.19 10,4.1 10,4A2,2 0 0,1 12,2A2,2 0 0,1 14,4C14,4.1 14,4.19 14,4.29C16.97,5.17 19,7.9 19,11V17L21,19M14,21A2,2 0 0,1 12,23A2,2 0 0,1 10,21" />
|
|
||||||
</vector>
|
|
||||||
+2
-1
@@ -1,9 +1,10 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
|
android:autoMirrored="true"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/themeLight"
|
android:fillColor="@color/accent"
|
||||||
android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z" />
|
android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z" />
|
||||||
</vector>
|
</vector>
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
|
android:autoMirrored="true"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/themeLight"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
android:pathData="M16,6v14l-11,-7z" />
|
||||||
</vector>
|
</vector>
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
|
android:autoMirrored="true"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/themeLight"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M19,8l-4,4h3c0,3.31 -2.69,6 -6,6 -1.01,0 -1.97,-0.25 -2.8,-0.7l-1.46,1.46C8.97,19.54 10.43,20 12,20c4.42,0 8,-3.58 8,-8h3l-4,-4zM6,12c0,-3.31 2.69,-6 6,-6 1.01,0 1.97,0.25 2.8,0.7l1.46,-1.46C15.03,4.46 13.57,4 12,4c-4.42,0 -8,3.58 -8,8H1l4,4 4,-4H6z" />
|
android:pathData="M19,8l-4,4h3c0,3.31 -2.69,6 -6,6 -1.01,0 -1.97,-0.25 -2.8,-0.7l-1.46,1.46C8.97,19.54 10.43,20 12,20c4.42,0 8,-3.58 8,-8h3l-4,-4zM6,12c0,-3.31 2.69,-6 6,-6 1.01,0 1.97,0.25 2.8,0.7l1.46,-1.46C15.03,4.46 13.57,4 12,4c-4.42,0 -8,3.58 -8,8H1l4,4 4,-4H6z" />
|
||||||
</vector>
|
</vector>
|
||||||
@@ -1,23 +1,14 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
|
android:autoMirrored="true"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
android:viewportWidth="128"
|
android:viewportWidth="128"
|
||||||
android:viewportHeight="128">
|
android:viewportHeight="128">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/themeLight"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="m105.138,114.121a2.864,2.864 90,0 0,1.701 -0.85l19.334,-19.331a2.864,2.864 90,0 0,-0 -4.027l-26.584,-26.625a2.864,2.864 90,0 0,-1.835 -0.805l-7.922,-0.626 5.773,-5.772a2.864,2.864 90,0 0,-0 -4.027l-19.602,-19.644a2.864,2.864 90,0 0,-4.073 -0l-5.773,5.772 -0.627,-7.92a2.864,2.864 90,0 0,-0.805 -1.835l-26.629,-26.58a2.864,2.864 90,0 0,-4.073 -0l-19.289,19.286a2.864,2.864 90,0 0,-0 4.072l26.584,26.625a2.864,2.864 90,0 0,1.835 0.805l7.922,0.626 -5.773,5.772a2.864,2.864 90,0 0,-0 4.072l19.603,19.599a2.864,2.864 90,0 0,4.073 -0l5.773,-5.772 0.626,7.92a2.864,2.864 90,0 0,0.806 1.835l26.629,26.58a2.864,2.864 90,0 0,2.059 0.85,2.864 2.864,90 0,0 0.269,-0zM104.825,107.23 L80.97,83.38 80.165,72.685 85.58,67.271 96.276,68.121 120.13,91.926zM55.326,47.85 L44.629,47 20.775,23.194 36.081,7.891 59.891,31.741 60.741,42.436z"
|
android:pathData="m96.642,31.504c7.911,7.997 17.143,13.584 25.365,15.581l1.754,-3.997C123.78,43.048 123.786,43.005 123.802,42.965 125.834,38.995 127,34.495 127,29.715 127,13.881 114.379,1 98.867,1c-5.125,0 -9.92,1.429 -14.064,3.884l-3.398,1.598c0.08,0.306 0.133,0.599 0.221,0.91 2.32,8.104 7.652,16.667 15.016,24.112z" />
|
||||||
tools:ignore="VectorPath" />
|
|
||||||
<path
|
<path
|
||||||
android:fillColor="@color/themeLight"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="m62.653,115.806a2.864,2.864 90,1 0,-0 -5.727c-11.457,-0 -22.876,-4.351 -31.634,-13.107 -8.741,-8.74 -13.095,-20.174 -13.095,-31.614a2.864,2.864 90,1 0,-5.728 -0c-0,12.897 4.935,25.829 14.773,35.664 9.85,9.848 22.772,14.785 35.684,14.785z" />
|
android:pathData="M93.876,34.3C86.053,26.391 80.369,17.225 77.87,8.49 77.84,8.385 77.818,8.285 77.788,8.181L2.629,43.513c-1.404,0.66 -2.019,2.358 -1.372,3.791 0.472,1.046 1.485,1.663 2.545,1.663 0.392,0 0.79,-0.084 1.169,-0.263L74.045,16.235c-1.873,3.578 -2.997,7.611 -3.228,11.885L35.566,51.732c-1.294,0.866 -1.653,2.638 -0.805,3.957 0.538,0.836 1.431,1.29 2.344,1.29 0.526,0 1.06,-0.151 1.533,-0.469L71.189,34.707c1.4,8.11 6.135,15.044 12.715,19.285l-54.945,68.337c-0.981,1.22 -0.808,3.021 0.388,4.023 0.521,0.437 1.149,0.649 1.775,0.649 0.809,0 1.611,-0.357 2.166,-1.045L89.056,56.595c3.06,1.168 6.357,1.838 9.811,1.838 4.932,0 9.566,-1.312 13.601,-3.597L84.602,118.323c-0.633,1.44 0,3.131 1.41,3.777 0.372,0.17 0.76,0.25 1.144,0.25 1.07,0 2.092,-0.629 2.556,-1.691L120.402,50.739C111.642,48.416 102.042,42.555 93.876,34.3Z" />
|
||||||
<path
|
|
||||||
android:fillColor="@color/themeLight"
|
|
||||||
android:pathData="m62.653,127a2.864,2.864 90,1 0,-0 -5.727c-14.325,-0 -28.612,-5.441 -39.547,-16.375 -10.935,-10.933 -16.378,-25.233 -16.378,-39.541a2.864,2.864 90,1 0,-5.728 -0c-0,15.763 6.025,31.562 18.055,43.59 12.029,12.028 27.818,18.052 43.597,18.052z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="@color/themeLight"
|
|
||||||
android:pathData="m62.653,104.698a2.864,2.864 90,1 0,-0 -5.727c-8.62,-0 -17.201,-3.263 -23.778,-9.839 -6.577,-6.576 -9.855,-15.174 -9.855,-23.774a2.864,2.864 90,1 0,-5.728 -0c-0,10.061 3.861,20.153 11.533,27.824 7.672,7.671 17.756,11.517 27.829,11.517z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="@color/themeLight"
|
|
||||||
android:pathData="m62.653,93.475a2.864,2.864 90,1 0,-0 -5.727c-5.737,-0 -11.453,-2.174 -15.836,-6.557 -4.384,-4.383 -6.558,-10.098 -6.558,-15.834a2.864,2.864 90,1 0,-5.728 -0c-0,7.192 2.757,14.406 8.236,19.883 5.479,5.478 12.693,8.234 19.887,8.234z" />
|
|
||||||
</vector>
|
</vector>
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@color/themeLight"
|
|
||||||
android:pathData="M19,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.11,0 2,-0.9 2,-2L21,5c0,-1.1 -0.89,-2 -2,-2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z" />
|
|
||||||
</vector>
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="192dp"
|
||||||
|
android:height="200dp"
|
||||||
|
android:viewportWidth="210"
|
||||||
|
android:viewportHeight="220">
|
||||||
|
<group
|
||||||
|
android:translateX="52"
|
||||||
|
android:translateY="44">
|
||||||
|
<path
|
||||||
|
android:fillColor="@color/accent"
|
||||||
|
android:pathData="m104.005,111.936q0.504,0 0.958,-0.063 0.467,-0.076 0.87,-0.176l0,1.674q-0.202,0.101 -0.555,0.214 -0.34,0.113 -0.807,0.201 -0.454,0.101 -1.034,0.164 -0.58,0.063 -1.261,0.063 -0.87,0 -1.576,-0.214 -0.706,-0.201 -1.198,-0.68 -0.492,-0.491 -0.757,-1.259 -0.265,-0.78 -0.265,-1.913l0,-8.244l-1.942,0l0,-1.322q0.832,0 1.488,-0.327 0.656,-0.327 1.047,-0.755 0.794,-0.831 1.185,-2.555l2.144,0l0,3.197l3.304,0l0,1.762l-3.304,0l0,7.98q0,1.171 0.391,1.712 0.404,0.541 1.311,0.541zM85.595,109.922q0,1.108 0.353,1.661 0.353,0.554 1.11,0.554 0.555,0 0.996,-0.227 0.454,-0.239 0.769,-0.667 0.328,-0.428 0.492,-1.032 0.177,-0.617 0.177,-1.372l0,-1.938l-1.16,0.076q-0.769,0.038 -1.299,0.252 -0.53,0.201 -0.845,0.566 -0.315,0.365 -0.454,0.894 -0.139,0.529 -0.139,1.233zM87.89,101.188q-0.492,0 -0.832,0.214 -0.328,0.201 -0.53,0.579 -0.202,0.365 -0.29,0.881 -0.076,0.503 -0.076,1.108 -1.74,0 -2.623,-0.39 -0.87,-0.39 -0.87,-1.334 0,-0.705 0.416,-1.208 0.429,-0.503 1.147,-0.818 0.731,-0.315 1.69,-0.453 0.971,-0.151 2.068,-0.151 1.362,0 2.371,0.239 1.021,0.227 1.69,0.743 0.681,0.516 1.021,1.359 0.34,0.831 0.34,2.039l0,6.57q0,0.529 0.076,0.881 0.088,0.352 0.265,0.566 0.177,0.201 0.479,0.289 0.303,0.088 0.731,0.088l0.101,0l0,1.372l-4.804,0l-0.542,-1.775l-0.227,0q-0.429,0.516 -0.807,0.906 -0.378,0.378 -0.807,0.629 -0.429,0.252 -0.971,0.365 -0.53,0.126 -1.261,0.126 -0.857,0 -1.601,-0.252 -0.731,-0.252 -1.286,-0.768 -0.542,-0.529 -0.857,-1.309 -0.315,-0.793 -0.315,-1.863 0,-2.102 1.438,-3.109 1.45,-1.007 4.35,-1.095l2.118,-0.076l0,-1.422q0,-0.642 -0.05,-1.183 -0.05,-0.541 -0.214,-0.931 -0.164,-0.39 -0.492,-0.604 -0.315,-0.214 -0.845,-0.214zM72.115,114.013q-1.74,0 -2.9,-0.327 -1.16,-0.327 -1.866,-0.843 -0.694,-0.516 -0.996,-1.158 -0.29,-0.642 -0.29,-1.271 0,-0.667 0.24,-1.12 0.252,-0.453 0.656,-0.743 0.416,-0.289 0.933,-0.415 0.53,-0.126 1.097,-0.126 0,1.108 0.265,1.951 0.265,0.831 0.731,1.397 0.467,0.554 1.11,0.843 0.656,0.277 1.425,0.277 0.757,0 1.349,-0.214 0.593,-0.214 1.009,-0.592 0.416,-0.378 0.63,-0.881 0.214,-0.516 0.214,-1.108 0,-0.654 -0.265,-1.158 -0.265,-0.516 -0.807,-0.944 -0.53,-0.441 -1.349,-0.831 -0.807,-0.403 -1.891,-0.856 -1.349,-0.554 -2.282,-1.133 -0.921,-0.592 -1.488,-1.271 -0.567,-0.68 -0.82,-1.473 -0.24,-0.793 -0.24,-1.737 0,-1.183 0.479,-2.14 0.479,-0.957 1.324,-1.636 0.845,-0.68 2.005,-1.045 1.16,-0.365 2.522,-0.365 1.425,0 2.459,0.239 1.034,0.227 1.702,0.629 0.681,0.403 1.009,0.957 0.328,0.541 0.328,1.171 0,0.453 -0.202,0.843 -0.189,0.39 -0.593,0.68 -0.391,0.289 -0.996,0.453 -0.593,0.164 -1.387,0.164 0,-0.541 -0.151,-1.158 -0.139,-0.629 -0.467,-1.158 -0.328,-0.541 -0.857,-0.894 -0.517,-0.352 -1.261,-0.352 -0.517,0 -0.984,0.151 -0.467,0.151 -0.82,0.453 -0.34,0.302 -0.542,0.755 -0.202,0.441 -0.202,1.019 0,0.541 0.177,1.019 0.177,0.478 0.643,0.944 0.479,0.453 1.299,0.919 0.832,0.453 2.144,0.982 1.324,0.541 2.257,1.108 0.933,0.566 1.526,1.221 0.605,0.642 0.87,1.422 0.277,0.768 0.277,1.712 0,1.246 -0.492,2.291 -0.492,1.032 -1.412,1.775 -0.921,0.743 -2.219,1.158 -1.286,0.415 -2.9,0.415zM62.115,108.966l0,1.573q0,0.604 0.164,0.969 0.177,0.365 0.454,0.566 0.29,0.189 0.681,0.252 0.391,0.063 0.82,0.063l0.328,0l0,1.372l-9.054,0l0,-1.372l0.719,0q0.441,0 0.82,-0.063 0.391,-0.063 0.668,-0.252 0.29,-0.201 0.454,-0.566 0.177,-0.365 0.177,-0.969l0,-1.573l-7.15,0l0,-1.573l7.326,-12.032l3.594,0l0,11.906l2.686,0l0,1.699zM58.345,102.761q0,-1.133 0.038,-2.442 0.038,-1.309 0.139,-2.517 -0.126,0.302 -0.404,0.831 -0.277,0.529 -0.593,1.095 -0.315,0.554 -0.605,1.057 -0.29,0.491 -0.441,0.73l-3.48,5.752l5.347,0zM50.855,113.761l-0.366,0q-1.412,0 -2.371,-0.063 -0.958,-0.063 -1.652,-0.289 -0.694,-0.227 -1.198,-0.68 -0.504,-0.466 -1.009,-1.259l-2.295,-3.612 -1.034,0.768l0,2.089q0,0.554 0.177,0.881 0.189,0.327 0.454,0.503 0.277,0.176 0.605,0.239 0.328,0.05 0.605,0.05l0.303,0l0,1.372L34.866,113.761l0,-1.372l0.303,0q0.29,0 0.605,-0.05 0.328,-0.063 0.593,-0.239 0.277,-0.176 0.454,-0.503 0.189,-0.327 0.189,-0.881l0,-13.606q0,-0.516 -0.189,-0.818 -0.177,-0.315 -0.454,-0.478 -0.277,-0.176 -0.605,-0.227 -0.315,-0.05 -0.593,-0.05l-0.303,0l0,-1.359l6.065,0l0,9.943q0,0.352 -0.025,0.818 -0.013,0.453 -0.038,0.868 -0.038,0.491 -0.063,0.994l3.253,-3.637q0.429,-0.478 0.555,-0.755 0.139,-0.289 0.139,-0.478 0,-0.327 -0.353,-0.503 -0.34,-0.189 -0.984,-0.189l0,-1.296l6.507,0l0,1.296q-0.895,0 -1.765,0.529 -0.857,0.516 -1.879,1.661l-1.627,1.85 3.43,5.11q0.681,1.019 1.374,1.51 0.694,0.491 1.311,0.491l0.088,0zM33.517,106.31q0,1.007 -0.29,1.901 -0.29,0.894 -0.82,1.573 -0.517,0.667 -1.261,1.057 -0.731,0.39 -1.627,0.39 -1.198,0 -2.333,-0.73 -1.122,-0.743 -2.055,-2.417 -0.794,1.523 -1.828,2.303 -1.034,0.768 -2.181,0.768 -0.883,0 -1.627,-0.34 -0.744,-0.352 -1.286,-0.982 -0.542,-0.642 -0.845,-1.548 -0.29,-0.919 -0.29,-2.052 0,-1.032 0.29,-1.926 0.29,-0.906 0.82,-1.561 0.53,-0.667 1.274,-1.045 0.744,-0.39 1.664,-0.39 0.593,0 1.173,0.176 0.593,0.164 1.147,0.541 0.567,0.365 1.072,0.944 0.517,0.579 0.946,1.41 0.794,-1.498 1.791,-2.24 0.996,-0.755 2.295,-0.755Line truncated
|
||||||
|
</group>
|
||||||
|
</vector>
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:width="192dp"
|
|
||||||
android:height="200dp"
|
|
||||||
android:viewportWidth="105.83323"
|
|
||||||
android:viewportHeight="114.01299">
|
|
||||||
<path
|
|
||||||
android:fillColor="@color/themeLight"
|
|
||||||
android:pathData="m104.005,111.936q0.504,0 0.958,-0.063 0.467,-0.076 0.87,-0.176l0,1.674q-0.202,0.101 -0.555,0.214 -0.34,0.113 -0.807,0.201 -0.454,0.101 -1.034,0.164 -0.58,0.063 -1.261,0.063 -0.87,0 -1.576,-0.214 -0.706,-0.201 -1.198,-0.68 -0.492,-0.491 -0.757,-1.259 -0.265,-0.78 -0.265,-1.913l0,-8.244l-1.942,0l0,-1.322q0.832,0 1.488,-0.327 0.656,-0.327 1.047,-0.755 0.794,-0.831 1.185,-2.555l2.144,0l0,3.197l3.304,0l0,1.762l-3.304,0l0,7.98q0,1.171 0.391,1.712 0.404,0.541 1.311,0.541zM85.595,109.922q0,1.108 0.353,1.661 0.353,0.554 1.11,0.554 0.555,0 0.996,-0.227 0.454,-0.239 0.769,-0.667 0.328,-0.428 0.492,-1.032 0.177,-0.617 0.177,-1.372l0,-1.938l-1.16,0.076q-0.769,0.038 -1.299,0.252 -0.53,0.201 -0.845,0.566 -0.315,0.365 -0.454,0.894 -0.139,0.529 -0.139,1.233zM87.89,101.188q-0.492,0 -0.832,0.214 -0.328,0.201 -0.53,0.579 -0.202,0.365 -0.29,0.881 -0.076,0.503 -0.076,1.108 -1.74,0 -2.623,-0.39 -0.87,-0.39 -0.87,-1.334 0,-0.705 0.416,-1.208 0.429,-0.503 1.147,-0.818 0.731,-0.315 1.69,-0.453 0.971,-0.151 2.068,-0.151 1.362,0 2.371,0.239 1.021,0.227 1.69,0.743 0.681,0.516 1.021,1.359 0.34,0.831 0.34,2.039l0,6.57q0,0.529 0.076,0.881 0.088,0.352 0.265,0.566 0.177,0.201 0.479,0.289 0.303,0.088 0.731,0.088l0.101,0l0,1.372l-4.804,0l-0.542,-1.775l-0.227,0q-0.429,0.516 -0.807,0.906 -0.378,0.378 -0.807,0.629 -0.429,0.252 -0.971,0.365 -0.53,0.126 -1.261,0.126 -0.857,0 -1.601,-0.252 -0.731,-0.252 -1.286,-0.768 -0.542,-0.529 -0.857,-1.309 -0.315,-0.793 -0.315,-1.863 0,-2.102 1.438,-3.109 1.45,-1.007 4.35,-1.095l2.118,-0.076l0,-1.422q0,-0.642 -0.05,-1.183 -0.05,-0.541 -0.214,-0.931 -0.164,-0.39 -0.492,-0.604 -0.315,-0.214 -0.845,-0.214zM72.115,114.013q-1.74,0 -2.9,-0.327 -1.16,-0.327 -1.866,-0.843 -0.694,-0.516 -0.996,-1.158 -0.29,-0.642 -0.29,-1.271 0,-0.667 0.24,-1.12 0.252,-0.453 0.656,-0.743 0.416,-0.289 0.933,-0.415 0.53,-0.126 1.097,-0.126 0,1.108 0.265,1.951 0.265,0.831 0.731,1.397 0.467,0.554 1.11,0.843 0.656,0.277 1.425,0.277 0.757,0 1.349,-0.214 0.593,-0.214 1.009,-0.592 0.416,-0.378 0.63,-0.881 0.214,-0.516 0.214,-1.108 0,-0.654 -0.265,-1.158 -0.265,-0.516 -0.807,-0.944 -0.53,-0.441 -1.349,-0.831 -0.807,-0.403 -1.891,-0.856 -1.349,-0.554 -2.282,-1.133 -0.921,-0.592 -1.488,-1.271 -0.567,-0.68 -0.82,-1.473 -0.24,-0.793 -0.24,-1.737 0,-1.183 0.479,-2.14 0.479,-0.957 1.324,-1.636 0.845,-0.68 2.005,-1.045 1.16,-0.365 2.522,-0.365 1.425,0 2.459,0.239 1.034,0.227 1.702,0.629 0.681,0.403 1.009,0.957 0.328,0.541 0.328,1.171 0,0.453 -0.202,0.843 -0.189,0.39 -0.593,0.68 -0.391,0.289 -0.996,0.453 -0.593,0.164 -1.387,0.164 0,-0.541 -0.151,-1.158 -0.139,-0.629 -0.467,-1.158 -0.328,-0.541 -0.857,-0.894 -0.517,-0.352 -1.261,-0.352 -0.517,0 -0.984,0.151 -0.467,0.151 -0.82,0.453 -0.34,0.302 -0.542,0.755 -0.202,0.441 -0.202,1.019 0,0.541 0.177,1.019 0.177,0.478 0.643,0.944 0.479,0.453 1.299,0.919 0.832,0.453 2.144,0.982 1.324,0.541 2.257,1.108 0.933,0.566 1.526,1.221 0.605,0.642 0.87,1.422 0.277,0.768 0.277,1.712 0,1.246 -0.492,2.291 -0.492,1.032 -1.412,1.775 -0.921,0.743 -2.219,1.158 -1.286,0.415 -2.9,0.415zM62.115,108.966l0,1.573q0,0.604 0.164,0.969 0.177,0.365 0.454,0.566 0.29,0.189 0.681,0.252 0.391,0.063 0.82,0.063l0.328,0l0,1.372l-9.054,0l0,-1.372l0.719,0q0.441,0 0.82,-0.063 0.391,-0.063 0.668,-0.252 0.29,-0.201 0.454,-0.566 0.177,-0.365 0.177,-0.969l0,-1.573l-7.15,0l0,-1.573l7.326,-12.032l3.594,0l0,11.906l2.686,0l0,1.699zM58.345,102.761q0,-1.133 0.038,-2.442 0.038,-1.309 0.139,-2.517 -0.126,0.302 -0.404,0.831 -0.277,0.529 -0.593,1.095 -0.315,0.554 -0.605,1.057 -0.29,0.491 -0.441,0.73l-3.48,5.752l5.347,0zM50.855,113.761l-0.366,0q-1.412,0 -2.371,-0.063 -0.958,-0.063 -1.652,-0.289 -0.694,-0.227 -1.198,-0.68 -0.504,-0.466 -1.009,-1.259l-2.295,-3.612 -1.034,0.768l0,2.089q0,0.554 0.177,0.881 0.189,0.327 0.454,0.503 0.277,0.176 0.605,0.239 0.328,0.05 0.605,0.05l0.303,0l0,1.372L34.866,113.761l0,-1.372l0.303,0q0.29,0 0.605,-0.05 0.328,-0.063 0.593,-0.239 0.277,-0.176 0.454,-0.503 0.189,-0.327 0.189,-0.881l0,-13.606q0,-0.516 -0.189,-0.818 -0.177,-0.315 -0.454,-0.478 -0.277,-0.176 -0.605,-0.227 -0.315,-0.05 -0.593,-0.05l-0.303,0l0,-1.359l6.065,0l0,9.943q0,0.352 -0.025,0.818 -0.013,0.453 -0.038,0.868 -0.038,0.491 -0.063,0.994l3.253,-3.637q0.429,-0.478 0.555,-0.755 0.139,-0.289 0.139,-0.478 0,-0.327 -0.353,-0.503 -0.34,-0.189 -0.984,-0.189l0,-1.296l6.507,0l0,1.296q-0.895,0 -1.765,0.529 -0.857,0.516 -1.879,1.661l-1.627,1.85 3.43,5.11q0.681,1.019 1.374,1.51 0.694,0.491 1.311,0.491l0.088,0zM33.517,106.31q0,1.007 -0.29,1.901 -0.29,0.894 -0.82,1.573 -0.517,0.667 -1.261,1.057 -0.731,0.39 -1.627,0.39 -1.198,0 -2.333,-0.73 -1.122,-0.743 -2.055,-2.417 -0.794,1.523 -1.828,2.303 -1.034,0.768 -2.181,0.768 -0.883,0 -1.627,-0.34 -0.744,-0.352 -1.286,-0.982 -0.542,-0.642 -0.845,-1.548 -0.29,-0.919 -0.29,-2.052 0,-1.032 0.29,-1.926 0.29,-0.906 0.82,-1.561 0.53,-0.667 1.274,-1.045 0.744,-0.39 1.664,-0.39 0.593,0 1.173,0.176 0.593,0.164 1.147,0.541 0.567,0.365 1.072,0.944 0.517,0.579 0.946,1.41 0.794,-1.498 1.791,-2.24 0.996,-0.755 2.295,-0.755 0.8Line truncated
|
|
||||||
tools:ignore="VectorPath" />
|
|
||||||
</vector>
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:autoMirrored="true"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M14,21c1.93,0 3.62,-1.17 4,-3l-1.75,-0.88C16,18.21 15.33,19 14,19l-4.9,0c0.83,-1 1.5,-2.34 1.5,-4c0,-0.35 -0.03,-0.69 -0.08,-1L14,14v-2l-4.18,0C9,10.42 8,9.6 8,8c0,-1.93 1.57,-3.5 3.5,-3.5c1.5,0 2.79,0.95 3.28,2.28L16.63,6c-0.8,-2.05 -2.79,-3.5 -5.13,-3.5C8.46,2.5 6,4.96 6,8c0,1.78 0.79,2.9 1.49,4L6,12v2l2.47,0c0.08,0.31 0.13,0.64 0.13,1c0,2.7 -2.6,4 -2.6,4v2H14z" />
|
||||||
|
</vector>
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@color/themeLight"
|
|
||||||
android:pathData="M6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6L6,2zM13,9L13,3.5L18.5,9L13,9z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@color/themeLight"
|
|
||||||
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM17,13l-5,5 -5,-5h3V9h4v4h3z" />
|
|
||||||
</vector>
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<size
|
||||||
|
android:width="1dp"
|
||||||
|
android:height="1dp" />
|
||||||
|
<solid android:color="@color/background" />
|
||||||
|
</shape>
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<stroke
|
|
||||||
android:width="0dp"
|
|
||||||
android:color="@color/mapInfoLayout" />
|
|
||||||
<solid android:color="@color/mapInfoLayout" />
|
|
||||||
<corners android:radius="8dp" />
|
|
||||||
</shape>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item>
|
|
||||||
<shape android:shape="rectangle">
|
|
||||||
<size
|
|
||||||
android:width="1dp"
|
|
||||||
android:height="2dp" />
|
|
||||||
<solid android:color="@color/greyDark" />
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
</layer-list>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item>
|
|
||||||
<shape android:shape="rectangle">
|
|
||||||
<size
|
|
||||||
android:width="1dp"
|
|
||||||
android:height="0.5dp" />
|
|
||||||
<solid android:color="@color/greyDivider" />
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
</layer-list>
|
|
||||||
Loaded 100 of 191 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user