Compare commits

...
287 Commits
Author SHA1 Message Date
Arty Bishop 99158e74fa v4.2.1 - Turkish translation, DeepSpace filter, various tweaks 2026-04-19 13:14:31 +01:00
Arty Bishop 8265b73d75 Added DeepSpace passes filter, fixed refresh issue (#208) 2026-04-18 15:56:28 +01:00
Emre Can Akdaş d2b3184084 Added Turkish translation, by Emre Can Akdaş (TA3ECR) (#211) 2026-04-18 10:38:42 +01:00
Arty Bishop 0c5d3699c0 v4.2.0 - CAT control, HamLib commands, performance tweaks 2026-04-11 16:48:24 +01:00
Arty Bishop a94f95874b Added several tweaks to Settings and RadioControl screens 2026-04-10 14:13:53 +01:00
Arty Bishop 4a3a01729b Added several tweaks to Radar and RadioControl screens 2026-04-10 12:21:03 +01:00
Arty Bishop aadc285d7f Added multiple tweaks to Satellites, Passes and Map screens 2026-04-10 11:50:32 +01:00
jcmerg 2df0b9ba3d Add CAT radio control for full-duplex satellite operation (#207) 2026-04-10 11:21:52 +01:00
Arty Bishop 868155c532 Simplified data output code, aligned with HamLib defaults 2026-04-06 12:48:06 +01:00
Arty Bishop 3f981c09d5 Tweaked output dialogs ui, fixed manual import category 2026-04-06 11:57:12 +01:00
Arty Bishop b79fec13f4 Removed the eclipsed badge, tweaked visual indication 2026-04-05 11:45:07 +01:00
Arty Bishop 6588291997 Fixed UTC pass time problem mentioned in issue #201 2026-04-05 11:27:27 +01:00
Arty Bishop be96602c76 Added normalized time for AOS/LOS, fixed passes refresh 2026-04-04 18:43:05 +01:00
Arty Bishop b59f9a6c17 Simplified SettingsRepo.kt and SettingsScreen.kt classes 2026-04-04 15:44:51 +01:00
Arty Bishop 9afe2ab69e Heavily optimized MapScreen.kt and position calculations 2026-04-03 16:45:02 +01:00
theojalba 2edc892dc2 Switch to short-form commands in NetworkReporter (#206) 2026-04-03 16:42:18 +01:00
Arty Bishop 45eb6915c2 Optimized common composables and satellite selection 2026-04-02 08:28:37 +01:00
Arty Bishop 706c912b13 Simplified BluetoothReporter and NetworkReporter 2026-03-30 22:02:49 +01:00
Arty Bishop e523eb5517 Added tweaks to DataParser.kt and DatabaseRepo.kt 2026-03-29 13:37:04 +01:00
Arty Bishop 0bef2ffddc Added tweaks to positions and passes calculation 2026-03-29 13:09:03 +01:00
Arty Bishop 279594c425 Simplified RadarScreen/View, added sensors smoothing 2026-03-28 20:04:03 +00:00
Arty Bishop 8c1df334d8 Added even more tweaks to convention plugins setup 2026-03-28 20:03:11 +00:00
Arty Bishop e2bf2a09a4 v4.1.1 - Fixes for landscape/split screen issues, by londre7 2026-03-22 11:07:23 +00:00
londre7 1d48ce144f Use the compass in landscape 2026-03-21 12:29:46 +00:00
Arty Bishop 082b52c58b Added more tweaks to convention plugins setup 2026-03-18 15:59:55 +00:00
londre7 c1966442a7 Fixed track path creation issue for split screens (#199) 2026-03-09 13:50:00 +00:00
Arty Bishop 4d88214c1d Added basic convention plugins setup for modularization 2026-03-01 20:02:30 +00:00
Arty Bishop de99e09f5e v4.1.0 - Remote freq control for Hamlib/SDR++, by londre7 2026-02-22 13:51:58 +00:00
Arty Bishop e717d75324 Added a few visual fixes to extended data output feature 2026-02-21 12:31:59 +00:00
wty2019wty a545863473 Updated the Chinese translation for freq output (#196) 2026-02-21 09:35:22 +00:00
londre7 85ffe94266 Implemented extended data output (#195) 2026-02-20 18:42:55 +00:00
Arty Bishop 7adbf951bc v4.0.4 - R4UAB source priority, Chinese translation fixes 2026-02-14 14:12:36 +00:00
Mubi-Baihua 78dc217642 Revise the Chinese translation. (#194) 2026-02-14 12:22:25 +00:00
Arty Bishop 64137473b3 v4.0.3 - KeepScreenOn for RadarScreen, translation fixes 2026-02-10 15:25:28 +00:00
Kaitlyn Kenwell c08effb445 Add setting to keep the screen on in radar view (#193) 2026-02-09 19:31:55 +00:00
troilus 65410defe2 Translate custom data source strings to Chinese 2026-02-08 11:09:03 +00:00
Arty Bishop ed71c8148b v4.0.2 - Custom data import by londre7, minor UI tweaks 2026-02-07 17:22:42 +00:00
Arty Bishop 13a79af27e Added minor tweaks to import components and strings 2026-02-07 13:41:12 +00:00
londre7 f6564c3c71 Added custom data sources import, by londre7 (#189) 2026-02-07 13:03:12 +00:00
Arty Bishop 8e42f81ea3 Added tweaks to components, updated dependencies 2026-02-07 12:30:46 +00:00
Eugene ada5b48ba5 minor updates for uk localization 2026-02-02 20:28:48 +00:00
Arty Bishop a6a59877a8 v4.0.1 - Hotfix for RadarScreen track projection display 2026-01-31 13:55:44 +00:00
Arty Bishop 6aa455490b v4.0.0 - Major release with Jetpack Compose and API 36 2026-01-30 18:19:19 +00:00
Arty Bishop f81738bc8b Added even more tweaks to translations and strings.xml 2026-01-30 15:40:02 +00:00
Arty Bishop a57effd073 Added even more tweaks to resources and app icons 2026-01-28 12:36:24 +00:00
Arty Bishop c180f6e58e Added more tweaks to translations and strings.xml 2026-01-27 18:12:20 +00:00
Arty Bishop 1290b8f32f Added various tweaks to translations, sorted strings.xml 2026-01-26 18:04:02 +00:00
Arty Bishop 9e765a3575 Added various tweaks to dependencies and translations 2026-01-16 14:59:21 +00:00
Arty Bishop 65a0c33007 Migrated AGP to v9.0.0, tweaked gradle.properties 2026-01-16 13:54:13 +00:00
Arty Bishop 7add81e8b6 Added tweaks to GH actions workflow, refactored strings 2026-01-11 17:33:16 +00:00
Arty Bishop 1a69822d3f Added more tweaks to the Transceiver card component 2025-12-27 15:02:15 +00:00
Arty Bishop c370d9febd Added more tweaks to components and dependencies 2025-12-27 15:02:15 +00:00
CertainBot 24002827ed Created Spanish tranlation
Thanks for this great app!

A Spanish translation is missing, so I have added the spanish `.../values-es-rES/strings.xml`. Hope this will give access to many new users.

Pulled against development branch for you to use it as you wish.

Regards
2025-12-27 15:02:15 +00:00
Arty Bishop a835ac4e07 Added remote data output sections to SettingsScreen.kt 2025-12-27 15:02:15 +00:00
Arty Bishop a68e466040 Restructured SettingsScreen layout, tweaked components 2025-12-27 15:02:15 +00:00
Arty Bishop bfbe437ab1 Cleaned up classes and resources, updated dependencies 2025-12-27 15:02:15 +00:00
Arty Bishop 574ffe09c2 Switched back to bottom navigation, tweaked components 2025-12-27 15:02:15 +00:00
WaterBlock623 529a68f291 Update strings.xml
Add some new text in Chinese
2025-12-27 15:02:15 +00:00
Arty Bishop 266fbfed1f Added more tweaks to components and dependencies 2025-12-27 15:02:14 +00:00
Arty Bishop fb8053ef60 Added animated visibility to the FloatingBar component 2025-12-27 15:02:14 +00:00
Eugene 34e66f9ae0 Update Ukrainian translation 2025-12-27 15:02:12 +00:00
Arty Bishop 10fc88364f Integrated fixes from release v3.2.1, updated dependencies 2025-12-27 15:01:47 +00:00
Mubi-Baihua 2e5a15eec7 Update strings.xml 2025-12-27 15:01:47 +00:00
Arty Bishop 42aa4deb0d Tried using floating app bar layout for screen navigation 2025-12-27 15:01:47 +00:00
Arty Bishop 82f41940c7 Added a few minor tweaks to resources and screens 2025-12-27 15:01:47 +00:00
Arty Bishop 6c3aeab041 Added a few minor fixes to Dialogs and RadarScreen 2025-12-27 15:01:47 +00:00
Arty Bishop 609c83ed34 Implemented landscape support for MapScreen 2025-12-27 15:01:47 +00:00
Arty Bishop 5a0ff019e9 Added more tweaks to screens and dependencies 2025-12-27 15:01:47 +00:00
Arty Bishop ad89830c51 Set up more screens with portrait/landscape TopBar 2025-12-27 15:01:47 +00:00
Arty Bishop da2c3853b0 Added initial support for portrait/landscape TopBar 2025-12-27 15:01:47 +00:00
Arty Bishop 94735bc453 Reverted using SharedElement API and removing NavBar 2025-12-27 15:01:47 +00:00
Arty Bishop 42e7fa4356 Removed Material3 NavigationBar, other minor fixes 2025-12-27 15:01:47 +00:00
Arty Bishop 9ea4da9d21 Updated dependencies and gradle plugin, minor fixes 2025-12-27 15:01:47 +00:00
Arty Bishop a6cbd89ff1 Implemented experimental SharedElement transition API 2025-12-27 15:01:47 +00:00
Arty Bishop aa9365b2fd Updated to API 36 (Android 16), bumped dependencies 2025-12-27 15:01:47 +00:00
Arty Bishop bffb4842b1 Added Selection Warning and App Welcome Dialogs 2025-12-27 15:01:47 +00:00
Arty Bishop fcec36ec68 Updated dependencies and gradle plugin, minor fixes 2025-12-27 15:01:47 +00:00
Rico Beier-Grunwald 483d846233 Improvised solution to transmit frequencies to IC705 2025-12-27 15:01:47 +00:00
Arty Bishop 11841ca3d7 Extracted a few UI actions into reusable use cases 2025-12-27 15:01:47 +00:00
Arty Bishop 01139f76e7 Implemented EclipsedIndicator and MapScroll on select 2025-12-27 15:01:47 +00:00
Arty Bishop fdbdc513f1 Implemented a basic map data card on MapScreen 2025-12-27 15:01:47 +00:00
Arty Bishop 880fe10bfd Added multiple fixes to MapScreen and components 2025-12-27 15:01:47 +00:00
Arty Bishop 5b62474c27 Added multiple fixes to RadarScreen and components 2025-12-27 15:01:47 +00:00
Arty Bishop cdf642d9f3 Added multi selection for Satellite types filter dialog 2025-12-27 15:01:47 +00:00
Arty Bishop e3955239f3 Added even more tweaks to Dialog Layouts and UI 2025-12-27 15:01:47 +00:00
Arty Bishop 5842239e26 Added several tweaks to Screens, Dialogs and Layouts 2025-12-27 15:01:47 +00:00
Arty Bishop 86512a44d2 Updated dependencies and gradle plugin, minor fixes 2025-12-27 15:01:47 +00:00
Arty Bishop d1b579ac4c Updated dependencies and gradle plugin, minor fixes 2025-12-27 15:01:46 +00:00
Arty Bishop 5040767296 Updated dependencies and gradle plugin, minor fixes 2025-12-27 15:01:46 +00:00
Arty Bishop 2b6a892a50 Updated dependencies and gradle plugin, minor fixes 2025-12-27 15:01:46 +00:00
Arty Bishop 5aa837e5ef Updated dependencies and gradle plugin, minor fixes 2025-12-27 15:01:46 +00:00
Pr0pHesyer f1c978d167 Added light theme support, monochrome laucher icon 2025-12-27 15:01:46 +00:00
domnantas 34ec942e7b Implement audio cues for aiming 2025-12-27 15:01:46 +00:00
Arty Bishop 630fa2877f Integrated fixes from release v3.1.4 (leap year bug) 2025-12-27 15:01:46 +00:00
Arty Bishop ad0b611bcb Added several tweaks to PassesDialog, RadiosDialog 2025-12-27 15:01:46 +00:00
Arty Bishop 68d814d996 Added several tweaks to TimerRow and NextPassRow 2025-12-27 15:01:46 +00:00
Arty Bishop 08767c5589 Updated dependencies, added EntriesDialog tweaks 2025-12-27 15:01:46 +00:00
Vasileios Chroniadis 1f53ffbda9 Update compose activity and navigation libs 2025-12-27 15:01:45 +00:00
Arty Bishop 8dc0cf7051 Fixed a minor crash in SettingsRepo, updated deps 2025-12-27 15:01:45 +00:00
Arty Bishop ae53e5dc35 Added minor fixes to EntriesDialog and passes view 2025-12-27 15:01:45 +00:00
Arty Bishop 213ac7c278 Switched to gradle version catalog for dependencies 2025-12-27 15:01:45 +00:00
Arty Bishop ab02abbf4e Switched back to Wikimedia map tiles provider 2025-12-27 15:01:45 +00:00
Arty Bishop a1d5cda998 Updated dependencies, bumped the minSdk to 26 2025-12-27 15:01:45 +00:00
Arty Bishop a6b5498f55 Added type reset for satellite selection screen 2025-12-27 15:01:45 +00:00
Arty Bishop a52788e72f Added shared TimerBar composable function 2025-12-27 15:01:45 +00:00
Arty Bishop 4b63882c70 Fixed display and font size scaling for MainActivity 2025-12-27 15:01:45 +00:00
Arty Bishop 549a915b24 Slightly changed classes structure for readability 2025-12-27 15:01:45 +00:00
Arty Bishop 19d0c1b108 Added more frequency data fields to Radio class 2025-12-27 15:01:45 +00:00
Arty Bishop 269d48d250 Added passes filtering by modes to PassesScreen 2025-12-27 15:01:45 +00:00
Arty Bishop a088a7a0a2 Switched to MVI architecture for PassesScreen 2025-12-27 15:01:45 +00:00
Arty Bishop b9ade5f243 Cleaned up SharedPreferences keys and values 2025-12-27 15:01:45 +00:00
Arty Bishop e100484810 Updated gradle plugin, removed BuildConfig usages 2025-12-27 15:01:45 +00:00
Arty Bishop b1aa623b29 Slightly changed classes structure for readability 2025-12-27 15:01:45 +00:00
Arty Bishop e36e064374 Added minor tweaks to Dialogs and layouts 2025-12-27 15:01:45 +00:00
Arty Bishop d59f9c7506 Created future InfoScreen, added resources 2025-12-27 15:01:45 +00:00
Arty Bishop 49f713d82c Cleaned up EntriesScreen and ViewModel logic 2025-12-27 15:01:45 +00:00
Arty Bishop 1aefb04eb5 Enabled StrictMode, removed several deprecations 2025-12-27 15:01:45 +00:00
Arty Bishop dca3f2c773 Updated compileSdk and targetSdk to Android 14 2025-12-27 15:01:45 +00:00
Arty Bishop fc0b995bac Added various minor fixes, cleaned up classes 2025-12-27 15:01:45 +00:00
Arty Bishop 5a8108384f Added minor fixes to resources and build scripts 2025-12-27 15:01:45 +00:00
Arty Bishop 7191e02f99 Updated gradle plugin and dependencies versions 2025-12-27 15:01:45 +00:00
Arty Bishop 0ca372b73f Added Sinhala translation by Dilshan Hapuarachchi 2025-12-27 15:01:45 +00:00
Arty Bishop c4ecdb7a78 Added GeoConverter.kt and various minor fixes 2025-12-27 15:01:45 +00:00
Arty Bishop 1719f8cc2e Updated gradle wrapper and cleaned up files 2025-12-27 15:01:45 +00:00
Arty Bishop 3580854479 Migrated project build files to use kotlin script 2025-12-27 15:01:45 +00:00
Arty Bishop 28e926eb86 Extracted implementation classes into data module 2025-12-27 15:01:45 +00:00
Arty Bishop 5a449d4133 Added a basic .editorconfig file to the project 2025-12-27 15:01:45 +00:00
Arty Bishop f8d5542af5 Renamed base module into domain 2025-12-27 15:01:45 +00:00
Arty Bishop 9bb73f28b7 Integrated the data update fix from v3.1.3 2025-12-27 15:01:45 +00:00
Arty Bishop 7e2b3bc712 Updated dependencies and libraries versions 2025-12-27 15:01:43 +00:00
Arty Bishop f8e7bcdf39 Even more restructuring and code cleanup 2025-12-27 15:01:40 +00:00
Arty Bishop a807cfd8f6 Further classes restructuring and code cleanup 2025-12-27 15:01:38 +00:00
Arty Bishop f6fcd21404 Simplified packages and classes structure 2025-12-27 15:01:33 +00:00
Arty Bishop 208c587ddf Created SelectionRepository, cleaned up code 2025-12-27 15:01:33 +00:00
Arty Bishop 5d2a870319 Minor fixes for entries, settings and maps 2025-12-27 15:01:33 +00:00
Arty Bishop 942c7ce65f Switched to a different maps tile source 2025-12-27 15:01:33 +00:00
Arty Bishop 4c13cc98a1 Replaced Google Hilt with manual DI 2025-12-27 15:01:33 +00:00
Arty Bishop 622d4e3632 Further classes movement and cleanup 2025-12-27 15:01:33 +00:00
Arty Bishop 542cfcfd50 Fixes and refactoring for LocationSource 2025-12-27 15:01:33 +00:00
Arty Bishop 12fabc8f23 Fixes and refactoring for SettingsScreen 2025-12-27 15:01:33 +00:00
Arty Bishop f7f317cf8b Major classes refactoring and code cleanup 2025-12-27 15:01:33 +00:00
Arty Bishop 6572dd4eab More fixes for SettingsScreen.kt and its states 2025-12-27 15:01:33 +00:00
Arty Bishop 466c029be6 Added more functionality to SettingsScreen.kt 2025-12-27 15:01:33 +00:00
Arty Bishop 3da5b43e0a Added radar ping animation of satellite position 2025-12-27 15:01:33 +00:00
Arty Bishop 38ad7c46a8 Reimplemented RadarView in Jetpack Compose 2025-12-27 15:01:33 +00:00
Arty Bishop 05b5af41ba Added navigation parameters to RadarScreen 2025-12-27 15:01:33 +00:00
Arty Bishop 049d84e261 Further classes movement and cleanup 2025-12-27 15:01:33 +00:00
Arty Bishop bf51fddc4d Further classes and dependencies cleanup 2025-12-27 15:01:33 +00:00
Arty Bishop 007e6d8f9d Dependencies, resources and code cleanup 2025-12-27 15:01:33 +00:00
Arty Bishop 68d35f0104 Converted LocatorDialog and PositionDialog 2025-12-27 15:01:33 +00:00
Arty Bishop 39959c7a10 Implemented ModesDialog in Jetpack Compose 2025-12-27 15:01:32 +00:00
Arty Bishop c592b8d3ce Added map in compose, removed MapFragment 2025-12-27 15:01:32 +00:00
Arty Bishop acb851d4f6 Added filter dialog, removed PassesFragment 2025-12-27 15:01:32 +00:00
Arty Bishop a08cfc7fb8 Added compose pull to refresh for passes list 2025-12-27 15:01:32 +00:00
Arty Bishop 1ea4da3baf Added types dialog, removed EntriesFragment 2025-12-27 15:01:32 +00:00
Arty Bishop a817423e7e Even more fixes for pass item in Compose 2025-12-27 15:01:32 +00:00
Arty Bishop 46f0b9608b More fixes for pass item in Compose 2025-12-27 15:01:32 +00:00
Arty Bishop ac479f80ee Initial pass item Compose implementation 2025-12-27 15:01:32 +00:00
Arty Bishop f13662747c Various fixes for EntriesScreen class 2025-12-27 15:01:32 +00:00
Arty Bishop f8d1909737 Installed Timber, updated dependencies 2025-12-27 15:01:32 +00:00
Arty Bishop 7628582fd7 Added fixes after switching to Material3 2025-12-27 15:01:32 +00:00
Arty Bishop b1ff0921c3 Switched to using Material3 components 2025-12-27 15:01:32 +00:00
Arty Bishop 0755b20cf0 Added Material3 dependencies and Theme 2025-12-27 15:01:30 +00:00
Arty Bishop 7dca788c01 More changes to Entries Screen in Compose 2025-12-27 15:01:29 +00:00
Arty Bishop 853c36d19b Initial entries list implementation in Compose 2025-12-27 15:01:28 +00:00
Arty Bishop ee5b5a894b Recreated About screen in Compose 2025-12-27 15:01:28 +00:00
Arty Bishop 8f411d4c5e Added Compose dependencies and basic classes 2025-12-27 15:01:28 +00:00
Arty Bishop 0303c53a72 Switched back to BottomNavigationView 2025-12-27 15:01:28 +00:00
Arty Bishop c7282a3290 v3.2.3 - Added Spanish (es-rES) translation by CertainBot #183 2025-12-27 13:46:17 +00:00
Arty Bishop 2abe0c4582 v3.2.2 - Fixed translation issues #168 #172, AMSAT priority #173 2025-10-04 11:21:22 +01:00
Eugene c2081e93b8 fixes for Ukrainian translation 2025-09-27 14:51:39 +01:00
Arty Bishop c9d7f5df09 v3.2.1 - Added Ukrainian language and language switching, API 35 2025-09-07 15:35:48 +01:00
Arty Bishop c910e37d00 Added negative elevation filtering when sent via BT/Network (#164) 2025-09-07 15:22:06 +01:00
Eugene c0debc038a Add locale_config.xml to allow per-app language switching (#166) 2025-09-02 17:33:48 +01:00
Eugene c3e2c75b60 Add translations for Ukrainian 2025-08-26 14:21:00 +01:00
Arty Bishop f4c7814061 v3.2.0 - Added calendar support, Sinhala translation 2025-07-07 17:31:58 +01:00
Arty Bishop 3a163fb939 Merge pull request #137 from 493505110/main
Add calendar support
2024-08-04 15:20:24 +01:00
zhou2008 e41ac8f367 add calendar support 2024-07-31 19:07:32 +08:00
Arty Bishop 04da45e278 v3.1.4 - Fixed leap year bug, various other minor fixes 2024-03-09 15:18:50 +00:00
Arty Bishop b1562b4796 v3.1.3 - Fixed the satellite data update bug #108 2023-05-21 19:36:30 +01:00
Chris Kuethe b5adc73a6d Update McCants TLE URL
According to https://www.mmccants.org/tles/index.html, "PrismNet will be shutting down in a few weeks."

I have no idea how long that means, but let's fix it before it's too late.
2023-04-12 10:10:52 +01:00
Arty Bishop 4fc76ecbd9 Updated dependencies, minor fixes 2023-01-27 15:59:19 +00:00
mrchi fb296786a5 Update button width for btn_prev and btn_next 2022-12-07 10:32:40 +00:00
mrchi e41cee61cf Update translations for zh-rCN 2022-12-07 10:32:40 +00:00
mrchi c9d0c414d0 Update zh-rCN file to keep same format with origin 2022-12-07 10:32:40 +00:00
Arty Bishop b156f666ef v3.1.2 - Android 13, auto update, R8 minification 2022-10-23 14:20:54 +01:00
Arty Bishop baf61ca9bc Swapped WorkManager for manual update check 2022-10-23 14:20:54 +01:00
Arty Bishop 1685691d2f Added recent data update info 2022-10-23 14:20:54 +01:00
Arty Bishop e531eb85e3 Enabled R8 build minification 2022-10-23 14:20:54 +01:00
Arty Bishop ccad82aa6d Added automatic data update toggle #96 2022-10-23 14:20:54 +01:00
Arty Bishop 836fac2bb7 Added automatic data update worker 2022-10-23 14:20:54 +01:00
Arty Bishop 216f96caef Added auto passes recalculation on TLE update 2022-10-23 14:20:54 +01:00
Arty Bishop c5c8df0b5e Switched to targeting Android 13, API v33 2022-10-23 14:20:54 +01:00
Arty Bishop b9a25cc941 Added Charles proxy network config for debugging 2022-10-23 14:20:54 +01:00
Arty Bishop bbb89b7e6b v3.1.1 - R4UAB source, minor fixes, libs update 2022-08-07 13:33:21 +01:00
Arty Bishop bf1ac5fc2c Fixed negative lat/lon manual input bug 2022-07-16 13:47:41 +02:00
Arty Bishop 73557d30f2 Increased radar sweep rotation speed 2022-07-16 13:34:09 +02:00
Arty Bishop 0de157f3e2 Re-enabled the rotator control switch 2022-07-16 13:33:50 +02:00
Arty Bishop 9529f74c48 v3.1.0 - Chinese i18n, BT reporting, satellite categories 2022-06-03 16:14:40 +01:00
Arty Bishop 378c3ac4bf Updated dependencies, minor fixes 2022-05-18 19:00:48 +01:00
Arty Bishop e4a4b7eeb1 Updated dependencies, extracted versions 2022-05-18 19:00:48 +01:00
Arty Bishop 7653de2cee Added basic SatTypes spinner to EntriesFragment.kt 2022-05-18 19:00:48 +01:00
Arty Bishop 53697ef0c4 Now storing fetched satellite types in shared prefs 2022-05-18 19:00:48 +01:00
Arty Bishop 8c9d2031c2 Added satellite categories and sources map 2022-05-18 19:00:48 +01:00
Arty Bishop a006d0558a Fixed radar track display bug in RadarViewModel.kt 2022-05-18 19:00:48 +01:00
Arty Bishop 278c06170b Minor fixes to strings and ui, crash fixed #63 2022-05-18 19:00:48 +01:00
Arty Bishop a2f37e6b9f Merge pull request #92 from BA7LWN/main
Add files via upload
2022-05-16 20:27:52 +01:00
BA7LWN 93fa038b36 Add files via upload
Add Simplified Chinese
2022-05-15 12:36:09 +08:00
Arty Bishop 9f10f1d2e4 Merge pull request #87 from ajohns1288/main
Bluetooth added - but needs improvement
2022-03-21 19:44:01 +00:00
Andrew dd76dcfc98 Bluetooth added 2022-03-18 14:35:14 -04:00
Arty Bishop b35c3a0e61 v3.0.3 - Minor correcting release 2022-03-16 08:55:15 +00:00
Arty Bishop 47a83a8bfe Fixed UninitPropertyAccessException crash in MapFragment 2022-03-13 20:38:06 +00:00
Arty Bishop de2978138a v3.0.2 - Fixes and code cleanup, version bumped 2022-03-11 14:38:53 +00:00
Arty Bishop abb1bd4718 Various tweaks and minor refactoring, fixed issue #84 2022-03-11 12:45:26 +00:00
Arty Bishop ec553bf429 Added more satellite parameters to the map screen 2022-03-10 10:42:20 +00:00
Arty Bishop 488fe352cd Added satellite visibility and phase to MapFragment.kt #62 2022-03-09 13:22:20 +00:00
Arty Bishop 7fdc939450 Added satellite visibility parameter to RadarFragment.kt #62 2022-03-09 12:50:15 +00:00
Arty Bishop ae08c7c937 Added satellite visibility parameter to SatPos.kt #62 2022-03-07 15:14:57 +00:00
Arty Bishop 4ee74a6789 Renamed and moved several classes 2022-03-06 18:12:11 +00:00
Arty Bishop 5804f42211 Swapped buy me a coffee platform for ko-fi 2022-03-06 18:12:11 +00:00
Arty Bishop 29c3b2f2f1 Moved and renamed several classes 2022-03-06 18:12:11 +00:00
Arty Bishop 20bf1cedd0 Added Celestrak and SatNOGS links to the README.md 2022-03-03 20:28:32 +00:00
Arty Bishop 8a24c50448 v3.0.0 - Minor fixes, updates to readme, copyright and version 2022-03-02 15:07:29 +00:00
Arty Bishop 3b761f9589 Added an empty list message to the Radar screen 2022-03-01 18:49:32 +00:00
Arty Bishop 20306ec83e Added a basic default fragment transition animation 2022-03-01 18:33:40 +00:00
Arty Bishop 930e05cc58 Various changes to layouts and strings 2022-03-01 18:32:59 +00:00
Arty Bishop acc308379c Added an empty list message to the Passes screen 2022-03-01 18:32:58 +00:00
Arty Bishop 65941a8304 Sorted out the memory leak in radar fragment 2022-02-27 10:41:46 +00:00
Arty Bishop c58cf94181 Sorted out the memory leak in passes fragment 2022-02-26 19:50:09 +00:00
Arty Bishop 88228bfad7 Fixed an issue with messed up layouts via system override #83 2022-02-25 21:33:39 +00:00
Arty Bishop 550ffdf92d Swapped colors and enabled smooth scroll on the map screen 2022-02-24 18:53:48 +00:00
Arty Bishop 89be2dfa43 Added satellite dots to increase visibility on the map 2022-02-24 18:24:45 +00:00
Arty Bishop 03323172ea Fixed an old issue regarding satellite footprint rendering #36 2022-02-23 19:17:41 +00:00
Arty Bishop 74cbad5127 Interfaces and classes refactoring and code cleanup 2022-02-22 19:30:07 +00:00
Arty Bishop 0c71c4db29 Changes to RadarFragment's satellite data views 2022-02-22 11:29:49 +00:00
Arty Bishop 6189fb497e Further fixes to strings and PassesFragment's ui 2022-02-21 18:45:41 +00:00
Arty Bishop 00549bbcba Various fixes to PassesFragment's ui state handling 2022-02-21 17:27:53 +00:00
Arty Bishop 9fd0d5f2e2 Various changes to layouts, styles, strings and drawables 2022-02-20 20:01:47 +00:00
Arty Bishop 567200cfe4 Showing entries and radios numbers in SettingsFragment.kt 2022-02-19 13:52:45 +00:00
Arty Bishop 0404f7be90 Added ability to get the number of entries and radios 2022-02-19 13:27:15 +00:00
Arty Bishop afe9f77869 Further changes to strings, layouts and styles 2022-02-19 12:32:24 +00:00
Arty Bishop 72f78e5c6b Changes to location and settings handlers 2022-02-19 12:13:02 +00:00
Arty Bishop ffd1fc1551 Restored manual location input functionality #40 2022-02-19 12:13:01 +00:00
Arty Bishop 9d6dbc8d31 Swapped locator alert dialog for a dedicated dialog fragment 2022-02-19 12:11:50 +00:00
Arty Bishop 6400d1d395 Alterations to the pass filter dialog UI 2022-02-19 12:11:50 +00:00
Arty Bishop dc680231ea Swapped modes alert dialog for a custom dialog fragment 2022-02-19 12:11:50 +00:00
Arty Bishop 97e6f8b7f5 Various UI and code changes to SourcesDialog.kt 2022-02-19 12:11:50 +00:00
Arty Bishop 22d0db1d78 Further changes to layouts and views #76 2022-02-19 12:11:49 +00:00
Arty Bishop bdc0000476 Added BottomAppBar to several fragments, various ui changes 2022-02-19 12:10:30 +00:00
Arty Bishop 64b5010a44 Destructively migrated to the new database 2022-02-19 12:10:30 +00:00
Arty Bishop 92eec290b1 Moved satellite selections storage to SharedPreferences 2022-02-19 12:10:30 +00:00
Arty Bishop a7c840d532 Restored satellite selection functionality 2022-02-19 12:10:30 +00:00
Arty Bishop 80ba3644f7 Altered bottom navigation view back stack saving 2022-02-19 12:10:30 +00:00
Arty Bishop a0c6ceef15 Restored bottom navigation bar for comparison 2022-02-19 12:10:29 +00:00
Arty Bishop 8b8c443bd1 Switched to using SafeArgs plugin for data transfer 2022-02-19 12:07:40 +00:00
Arty Bishop 72f71b2153 Fixed pass preferences values handling 2022-02-19 12:07:40 +00:00
Arty Bishop 7349ee6c5e Optimized satellite item selection flow + bugfix 2022-02-19 12:07:39 +00:00
Arty Bishop 04c4ef25d2 Extracted some logic to the core Predictor.kt class 2022-02-19 12:07:39 +00:00
Arty Bishop b5da3d3ac4 Various visual changes, update error fix 2022-02-19 12:07:39 +00:00
Arty Bishop 28a6fecab3 Added instrumentation testing dependencies 2022-02-19 12:07:39 +00:00
Arty Bishop 7eb25e45b3 Disabled navigation to satellites fragment on empty database 2022-02-19 12:07:39 +00:00
Arty Bishop e9dc166e1e Added a function to clear the database #79 2022-02-19 12:07:39 +00:00
Arty Bishop a7427f3632 Extracted ContentResolver into LocalSource.kt 2022-02-07 17:50:03 +00:00
Arty Bishop 9b38e2c63e Enabled data update progress indicator 2022-02-07 16:01:40 +00:00
Arty Bishop 61cbc212b5 Swapped SettingsHandler.kt class for its interface 2022-02-06 19:22:39 +00:00
Arty Bishop 27156b5070 Dependencies update, minor code cleanup 2022-02-06 18:02:35 +00:00
Arty Bishop b8bb6d41f9 Extracted logic into SettingsViewModel.kt 2022-01-30 20:32:49 +00:00
Arty Bishop 794d132086 Dependencies update, code cleanup 2022-01-26 19:15:44 +00:00
Arty Bishop 608b7e9311 Now properly handling the state of location updates 2022-01-24 17:56:57 +00:00
Arty Bishop b0b2283ca5 Even further changes to layouts and styles 2022-01-23 19:21:07 +00:00
Arty Bishop 38787b45c1 More changes to layouts, fragments and styles 2022-01-23 17:01:59 +00:00
Arty Bishop f9ebe84012 Further changes to layouts and fragments 2022-01-22 20:36:59 +00:00
Arty Bishop f34cb8eb3c Added database migration to v5 2022-01-22 13:42:10 +00:00
Arty Bishop 148104880e Minor fixes and code cleanup 2022-01-13 21:13:17 +00:00
Arty Bishop f559c60154 Temporarily restored BottomNavigationBar 2022-01-10 22:15:21 +00:00
Arty Bishop 82032109dd Minor visual changes to dialogs and settings 2022-01-09 18:03:21 +00:00
Arty Bishop 7d3d746c4d Further changes to SettingsFragment.kt 2022-01-09 18:03:21 +00:00
Arty Bishop a9a4aa9737 Switched to a generic LocationHandler.kt 2022-01-09 18:03:21 +00:00
Arty Bishop eebe691419 Fixed satellite track exception crash 2022-01-09 18:03:20 +00:00
Arty Bishop 6926f87747 Further changes to layouts and fragments 2022-01-09 18:01:48 +00:00
Arty Bishop 971379b773 Added stale data cleanup on every data update 2022-01-09 18:01:48 +00:00
Arty Bishop ad3bdff823 Further changes to fragments and layouts 2022-01-09 18:01:45 +00:00
Arty Bishop 9c137ada9f Moved DataSources storage to SharedPreferences 2022-01-09 18:00:25 +00:00
Arty Bishop 0f4a70b204 Further changes to layouts and fragments 2022-01-09 17:59:24 +00:00
Arty Bishop f58f851151 Added PassesPrefsDialog.kt with extra, will cleanup later 2022-01-09 17:59:24 +00:00
Arty Bishop be26b3e1dc Restored Preferences fragment functionality 2022-01-09 17:59:24 +00:00
Arty Bishop 9ca8ec65c4 Replaced PreferencesAPI fragment with a regular one 2022-01-09 17:59:10 +00:00
5775 changed files with 14203 additions and 8680 deletions

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
# indicate this is the root of the project
root = true
[*.{kt,java,xml,gradle,md}]
charset = utf-8
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true
end_of_line = lf
-67
View File
@@ -1,67 +0,0 @@
name: Look4Sat CI
on:
push:
tags:
- '*'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout latest commit
uses: actions/checkout@v2
- name: Setup java environment
uses: actions/setup-java@v1
with: { java-version: 11 }
- name: Assemble APK and Bundle
run: |
chmod +x ./gradlew
./gradlew assembleRelease
./gradlew bundleRelease
- name: Sign APK
uses: r0adkll/sign-android-release@v1
id: sign_apk
with:
releaseDirectory: app/build/outputs/apk/release
signingKeyBase64: ${{ secrets.KEY_STORE }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
alias: ${{ secrets.KEY_ALIAS }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: "31.0.0"
- name: Sign Bundle
uses: r0adkll/sign-android-release@v1
id: sign_bundle
with:
releaseDirectory: app/build/outputs/bundle/release
signingKeyBase64: ${{ secrets.KEY_STORE }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
alias: ${{ secrets.KEY_ALIAS }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: "31.0.0"
- name: Rename APK
run: |
mv ${{steps.sign_apk.outputs.signedReleaseFile}} app/build/outputs/apk/release/look4sat.apk
- name: Deploy Bundle to Play Store
uses: r0adkll/upload-google-play@v1.0.15
with:
serviceAccountJsonPlainText: ${{secrets.SERVICE_ACCOUNT_JSON}}
packageName: com.rtbishop.look4sat
track: production
whatsNewDirectory: fastlane/metadata/android/en-US/whatsnew
releaseFiles: ${{steps.sign_bundle.outputs.signedReleaseFile}}
- name: Create release
uses: ncipollo/release-action@v1
with:
artifacts: app/build/outputs/apk/release/look4sat.apk
bodyFile: fastlane/metadata/android/en-US/whatsnew/whatsnew-en-US
token: ${{ secrets.RELEASE_TOKEN }}
+76
View File
@@ -0,0 +1,76 @@
name: release
on:
push:
tags:
- v**
env:
TAG_NAME: ${{ github.ref_name }}
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Setup Java
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Assemble Artifacts
run: |
./gradlew assembleRelease
./gradlew bundleRelease
- name: Sign APK
uses: r0adkll/sign-android-release@v1
id: sign_apk
with:
releaseDirectory: app/build/outputs/apk/release
signingKeyBase64: ${{ secrets.KEY_STORE }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
alias: ${{ secrets.KEY_ALIAS }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: "36.0.0"
- name: Sign Bundle
uses: r0adkll/sign-android-release@v1
id: sign_bundle
with:
releaseDirectory: app/build/outputs/bundle/release
signingKeyBase64: ${{ secrets.KEY_STORE }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
alias: ${{ secrets.KEY_ALIAS }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: "36.0.0"
- name: Rename Artifacts
run: |
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
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{secrets.SERVICE_ACCOUNT_JSON}}
packageName: com.rtbishop.look4sat
releaseFiles: app/build/outputs/apk/release/look4sat.aab
track: production
whatsNewDirectory: fastlane/metadata/android/en-US/whatsnew
- name: Create Release
run: |
gh release create $TAG_NAME --title=$TAG_NAME --generate-notes
gh release upload $TAG_NAME app/build/outputs/apk/release/look4sat.apk
+2 -1
View File
@@ -62,4 +62,5 @@ fastlane/readme.md
/app/release/app-release.aab
/app/release/app-release.apk
/app/release/output-metadata.json
/app/release/
/app/release/
/.kotlin/sessions/
+15 -12
View File
@@ -1,6 +1,6 @@
# Look4Sat: Satellite tracker
[![Look4Sat CI](https://github.com/rt-bishop/Look4Sat/actions/workflows/main.yml/badge.svg)](https://github.com/rt-bishop/Look4Sat/actions/workflows/main.yml)
[![Look4Sat CI](https://github.com/rt-bishop/Look4Sat/actions/workflows/release.yml/badge.svg)](https://github.com/rt-bishop/Look4Sat/actions/workflows/release.yml)
[<img src="https://play.google.com/intl/en_gb/badges/static/images/badges/en_badge_web_generic.png" alt="Get it on Google Play" height="80">](https://play.google.com/store/apps/details?id=com.rtbishop.look4sat)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="80">](https://f-droid.org/packages/com.rtbishop.look4sat/)
@@ -8,25 +8,28 @@
### Radio satellite tracker and pass predictor for Android, inspired by Gpredict
<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/2.png" width="160"/>
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/3.png" width="160"/>
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/4.png" width="160">
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/1.png" width="192"/>
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/2.png" width="192"/>
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/3.png" width="192"/>
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/4.png" width="192">
</p>
### 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 [Celestrak](https://celestrak.com/) and [SatNOGS](https://satnogs.org/) you have access to over 9000 active satellites.\
You can search the entire database by NORAD Catalog Number or the satellite's name.
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.
Orbital positions and passes are calculated relative to your location.\
To get reliable data make sure to set the station position via the app Settings.
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, Jetpack Compose and Navigation.\
It is now and always will be completely ad-free and open-source.
## Main features:
* Predicting satellite positions and passes for up to 4 days (96 hours)
* Predicting satellite positions and passes for up to 10 days
* Showing the list of currently active and upcoming satellite passes
* Showing the active pass progress, polar trajectory and transceivers info
* Showing the satellite positional data, footprint and ground track on a map
* Custom TLE data import is available via files with TXT or TLE extensions
* Offline first: calculations are made offline. Weekly update of TLE data is recommended.
* Showing the satellite positional data, footprint and ground track on the map
* Custom TLE satellite data import is available via Three Line Element .txt files
* Offline first: calculations are made offline. Weekly TLE data update is recommended.
-83
View File
@@ -1,83 +0,0 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "kotlin-kapt"
id "dagger.hilt.android.plugin"
}
android {
compileSdkVersion 31
buildToolsVersion "31.0.0"
defaultConfig {
applicationId "com.rtbishop.look4sat"
minSdkVersion 21
resConfigs "en,ru"
targetSdkVersion 31
versionCode 260
versionName "2.6.0"
kapt {
arguments {
arg("room.schemaLocation", "$projectDir/schemas")
arg("room.incremental", "true")
arg("room.expandProjection", "true")
}
}
}
// def props = new Properties()
// file("../securityKeys.properties").withInputStream { props.load(it) }
buildTypes {
release {
// buildConfigField "String", "ApiKey", props.getProperty("apiKey")
// minifyEnabled true
// shrinkResources true
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
postprocessing {
removeUnusedCode true
removeUnusedResources true
obfuscate false
optimizeCode true
}
}
}
buildFeatures {
viewBinding true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
packagingOptions {
resources.excludes += "DebugProbesKt.bin"
}
}
dependencies {
implementation project(':core')
implementation "androidx.core:core-splashscreen:$splash_screen_version"
implementation "androidx.constraintlayout:constraintlayout:$constraint_layout_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
implementation "androidx.preference:preference-ktx:$preference_version"
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
implementation "org.osmdroid:osmdroid-android:$osmdroid_version"
implementation "com.jakewharton.timber:timber:$timber_version"
testImplementation "junit:junit:$junit_version"
testImplementation "org.mockito:mockito-android:$mockito_version"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
debugImplementation "com.squareup.leakcanary:leakcanary-android:$leak_canary_version"
}
+3
View File
@@ -0,0 +1,3 @@
plugins {
alias(libs.plugins.convention.applicationPlugin)
}
-21
View File
@@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# 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
@@ -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')"
]
}
}
@@ -1,212 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "0f08defa24a78647801005e9277faac7",
"entities": [
{
"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`))",
"fields": [
{
"fieldPath": "isSelected",
"columnName": "isSelected",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "tle.name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "tle.epoch",
"columnName": "epoch",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.meanmo",
"columnName": "meanmo",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.eccn",
"columnName": "eccn",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.incl",
"columnName": "incl",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.raan",
"columnName": "raan",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.argper",
"columnName": "argper",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.meanan",
"columnName": "meanan",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.catnum",
"columnName": "catnum",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "tle.bstar",
"columnName": "bstar",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.xincl",
"columnName": "xincl",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.xnodeo",
"columnName": "xnodeo",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.omegao",
"columnName": "omegao",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.xmo",
"columnName": "xmo",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.xno",
"columnName": "xno",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.isDeepspace",
"columnName": "isDeepspace",
"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, '0f08defa24a78647801005e9277faac7')"
]
}
}
+18 -8
View File
@@ -1,19 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.rtbishop.look4sat">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".presentation.Look4SatApplication"
android:allowBackup="false"
android:name=".MainApplication"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.Look4Sat.Main">
android:roundIcon="@mipmap/ic_launcher_round">
<activity
android:name=".presentation.Look4SatActivity"
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.Look4Sat.SplashScreen">
<intent-filter>
@@ -21,6 +27,10 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<meta-data
android:name="android.telephony.PROPERTY_SATELLITE_DATA_OPTIMIZED"
android:value="com.rtbishop.look4sat" />
</application>
</manifest>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 22 KiB

@@ -0,0 +1,45 @@
/*
* Look4Sat. Amateur radio satellite tracker and pass predictor.
* Copyright (C) 2019-2026 Arty Bishop and contributors.
*
* 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
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.rtbishop.look4sat.core.presentation.MainTheme
class MainActivity : ComponentActivity() {
override fun attachBaseContext(newBase: Context?) {
val config = Configuration(newBase?.resources?.configuration)
applyOverrideConfiguration(config.apply { fontScale = 1.0f })
super.attachBaseContext(newBase)
}
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
enableEdgeToEdge()
super.onCreate(savedInstanceState)
setContent {
MainTheme(isDarkTheme = true) { MainScreen() }
}
}
}
@@ -0,0 +1,54 @@
/*
* Look4Sat. Amateur radio satellite tracker and pass predictor.
* Copyright (C) 2019-2026 Arty Bishop and contributors.
*
* 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
import android.app.Application
import com.rtbishop.look4sat.core.data.injection.MainContainer
import com.rtbishop.look4sat.core.domain.repository.IContainerProvider
import com.rtbishop.look4sat.core.domain.repository.IMainContainer
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class MainApplication : Application(), IContainerProvider {
private lateinit var container: IMainContainer
override fun getMainContainer(): IMainContainer = container
override fun onCreate() {
super.onCreate()
container = MainContainer(this)
// trigger automatic update every 48 hours
container.appScope.launch { checkAutoUpdate() }
// load satellite data on every app start
container.appScope.launch { container.satelliteRepo.initRepository() }
}
private suspend fun checkAutoUpdate(timeNow: Long = System.currentTimeMillis()) {
if (container.settingsRepo.otherSettings.value.stateOfAutoUpdate) {
val timeDelta = timeNow - container.settingsRepo.databaseState.value.updateTimestamp
if (timeDelta > 172_800_000L) { // 48 hours in ms
val sdf = SimpleDateFormat("d MMM yyyy - HH:mm:ss", Locale.getDefault())
println("Started periodic data update on ${sdf.format(Date())}")
container.databaseRepo.updateFromRemote()
}
}
}
}
@@ -0,0 +1,185 @@
/*
* Look4Sat. Amateur radio satellite tracker and pass predictor.
* Copyright (C) 2019-2026 Arty Bishop and contributors.
*
* 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
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteDefaults
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteType
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.rtbishop.look4sat.core.presentation.Screen
import com.rtbishop.look4sat.core.presentation.hasEnoughHeight
import com.rtbishop.look4sat.core.presentation.hasEnoughWidth
import com.rtbishop.look4sat.core.domain.repository.IContainerProvider
import com.rtbishop.look4sat.feature.map.mapDestination
import com.rtbishop.look4sat.feature.passes.passesDestination
import com.rtbishop.look4sat.feature.radar.radarDestination
import com.rtbishop.look4sat.feature.radiocontrol.radioControlDestination
import com.rtbishop.look4sat.feature.satellites.satellitesDestination
import com.rtbishop.look4sat.feature.settings.settingsDestination
@Composable
fun MainScreen(navController: NavHostController = rememberNavController()) {
val items = listOf(Screen.Satellites, Screen.Passes, Screen.Radar, Screen.Map, Screen.Settings)
val currentDestination = navController.currentBackStackEntryAsState().value?.destination?.route
val startDestination = Screen.Passes.route
// Observe radio tracking state for the status bar
val context = LocalContext.current
val container = (context.applicationContext as IContainerProvider).getMainContainer()
val trackingState by container.radioTrackingService.state.collectAsStateWithLifecycle()
NavigationSuiteScaffold(
navigationSuiteItems = {
items.forEach {
item(
icon = { Icon(painterResource(it.iconResId), stringResource(it.titleResId)) },
label = { Text(stringResource(it.titleResId)) },
selected = currentDestination?.contains(it.route) ?: false,
onClick = {
if (currentDestination?.contains(it.route) ?: false) return@item
navController.navigate(it.route) {
popUpTo(startDestination) { saveState = false }
launchSingleTop = true
restoreState = false
}
})
}
}, navigationSuiteColors = NavigationSuiteDefaults.colors(
navigationRailContainerColor = MaterialTheme.colorScheme.surfaceContainer
), layoutType = when {
!hasEnoughHeight() && hasEnoughWidth() -> NavigationSuiteType.NavigationRail
!hasEnoughWidth() -> NavigationSuiteType.ShortNavigationBarCompact
else -> NavigationSuiteType.ShortNavigationBarMedium
}
) {
Column {
NavHost(
navController = navController,
startDestination = startDestination,
enterTransition = { fadeIn(animationSpec = tween(350)) },
exitTransition = { fadeOut(animationSpec = tween(350)) },
modifier = Modifier.weight(1f)
) {
satellitesDestination { navController.navigateUp() }
passesDestination { catNum: Int, aosTime: Long ->
val radarRoute = "${Screen.Radar.route}?catNum=${catNum}&aosTime=${aosTime}"
navController.navigate(radarRoute)
}
radarDestination(
navigateUp = { navController.navigateUp() },
navigateToRadioControl = { catNum, aosTime ->
val route = "${Screen.RadioControl.route}?catNum=$catNum&aosTime=$aosTime"
navController.navigate(route)
}
)
radioControlDestination { navController.navigateUp() }
mapDestination()
settingsDestination()
}
// Radio tracking status banner (above bottom navigation)
if (trackingState.isActive) {
val infiniteTransition = rememberInfiniteTransition(label = "trackingPulse")
val alpha by infiniteTransition.animateFloat(
initialValue = 1f, targetValue = 0.4f,
animationSpec = infiniteRepeatable(
animation = tween(1000, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
), label = "pulseAlpha"
)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.primaryContainer)
.clickable {
val pass = trackingState.currentPass
if (pass != null) {
val route = "${Screen.RadioControl.route}?catNum=${pass.catNum}&aosTime=${pass.aosTime}"
navController.navigate(route)
}
}
.padding(horizontal = 12.dp, vertical = 6.dp)
) {
Box(
modifier = Modifier
.size(8.dp)
.clip(CircleShape)
.background(Color(0xFF4CAF50).copy(alpha = alpha))
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "Tracking: ${trackingState.currentPass?.name ?: ""}",
fontSize = 13.sp,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.onPrimaryContainer,
modifier = Modifier.weight(1f)
)
val txOk = if (trackingState.txConnected) "TX" else ""
val rxOk = if (trackingState.rxConnected) "RX" else ""
Text(
text = listOf(txOk, rxOk).filter { it.isNotBlank() }.joinToString("/"),
fontSize = 12.sp,
color = MaterialTheme.colorScheme.onPrimaryContainer
)
}
}
} // end Column
}
}
@@ -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() }
@@ -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))
}
}
@@ -1,79 +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.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.round
@Singleton
class OrientationSource @Inject constructor(private val sensorManager: SensorManager) :
SensorEventListener {
interface OrientationListener {
fun onOrientationChanged(azimuth: Float, pitch: Float, roll: Float)
}
private val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
private val rotationMatrix = FloatArray(9)
private val orientationValues = FloatArray(3)
private val oneRadDegrees = 57.295779513f
private var sensorAccuracy: Int = SensorManager.SENSOR_STATUS_UNRELIABLE
private var orientationListener: OrientationListener? = null
fun startListening(listener: OrientationListener) {
if (sensor != null && orientationListener !== listener) {
orientationListener = listener
sensorManager.registerListener(this, sensor, 16000)
} else return
}
fun stopListening() {
sensorManager.unregisterListener(this)
orientationListener = null
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
sensorAccuracy = accuracy
}
override fun onSensorChanged(event: SensorEvent) {
when {
orientationListener == null -> return
sensorAccuracy == SensorManager.SENSOR_STATUS_UNRELIABLE -> return
event.sensor == sensor -> updateOrientation(event.values)
}
}
private fun updateOrientation(rotationVector: FloatArray) {
SensorManager.getRotationMatrixFromVector(rotationMatrix, rotationVector)
SensorManager.getOrientation(rotationMatrix, orientationValues)
val azimuth = orientationValues[0] * oneRadDegrees
val pitch = orientationValues[1] * oneRadDegrees
val roll = orientationValues[2] * oneRadDegrees
val magneticAzimuth = (azimuth + 360f) % 360f
val roundedAzimuth = round(magneticAzimuth * 10) / 10
val roundedPitch = round(pitch * 10) / 10
val roundedRoll = round(roll * 10) / 10
orientationListener?.onOrientationChanged(roundedAzimuth, roundedPitch, roundedRoll)
}
}
@@ -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())
}
}
@@ -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")
}
}
@@ -1,40 +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.Source
@Dao
interface SourcesDao {
@Query("SELECT sourceUrl FROM sources")
suspend fun getSources(): List<String>
@Transaction
suspend fun updateSources(sources: List<Source>) {
deleteSources()
insertSources(sources)
}
@Query("DELETE from sources")
suspend fun deleteSources()
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertSources(sources: List<Source>)
}
@@ -1,31 +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.Transmitter
@Dao
interface TransmittersDao {
@Query("SELECT * FROM transmitters WHERE catnum = :catnum")
suspend fun getTransmitters(catnum: Int): List<Transmitter>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun updateTransmitters(transmitters: List<Transmitter>)
}
@@ -1,29 +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.model
import androidx.room.Embedded
import androidx.room.Entity
import com.rtbishop.look4sat.domain.predict.TLE
@Entity(tableName = "entries", primaryKeys = ["catnum"])
data class SatEntry(
@Embedded
val tle: TLE,
var isSelected: Boolean = false
)
@@ -1,33 +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.model
import androidx.room.Relation
data class SatItem(
val catnum: Int,
val name: String,
var isSelected: Boolean,
@Relation(
parentColumn = "catnum",
entity = Transmitter::class,
entityColumn = "catnum",
projection = ["mode"]
)
val modes: List<String>
)
@@ -1,24 +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.model
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "sources")
data class Source(@PrimaryKey var sourceUrl: String = String())
@@ -1,33 +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.model
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "transmitters")
data class Transmitter(
@PrimaryKey val uuid: String,
val info: String,
val isAlive: Boolean,
var downlink: Long?,
var uplink: Long?,
val mode: String?,
val isInverted: Boolean,
val catnum: Int?
)
@@ -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,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.injection
import android.content.ContentResolver
import android.content.Context
import android.content.SharedPreferences
import android.hardware.SensorManager
import android.location.LocationManager
import androidx.preference.PreferenceManager
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
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideContentResolver(@ApplicationContext context: Context): ContentResolver {
return context.contentResolver
}
@Provides
@Singleton
fun provideLocationManager(@ApplicationContext context: Context): LocationManager {
return context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
}
@Provides
@Singleton
fun provideSensorManager(@ApplicationContext context: Context): SensorManager {
return context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
}
@Provides
@Singleton
fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences {
return PreferenceManager.getDefaultSharedPreferences(context)
}
}
@@ -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)
}
}
@@ -1,32 +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.app.Application
import com.rtbishop.look4sat.BuildConfig
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber
@HiltAndroidApp
class Look4SatApplication : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
}
}
@@ -1,78 +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.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.domain.model.SatItem
import com.rtbishop.look4sat.databinding.ItemEntryBinding
class EntriesAdapter(private val clickListener: EntriesClickListener) :
RecyclerView.Adapter<EntriesAdapter.SatItemHolder>() {
private val diffCallback = object : DiffUtil.ItemCallback<SatItem>() {
override fun areItemsTheSame(oldItem: SatItem, newItem: SatItem): Boolean {
return oldItem.catnum == newItem.catnum
}
override fun areContentsTheSame(oldItem: SatItem, newItem: SatItem): Boolean {
return oldItem.isSelected == newItem.isSelected
}
}
private val differ = AsyncListDiffer(this, diffCallback)
fun submitList(items: List<SatItem>) {
differ.submitList(items)
}
override fun getItemCount(): Int = differ.currentList.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SatItemHolder {
return SatItemHolder.from(parent)
}
override fun onBindViewHolder(holder: SatItemHolder, position: Int) {
holder.bind(differ.currentList[position], clickListener)
}
class SatItemHolder private constructor(private val binding: ItemEntryBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: SatItem, listener: EntriesClickListener) {
binding.entryCheckbox.text = item.name
binding.entryCheckbox.isChecked = item.isSelected
itemView.setOnClickListener {
listener.updateSelection(listOf(item.catnum), item.isSelected.not())
}
}
companion object {
fun from(parent: ViewGroup): SatItemHolder {
val inflater = LayoutInflater.from(parent.context)
return SatItemHolder(ItemEntryBinding.inflate(inflater, parent, false))
}
}
}
interface EntriesClickListener {
fun updateSelection(catNums: List<Int>, isSelected: Boolean)
}
}
@@ -1,135 +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.entriesScreen
import android.os.Bundle
import android.view.View
import android.view.inputmethod.EditorInfo
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.SimpleItemAnimator
import com.google.android.material.snackbar.Snackbar
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.databinding.FragmentEntriesBinding
import com.rtbishop.look4sat.domain.model.SatItem
import com.rtbishop.look4sat.domain.model.DataState
import com.rtbishop.look4sat.presentation.ItemDivider
import com.rtbishop.look4sat.presentation.getNavResult
import com.rtbishop.look4sat.presentation.navigateSafe
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class EntriesFragment : Fragment(R.layout.fragment_entries) {
private val viewModel: EntriesViewModel by viewModels()
private val contentContract = ActivityResultContracts.GetContent()
private val filePicker = registerForActivityResult(contentContract) { uri ->
uri?.let { viewModel.updateDataFromFile(uri) }
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupComponents(view)
}
private fun setupComponents(view: View) {
val entriesAdapter = EntriesAdapter(viewModel)
val binding = FragmentEntriesBinding.bind(view).apply {
entriesRecycler.apply {
setHasFixedSize(true)
adapter = entriesAdapter
layoutManager = GridLayoutManager(requireContext(), 2)
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
addItemDecoration(ItemDivider(R.drawable.rec_divider_light))
}
entriesImportWeb.setOnClickListener {
findNavController().navigateSafe(R.id.action_entries_to_sources)
}
entriesImportFile.setOnClickListener { filePicker.launch("*/*") }
entriesSelectMode.setOnClickListener { showModesDialog() }
entriesSelectAll.setOnClickListener { viewModel.selectCurrentItems() }
entriesSearchBar.setOnQueryTextListener(viewModel)
entriesSearchBar.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
}
viewModel.satData.observe(viewLifecycleOwner, { satData ->
handleSatData(satData, binding, entriesAdapter)
})
getNavResult<List<String>>(R.id.nav_entries, "sources") { sources ->
viewModel.updateDataFromWeb(sources)
}
}
private fun handleSatData(
dataState: DataState<List<SatItem>>,
binding: FragmentEntriesBinding,
entriesAdapter: EntriesAdapter
) {
when (dataState) {
is DataState.Success -> {
entriesAdapter.submitList(dataState.data)
binding.entriesProgress.visibility = View.INVISIBLE
binding.entriesRecycler.visibility = View.VISIBLE
}
is DataState.Loading -> {
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 -> {
}
}
}
private fun showModesDialog() {
val modes = arrayOf(
"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"
)
val savedModes = BooleanArray(modes.size)
val selectedModes = viewModel.loadSelectedModes().toMutableList()
selectedModes.forEach { savedModes[modes.indexOf(it)] = true }
AlertDialog.Builder(requireContext()).apply {
setTitle(context.getString(R.string.modes_title))
setMultiChoiceItems(modes, savedModes) { _, which, isChecked ->
when {
isChecked -> selectedModes.add(modes[which])
selectedModes.contains(modes[which]) -> selectedModes.remove(modes[which])
}
}
setPositiveButton(context.getString(android.R.string.ok)) { _, _ ->
viewModel.saveSelectedModes(selectedModes)
}
setNeutralButton(context.getString(android.R.string.cancel)) { dialog, _ ->
dialog.dismiss()
}
create().show()
}
}
}
@@ -1,125 +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.entriesScreen
import android.content.ContentResolver
import android.net.Uri
import android.widget.SearchView
import androidx.lifecycle.*
import com.rtbishop.look4sat.domain.model.SatItem
import com.rtbishop.look4sat.domain.model.DataState
import com.rtbishop.look4sat.domain.DataRepository
import com.rtbishop.look4sat.framework.PreferencesSource
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.*
import javax.inject.Inject
@HiltViewModel
class EntriesViewModel @Inject constructor(
private val preferences: PreferencesSource,
private val resolver: ContentResolver,
private val dataRepository: DataRepository
) : ViewModel(), SearchView.OnQueryTextListener, EntriesAdapter.EntriesClickListener {
private val coroutineHandler = CoroutineExceptionHandler { _, throwable ->
Timber.d(throwable)
_satData.value = DataState.Error(throwable)
}
private val transModes = MutableLiveData(preferences.loadModesSelection())
private val currentQuery = MutableLiveData(String())
private val itemsWithModes = transModes.switchMap { modes ->
liveData { dataRepository.getSatelliteItems().collect { emit(filterByModes(it, modes)) } }
}
private val itemsWithQuery = currentQuery.switchMap { query ->
itemsWithModes.map { items -> DataState.Success(filterByQuery(items, query)) }
}
private val _satData = MediatorLiveData<DataState<List<SatItem>>>().apply {
addSource(itemsWithQuery) { value -> this.value = value }
}
private var shouldSelectAll = true
val satData: LiveData<DataState<List<SatItem>>> = _satData
fun updateDataFromFile(uri: Uri) {
viewModelScope.launch(coroutineHandler) {
_satData.value = DataState.Loading
runCatching {
resolver.openInputStream(uri)?.use { stream ->
dataRepository.updateDataFromFile(stream)
}
}.onFailure { _satData.value = DataState.Error(it) }
}
}
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>) {
transModes.value = modes
preferences.saveModesSelection(modes)
}
override fun onQueryTextSubmit(query: String): Boolean {
return true
}
override fun onQueryTextChange(newText: String): Boolean {
currentQuery.value = newText
return true
}
override fun updateSelection(catNums: List<Int>, isSelected: Boolean) {
viewModelScope.launch { dataRepository.updateSelection(catNums, isSelected) }
}
private fun filterByModes(items: List<SatItem>, modes: List<String>): List<SatItem> {
if (modes.isEmpty()) return items
return items.filter { item -> item.modes.any { mode -> mode in modes } }
}
private fun filterByQuery(items: List<SatItem>, query: String): List<SatItem> {
if (query.isBlank()) return items
return try {
items.filter { it.catnum == query.toInt() }
} catch (e: Exception) {
items.filter { item ->
val itemName = item.name.lowercase(Locale.getDefault())
itemName.contains(query.lowercase(Locale.getDefault()))
}
}
}
}
@@ -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,32 +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.mapScreen
import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.domain.predict.Satellite
data class MapData(
val pass: Satellite,
val catNum: Int,
val name: String,
val range: Double,
val altitude: Double,
val velocity: Double,
val qthLoc: String,
val osmPos: GeoPos
)
@@ -1,205 +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.mapScreen
import android.content.SharedPreferences
import android.graphics.Color
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.Paint
import android.os.Bundle
import android.view.View
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.databinding.FragmentMapBinding
import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.domain.predict.Satellite
import dagger.hilt.android.AndroidEntryPoint
import org.osmdroid.config.Configuration
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
import org.osmdroid.util.GeoPoint
import org.osmdroid.views.CustomZoomButtonsController
import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.*
import timber.log.Timber
import java.util.*
import javax.inject.Inject
@AndroidEntryPoint
class MapFragment : Fragment(R.layout.fragment_map) {
@Inject
lateinit var sharedPreferences: SharedPreferences
private val viewModel: MapViewModel by viewModels()
private val minLat = MapView.getTileSystem().minLatitude
private val maxLat = MapView.getTileSystem().maxLatitude
private val trackPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
strokeWidth = 2f
style = Paint.Style.STROKE
color = Color.RED
strokeCap = Paint.Cap.ROUND
strokeJoin = Paint.Join.ROUND
}
private val footprintPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.FILL_AND_STROKE
color = Color.parseColor("#26FFE082")
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Configuration.getInstance().load(requireContext(), sharedPreferences)
val binding = FragmentMapBinding.bind(view).apply {
mapView.apply {
setMultiTouchControls(true)
setTileSource(TileSourceFactory.WIKIMEDIA)
minZoomLevel = getMinZoom(resources.displayMetrics.heightPixels) + 0.25
maxZoomLevel = 5.75
controller.setZoom(minZoomLevel + 0.25)
zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER)
overlayManager.tilesOverlay.loadingBackgroundColor = Color.TRANSPARENT
overlayManager.tilesOverlay.loadingLineColor = Color.TRANSPARENT
overlayManager.tilesOverlay.setColorFilter(getColorFilter())
setScrollableAreaLimitLatitude(maxLat, minLat, 0)
// add overlays: 0 - GSP, 1 - SatTrack, 2 - SatFootprint, 3 - SatIcons
overlays.addAll(Array(4) { FolderOverlay() })
}
}
binding.mapBtnPrev.setOnClickListener { viewModel.scrollSelection(true) }
binding.mapBtnNext.setOnClickListener { viewModel.scrollSelection(false) }
setupObservers(binding)
}
private fun setupObservers(binding: FragmentMapBinding) {
viewModel.selectDefaultSatellite(arguments?.getInt("catNum"))
viewModel.stationPosLiveData.observe(viewLifecycleOwner, { renderStationPos(it, binding) })
viewModel.satPositions.observe(viewLifecycleOwner, { renderSatPositions(it, binding) })
viewModel.satTrack.observe(viewLifecycleOwner, { renderSatTrack(it, binding) })
viewModel.satFootprint.observe(viewLifecycleOwner, { renderSatFootprint(it, binding) })
viewModel.mapData.observe(viewLifecycleOwner, { renderSatData(it, binding) })
}
private fun renderStationPos(stationPos: GeoPos, binding: FragmentMapBinding) {
binding.apply {
Marker(mapView).apply {
setInfoWindow(null)
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_map_pos)
position = GeoPoint(stationPos.latitude, stationPos.longitude)
mapView.overlays[0] = this
mapView.invalidate()
}
}
}
private fun renderSatPositions(posMap: Map<Satellite, GeoPos>, binding: FragmentMapBinding) {
binding.apply {
val markers = FolderOverlay()
posMap.entries.forEach {
Marker(mapView).apply {
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)
try {
position = GeoPoint(it.value.latitude, it.value.longitude)
} catch (exception: IllegalArgumentException) {
Timber.d(exception)
}
setOnMarkerClickListener { _, _ ->
viewModel.selectSatellite(it.key)
return@setOnMarkerClickListener true
}
markers.add(this)
}
}
mapView.overlays[3] = markers
mapView.invalidate()
}
}
private fun renderSatTrack(satTrack: List<List<GeoPos>>, binding: FragmentMapBinding) {
val trackOverlay = FolderOverlay()
satTrack.forEach { track ->
val trackPoints = track.map { GeoPoint(it.latitude, it.longitude) }
Polyline().apply {
try {
setPoints(trackPoints)
outlinePaint.set(trackPaint)
trackOverlay.add(this)
} catch (exception: IllegalArgumentException) {
Timber.d(exception)
}
}
}
binding.mapView.overlays[1] = trackOverlay
}
private fun renderSatFootprint(satFootprint: List<GeoPos>, binding: FragmentMapBinding) {
val footprintPoints = satFootprint.map { GeoPoint(it.latitude, it.longitude) }
val footprintOverlay = Polygon().apply {
fillPaint.set(footprintPaint)
outlinePaint.set(footprintPaint)
try {
this.points = footprintPoints
} catch (exception: IllegalArgumentException) {
Timber.d(exception)
}
}
binding.mapView.overlays[2] = footprintOverlay
}
private fun renderSatData(mapData: MapData, binding: FragmentMapBinding) {
binding.apply {
mapDataName.text =
String.format(getString(R.string.pat_osm_idName), mapData.catNum, mapData.name)
mapDataQth.text = String.format(getString(R.string.map_qth), mapData.qthLoc)
mapDataAlt.text = String.format(getString(R.string.pat_altitude), mapData.altitude)
mapDataDst.text = String.format(getString(R.string.pat_distance), mapData.range)
mapDataVel.text = String.format(getString(R.string.pat_osm_vel), mapData.velocity)
mapDataLat.text =
String.format(getString(R.string.pat_osm_lat), mapData.osmPos.latitude)
mapDataLon.text =
String.format(getString(R.string.pat_osm_lon), mapData.osmPos.longitude)
}
binding.mapView.invalidate()
}
private fun getColorFilter(): ColorMatrixColorFilter {
val grayScaleMatrix = ColorMatrix().apply { setSaturation(0f) }
val negativeMatrix = ColorMatrix(
floatArrayOf(
-1f, 0f, 0f, 0f, 255f,
0f, -1f, 0f, 0f, 255f,
0f, 0f, -1f, 0f, 255f,
0f, 0f, 0f, 1f, 0f
)
)
negativeMatrix.preConcat(grayScaleMatrix)
return ColorMatrixColorFilter(negativeMatrix)
}
private fun getMinZoom(screenHeight: Int): Double {
return MapView.getTileSystem().getLatitudeZoom(maxLat, minLat, screenHeight)
}
}
@@ -1,191 +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.mapScreen
import androidx.lifecycle.*
import com.rtbishop.look4sat.domain.predict.Predictor
import com.rtbishop.look4sat.domain.DataRepository
import com.rtbishop.look4sat.framework.PreferencesSource
import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.domain.QthConverter
import com.rtbishop.look4sat.domain.predict.Satellite
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.*
import java.util.*
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.min
@HiltViewModel
class MapViewModel @Inject constructor(
private val dataRepository: DataRepository,
private val predictor: Predictor,
private val preferences: PreferencesSource,
) : ViewModel() {
private val stationPos = preferences.loadStationPosition()
private var dataUpdateJob: Job? = null
private var allSatList = listOf<Satellite>()
private lateinit var selectedSat: Satellite
val stationPosLiveData = liveData {
val osmLat = clipLat(stationPos.latitude)
val osmLon = clipLon(stationPos.longitude)
emit(GeoPos(osmLat, osmLon))
}
private val _satTrack = MutableLiveData<List<List<GeoPos>>>()
val satTrack: LiveData<List<List<GeoPos>>> = _satTrack
private val _satFootprint = MutableLiveData<List<GeoPos>>()
val satFootprint: LiveData<List<GeoPos>> = _satFootprint
private val _satData = MutableLiveData<MapData>()
val mapData: LiveData<MapData> = this._satData
private val _satPositions = MutableLiveData<Map<Satellite, GeoPos>>()
val satPositions: LiveData<Map<Satellite, GeoPos>> = _satPositions
fun shouldUseTextLabels(): Boolean {
return preferences.shouldUseTextLabels()
}
fun scrollSelection(decrement: Boolean) {
if (allSatList.isNotEmpty()) {
val index = allSatList.indexOf(selectedSat)
if (decrement) {
if (index > 0) selectSatellite(allSatList[index - 1])
else selectSatellite(allSatList[allSatList.size - 1])
} else {
if (index < allSatList.size - 1) selectSatellite(allSatList[index + 1])
else selectSatellite(allSatList[0])
}
}
}
fun selectDefaultSatellite(catnum: Int?) {
viewModelScope.launch {
dataRepository.getSelectedSatellites().also { satellites ->
if (satellites.isNotEmpty()) {
allSatList = satellites
if (catnum == null) {
selectSatellite(satellites.first())
} else {
satellites.find { it.params.catnum == catnum }?.let { selectSatellite(it) }
}
}
}
}
}
fun selectSatellite(satellite: Satellite, updateFreq: Long = 2000) {
selectedSat = satellite
viewModelScope.launch {
dataUpdateJob?.cancelAndJoin()
dataUpdateJob = launch {
val dateNow = Date()
getSatTrack(selectedSat, stationPos, dateNow)
while (isActive) {
dateNow.time = System.currentTimeMillis()
getPositions(allSatList, stationPos, dateNow)
getSatFootprint(selectedSat, stationPos, dateNow)
getSatData(selectedSat, stationPos, dateNow)
delay(updateFreq)
}
}
}
}
private suspend fun getSatTrack(satellite: Satellite, pos: GeoPos, date: Date) {
val satTracks = mutableListOf<List<GeoPos>>()
val currentTrack = mutableListOf<GeoPos>()
val endDate = Date(date.time + (satellite.orbitalPeriod * 2.4 * 60000L).toLong())
var oldLongitude = 0.0
predictor.getSatTrack(satellite, pos, date.time, endDate.time).forEach { satPos ->
val osmLat = clipLat(Math.toDegrees(satPos.latitude))
val osmLon = clipLon(Math.toDegrees(satPos.longitude))
val currentPosition = GeoPos(osmLat, osmLon)
if (oldLongitude < -170.0 && currentPosition.longitude > 170.0) {
// adding left terminal position
currentTrack.add(GeoPos(osmLat, -180.0))
val finishedTrack = mutableListOf<GeoPos>().apply { addAll(currentTrack) }
satTracks.add(finishedTrack)
currentTrack.clear()
} else if (oldLongitude > 170.0 && currentPosition.longitude < -170.0) {
// adding right terminal position
currentTrack.add(GeoPos(osmLat, 180.0))
val finishedTrack = mutableListOf<GeoPos>().apply { addAll(currentTrack) }
satTracks.add(finishedTrack)
currentTrack.clear()
}
oldLongitude = currentPosition.longitude
currentTrack.add(currentPosition)
}
satTracks.add(currentTrack)
_satTrack.postValue(satTracks)
}
private suspend fun getPositions(satellites: List<Satellite>, pos: GeoPos, date: Date) {
val positions = mutableMapOf<Satellite, GeoPos>()
satellites.forEach { satellite ->
val satPos = predictor.getSatPos(satellite, pos, date.time)
val osmLat = clipLat(Math.toDegrees(satPos.latitude))
val osmLon = clipLon(Math.toDegrees(satPos.longitude))
positions[satellite] = GeoPos(osmLat, osmLon)
}
_satPositions.postValue(positions)
}
private suspend fun getSatFootprint(satellite: Satellite, pos: GeoPos, date: Date) {
val satPos = predictor.getSatPos(satellite, pos, date.time)
val satFootprint = satPos.getRangeCircle().map { rangePos ->
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) {
val satPos = predictor.getSatPos(satellite, pos, date.time)
val osmLat = clipLat(Math.toDegrees(satPos.latitude))
val osmLon = clipLon(Math.toDegrees(satPos.longitude))
val osmPos = GeoPos(osmLat, osmLon)
val qthLoc = QthConverter.positionToQth(osmPos.latitude, osmPos.longitude) ?: "-- --"
val satData = MapData(
satellite, satellite.params.catnum, satellite.params.name, satPos.range,
satPos.altitude, satPos.getOrbitalVelocity(), qthLoc, osmPos
)
_satData.postValue(satData)
}
private fun clipLat(latitude: Double): Double {
return clip(latitude, -85.05, 85.05)
}
private fun clipLon(longitude: Double): Double {
var result = longitude
while (result < -180.0) result += 360.0
while (result > 180.0) result -= 360.0
return clip(result, -180.0, 180.0)
}
private fun clip(currentValue: Double, minValue: Double, maxValue: Double): Double {
return min(max(currentValue, minValue), maxValue)
}
}
@@ -1,150 +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.passesScreen
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.ItemPassGeoBinding
import com.rtbishop.look4sat.databinding.ItemPassLeoBinding
import com.rtbishop.look4sat.domain.predict.SatPass
import java.text.SimpleDateFormat
import java.util.*
class PassesAdapter(private val isUTC: Boolean, private val clickListener: PassesClickListener) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val diffCallback = object : DiffUtil.ItemCallback<SatPass>() {
override fun areItemsTheSame(oldItem: SatPass, newItem: SatPass): Boolean {
return oldItem.catNum == newItem.catNum && oldItem.aosTime == newItem.aosTime
}
override fun areContentsTheSame(oldItem: SatPass, newItem: SatPass): Boolean {
return oldItem.progress == newItem.progress
}
}
private val differ = AsyncListDiffer(this, diffCallback)
interface PassesClickListener {
fun navigateToPass(satPass: SatPass)
}
fun submitList(passes: List<SatPass>) {
differ.submitList(passes)
}
override fun getItemCount() = differ.currentList.size
override fun getItemViewType(position: Int): Int {
return if (differ.currentList[position].isDeepSpace) 1
else 0
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == 0) {
SatPassLeoHolder.from(parent)
} else {
SatPassGeoHolder.from(parent)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder.itemViewType == 0) {
(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) :
RecyclerView.ViewHolder(binding.root) {
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 maxElFormat = itemView.context.getString(R.string.pass_maxEl)
private val losAzFormat = itemView.context.getString(R.string.pass_losAz)
private val startTimeFormat = itemView.context.getString(R.string.pass_startTime)
private val endTimeFormat = itemView.context.getString(R.string.pass_endTime)
private val timeZoneUTC = TimeZone.getTimeZone("UTC")
private val startFormat = SimpleDateFormat(startTimeFormat, Locale.getDefault())
private val endFormat = SimpleDateFormat(endTimeFormat, Locale.getDefault())
fun bind(satPass: SatPass, listener: PassesClickListener, shouldUseUTC: Boolean) {
binding.apply {
if (shouldUseUTC) {
startFormat.timeZone = timeZoneUTC
endFormat.timeZone = timeZoneUTC
}
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 {
listener.navigateToPass(satPass)
}
}
companion object {
fun from(parent: ViewGroup): SatPassLeoHolder {
val inflater = LayoutInflater.from(parent.context)
return SatPassLeoHolder(ItemPassLeoBinding.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))
}
}
}
}
@@ -1,137 +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.passesScreen
import android.Manifest
import android.os.Bundle
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.SimpleItemAnimator
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.databinding.FragmentPassesBinding
import com.rtbishop.look4sat.domain.predict.SatPass
import com.rtbishop.look4sat.domain.model.DataState
import com.rtbishop.look4sat.presentation.ItemDivider
import com.rtbishop.look4sat.presentation.navigateSafe
import com.rtbishop.look4sat.presentation.toTimerString
import dagger.hilt.android.AndroidEntryPoint
import java.util.*
@AndroidEntryPoint
class PassesFragment : Fragment(R.layout.fragment_passes), PassesAdapter.PassesClickListener {
private val passesViewModel: PassesViewModel by viewModels()
private val permReqContract = ActivityResultContracts.RequestMultiplePermissions()
private val locPermFine = Manifest.permission.ACCESS_FINE_LOCATION
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?) {
super.onViewCreated(view, savedInstanceState)
setupComponents(view)
}
private fun setupComponents(view: View) {
val passesAdapter = PassesAdapter(passesViewModel.shouldUseUTC(), this)
val binding = FragmentPassesBinding.bind(view).apply {
passesRecycler.apply {
setHasFixedSize(true)
adapter = passesAdapter
isVerticalScrollBarEnabled = false
layoutManager = LinearLayoutManager(context)
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
addItemDecoration(ItemDivider(R.drawable.rec_divider_dark))
}
passesRefresh.setOnClickListener { passesViewModel.forceCalculation() }
}
passesViewModel.passes.observe(viewLifecycleOwner, { passesResult ->
handleNewPasses(passesResult, passesAdapter, binding)
})
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 -> {
binding.apply {
passesTimer.text = 0L.toTimerString()
passesError.visibility = View.INVISIBLE
passesRecycler.visibility = View.INVISIBLE
passesProgress.visibility = View.VISIBLE
}
}
else -> {
binding.apply {
passesTimer.text = 0L.toTimerString()
passesProgress.visibility = View.INVISIBLE
passesRecycler.visibility = View.INVISIBLE
passesError.visibility = View.VISIBLE
}
}
}
}
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) {
if (satPass.progress < 100) {
val bundle = bundleOf("catNum" to satPass.catNum, "aosTime" to satPass.aosTime)
findNavController().navigateSafe(R.id.action_passes_to_radar, bundle)
}
}
}
@@ -1,122 +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.passesScreen
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
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.DataRepository
import com.rtbishop.look4sat.framework.PreferencesSource
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collect
import java.util.*
import javax.inject.Inject
@HiltViewModel
class PassesViewModel @Inject constructor(
private val dataRepository: DataRepository,
private val predictor: Predictor,
private val preferences: PreferencesSource
) : ViewModel() {
private val _passes = MutableLiveData<DataState<List<SatPass>>>(DataState.Loading)
private val _isFirstLaunchDone = MutableLiveData<Boolean>()
private var passesProcessing: Job? = null
val passes: LiveData<DataState<List<SatPass>>> = _passes
val isFirstLaunchDone: LiveData<Boolean> = _isFirstLaunchDone
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 {
predictor.passes.collect { passes ->
passesProcessing?.cancelAndJoin()
passesProcessing = viewModelScope.launch { tickPasses(passes) }
}
}
}
fun triggerInitialSetup() {
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() }))
delay(1000)
}
}
}
@@ -1,22 +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 com.rtbishop.look4sat.domain.predict.SatPos
data class RadarData(val satPos: SatPos, val satTrack: List<SatPos> = emptyList())
@@ -1,167 +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.os.Bundle
import android.view.View
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.SimpleItemAnimator
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.databinding.FragmentRadarBinding
import com.rtbishop.look4sat.framework.PreferencesSource
import com.rtbishop.look4sat.domain.predict.SatPass
import com.rtbishop.look4sat.domain.predict.SatPos
import com.rtbishop.look4sat.presentation.ItemDivider
import com.rtbishop.look4sat.presentation.navigateSafe
import com.rtbishop.look4sat.presentation.toTimerString
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class RadarFragment : Fragment(R.layout.fragment_radar) {
@Inject
lateinit var preferences: PreferencesSource
private val viewModel: RadarViewModel by viewModels()
private var radarView: RadarView? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
FragmentRadarBinding.bind(view).apply {
val transAdapter = TransmittersAdapter()
radarRecycler.apply {
setHasFixedSize(true)
adapter = transAdapter
isVerticalScrollBarEnabled = false
layoutManager = LinearLayoutManager(context)
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
addItemDecoration(ItemDivider(R.drawable.rec_divider_dark))
}
setupObservers(transAdapter, this)
}
}
override fun onResume() {
super.onResume()
viewModel.enableSensor()
}
override fun onPause() {
super.onPause()
viewModel.disableSensor()
}
private fun setupObservers(
transmittersAdapter: TransmittersAdapter,
binding: FragmentRadarBinding
) {
val catNum = requireArguments().getInt("catNum")
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) {
val timeNow = System.currentTimeMillis()
val polarAz = getString(R.string.pat_azimuth)
val polarEl = getString(R.string.pat_elevation)
val polarRng = getString(R.string.pat_distance)
val polarAlt = getString(R.string.pat_altitude)
binding.radarAz.text = String.format(polarAz, Math.toDegrees(satPos.azimuth))
binding.radarEl.text = String.format(polarEl, Math.toDegrees(satPos.elevation))
binding.radarDst.text = String.format(polarRng, satPos.range)
binding.radarAlt.text = String.format(polarAlt, satPos.altitude)
binding.radarName.text = satPass.name
if (!satPass.isDeepSpace) {
if (timeNow < satPass.aosTime) {
val millisBeforeStart = satPass.aosTime.minus(timeNow)
binding.radarTimer.text = millisBeforeStart.toTimerString()
} else {
val millisBeforeEnd = satPass.losTime.minus(timeNow)
binding.radarTimer.text = millisBeforeEnd.toTimerString()
if (timeNow > satPass.losTime) {
binding.radarTimer.text = 0L.toTimerString()
findNavController().navigateSafe(R.id.action_radar_to_passes)
}
}
} else {
binding.radarTimer.text = 0L.toTimerString()
}
}
}
@@ -1,265 +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.content.Context
import android.graphics.*
import android.view.View
import androidx.core.content.ContextCompat
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.domain.predict.SatPos
import kotlin.math.cos
import kotlin.math.min
import kotlin.math.sin
class RadarView(context: Context) : View(context) {
private val defaultColor = ContextCompat.getColor(context, R.color.themeLight)
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 var position: SatPos? = null
private var positions: List<SatPos> = emptyList()
private var radarColor = ContextCompat.getColor(context, R.color.greyLight)
private var radarCircleNum = 3
private var radarPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = radarColor
style = Paint.Style.STROKE
strokeWidth = strokeSize
}
private var shouldShowSweep = true
private var sweepColor = defaultColor
private var sweepDegrees = 0f
private var sweepSpeed = 30.0f
private var sweepPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = sweepColor
}
private var shouldShowBeacons = true
private var beaconColor = defaultColor
private var beaconSize = scale * 7f
private var beaconPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = beaconColor
style = Paint.Style.FILL
}
private var radarTextSize = scale * 16f
private var radarTextColor = defaultColor
private var radarTextPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = radarTextColor
textSize = radarTextSize
}
private var isTrackCreated = false
private val trackPath: Path = Path()
private var trackPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.RED
style = Paint.Style.STROKE
strokeWidth = strokeSize
}
private val arrowPath = Path()
private var arrowPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = ContextCompat.getColor(context, R.color.themeLight)
style = Paint.Style.FILL
strokeWidth = strokeSize
}
private var shouldShowAim = true
private var aimColor = Color.RED
private var aimPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = aimColor
style = Paint.Style.STROKE
strokeWidth = strokeSize
}
private var shouldRotateRadar = true
private var azimuth: Float = 0f
private var pitch: Float = 0f
private var roll: Float = 0f
fun setScanning(isScanning: Boolean) {
shouldShowSweep = isScanning
}
fun setPosition(position: SatPos) {
this.position = position
}
fun setPositions(positions: List<SatPos>) {
this.positions = positions
}
fun setShowAim(showAim: Boolean) {
shouldShowAim = showAim
}
fun setOrientation(azimuth: Float, pitch: Float, roll: Float) {
this.azimuth = azimuth
this.pitch = pitch
this.roll = roll
if (shouldRotateRadar) {
this.rotation = -azimuth
}
}
override fun onDraw(canvas: Canvas) {
val radarWidth = width - paddingLeft - paddingRight
val radarHeight = height - paddingTop - paddingBottom
val radarRadius = min(width, height) * 0.49f
val cx = paddingLeft + radarWidth / 2f
val cy = paddingTop + radarHeight / 2f
if (positions.isNotEmpty() && !isTrackCreated) {
createPassTrajectory()
createPassTrajectoryArrow()
isTrackCreated = true
}
canvas.drawColor(ContextCompat.getColor(context, R.color.greyDark))
drawRadarCircle(canvas, cx, cy, radarRadius)
drawRadarCross(canvas, cx, cy, radarRadius)
drawRadarText(canvas, cx, radarRadius)
canvas.translate(cx, cy)
if (positions.isNotEmpty()) {
canvas.drawPath(trackPath, trackPaint)
canvas.drawPath(trackPath, arrowPaint)
}
if (shouldShowBeacons) {
drawSatellite(canvas)
}
if (shouldShowAim) {
drawCrosshair(canvas, azimuth, pitch)
}
if (shouldShowSweep) {
canvas.translate(-cx, -cy)
drawRadarSweep(canvas, cx, cy, radarRadius)
sweepDegrees = (sweepDegrees + 360 / sweepSpeed / 60) % 360
}
invalidate()
}
private fun drawRadarCircle(canvas: Canvas, cx: Float, cy: Float, radius: Float) {
for (i in 0 until radarCircleNum) {
canvas.drawCircle(cx, cy, radius - radius / radarCircleNum * i, radarPaint)
}
}
private fun drawRadarCross(canvas: Canvas, cx: Float, cy: Float, radius: Float) {
canvas.drawLine(cx - radius, cy, cx + radius, cy, radarPaint)
canvas.drawLine(cx, cy - radius, cx, cy + radius, radarPaint)
}
private fun drawRadarText(canvas: Canvas, cx: Float, radius: Float) {
for (i in 0 until radarCircleNum) {
val degText = " ${(90 / radarCircleNum) * (radarCircleNum - i)}°"
canvas.drawText(degText, cx, radius - radius / radarCircleNum * i, radarTextPaint)
}
}
private fun drawSatellite(canvas: Canvas) {
position?.let { satPos ->
if (satPos.elevation > 0) {
val satX = sph2CartX(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
val satY = sph2CartY(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
canvas.drawCircle(satX, -satY, beaconSize, beaconPaint)
}
}
}
private fun drawCrosshair(canvas: Canvas, azimuth: Float, pitch: Float) {
val azimuthRad = Math.toRadians(azimuth.toDouble())
val tmpElevation = Math.toRadians(pitch.toDouble())
val elevationRad = if (tmpElevation > 0.0) 0.0 else tmpElevation
val crossX = sph2CartX(azimuthRad, -elevationRad, radarRadius.toDouble())
val crossY = sph2CartY(azimuthRad, -elevationRad, radarRadius.toDouble())
canvas.drawLine(crossX - radarTextSize, -crossY, crossX + radarTextSize, -crossY, aimPaint)
canvas.drawLine(crossX, -crossY - radarTextSize, crossX, -crossY + radarTextSize, aimPaint)
canvas.drawCircle(crossX, -crossY, radarTextSize / 2, aimPaint)
}
private fun sph2CartX(azimuth: Double, elevation: Double, r: Double): Float {
val radius = r * (piDiv2 - elevation) / piDiv2
return (radius * cos(piDiv2 - azimuth)).toFloat()
}
private fun sph2CartY(azimuth: Double, elevation: Double, r: Double): Float {
val radius = r * (piDiv2 - elevation) / piDiv2
return (radius * sin(piDiv2 - azimuth)).toFloat()
}
private fun createPassTrajectory() {
positions.forEachIndexed { index, satPos ->
val passX = sph2CartX(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
val passY = sph2CartY(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
if (index == 0) {
trackPath.moveTo(passX, -passY)
} else {
trackPath.lineTo(passX, -passY)
}
}
}
private fun createPassTrajectoryArrow() {
val radius = beaconSize
val sides = 3
val angle = 2.0 * Math.PI / sides
arrowPath.moveTo((radius * cos(angle)).toFloat(), (radius * sin(angle)).toFloat())
for (i in 1 until sides) {
val x = (radius * cos(angle - angle * i)).toFloat()
val y = (radius * sin(angle - angle * i)).toFloat()
arrowPath.lineTo(x, y)
}
arrowPath.close()
val trackLength = PathMeasure(trackPath, false).length
val quarter = trackLength / 4f
val center = trackLength / 2f
val effect = PathDashPathEffect(arrowPath, center, quarter, PathDashPathEffect.Style.ROTATE)
arrowPaint.pathEffect = effect
}
private fun drawRadarSweep(canvas: Canvas, cx: Float, cy: Float, radius: Float) {
val sweepGradient = SweepGradient(
cx,
cy,
intArrayOf(
Color.TRANSPARENT,
changeAlpha(sweepColor, 0),
changeAlpha(sweepColor, 164),
changeAlpha(sweepColor, 255),
changeAlpha(sweepColor, 255)
),
floatArrayOf(0.0f, 0.55f, 0.996f, 0.999f, 1f)
)
sweepPaint.shader = sweepGradient
canvas.rotate(-90 + sweepDegrees, cx, cy)
canvas.drawCircle(cx, cy, radius, sweepPaint)
}
private fun changeAlpha(color: Int, alpha: Int): Int {
val red = Color.red(color)
val green = Color.green(color)
val blue = Color.blue(color)
return Color.argb(alpha, red, green, blue)
}
}
@@ -1,120 +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 androidx.lifecycle.*
import com.rtbishop.look4sat.domain.DataReporter
import com.rtbishop.look4sat.domain.DataRepository
import com.rtbishop.look4sat.domain.predict.Predictor
import com.rtbishop.look4sat.domain.predict.SatPass
import com.rtbishop.look4sat.domain.predict.SatPos
import com.rtbishop.look4sat.domain.model.Transmitter
import com.rtbishop.look4sat.framework.OrientationSource
import com.rtbishop.look4sat.framework.PreferencesSource
import com.rtbishop.look4sat.presentation.round
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import java.util.*
import javax.inject.Inject
@HiltViewModel
class RadarViewModel @Inject constructor(
private val orientationSource: OrientationSource,
private val preferences: PreferencesSource,
private val predictor: Predictor,
private val dataRepository: DataRepository,
private val dataReporter: DataReporter
) : ViewModel(), OrientationSource.OrientationListener {
private val stationPos = preferences.loadStationPosition()
private val _passData = MutableLiveData<RadarData>()
private val _transmitters = MutableLiveData<List<Transmitter>>()
private val _orientation = MutableLiveData<Triple<Float, Float, Float>>()
val radarData: LiveData<RadarData> = _passData
val transmitters: LiveData<List<Transmitter>> = _transmitters
val orientation: LiveData<Triple<Float, Float, Float>> = _orientation
fun getPass(catNum: Int, aosTime: Long) = liveData {
predictor.passes.collect { passes ->
val pass = passes.find { it.catNum == catNum && it.aosTime == aosTime }
pass?.let { satPass ->
emit(satPass)
sendPassData(satPass)
processTransmitters(satPass)
}
}
}
fun enableSensor() {
if (preferences.shouldUseCompass()) orientationSource.startListening(this)
}
fun disableSensor() {
if (preferences.shouldUseCompass()) orientationSource.stopListening()
}
override fun onOrientationChanged(azimuth: Float, pitch: Float, roll: Float) {
_orientation.value = Triple(azimuth + preferences.getMagDeclination(), pitch, roll)
}
private fun sendPassData(satPass: SatPass) {
viewModelScope.launch {
var satTrack: List<SatPos> = emptyList()
if (!satPass.isDeepSpace) {
val startDate = satPass.aosTime
val endDate = satPass.losTime
satTrack = predictor.getSatTrack(satPass.satellite, stationPos, startDate, endDate)
}
while (isActive) {
val satPos = predictor.getSatPos(satPass.satellite, stationPos, Date().time)
if (preferences.isRotatorEnabled()) {
val server = preferences.getRotatorServer().first
val port = preferences.getRotatorServer().second
val azimuth = Math.toDegrees(satPos.azimuth).round(1)
val elevation = Math.toDegrees(satPos.elevation).round(1)
dataReporter.reportRotation(server, port, azimuth, elevation)
}
_passData.postValue(RadarData(satPos, satTrack))
delay(1000)
}
}
}
private fun processTransmitters(satPass: SatPass) {
viewModelScope.launch {
val transmitters = dataRepository.getTransmitters(satPass.catNum)
while (isActive) {
val satPos = predictor.getSatPos(satPass.satellite, stationPos, Date().time)
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)
}
}
}
}
@@ -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))
}
}
}
}
@@ -1,124 +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.settingsScreen
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.text.InputType
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.preference.EditTextPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.google.android.material.snackbar.Snackbar
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.framework.PreferencesSource
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class SettingsFragment : PreferenceFragmentCompat() {
@Inject
lateinit var preferences: PreferencesSource
private val permReqContract = ActivityResultContracts.RequestMultiplePermissions()
private val locPermFine = Manifest.permission.ACCESS_FINE_LOCATION
private val locPermCoarse = Manifest.permission.ACCESS_COARSE_LOCATION
private val locPermReq = registerForActivityResult(permReqContract) { permissions ->
when {
permissions[locPermFine] == true -> updatePositionFromGPS()
permissions[locPermCoarse] == true -> updatePositionFromGPS()
else -> showSnack(getString(R.string.pref_pos_gps_error))
}
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preference, rootKey)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
findPreference<Preference>(PreferencesSource.keyPositionGPS)?.apply {
setOnPreferenceClickListener {
updatePositionFromGPS()
return@setOnPreferenceClickListener true
}
}
findPreference<Preference>(PreferencesSource.keyPositionQTH)?.apply {
setOnPreferenceChangeListener { _, newValue ->
updatePositionFromQth(newValue.toString())
}
}
findPreference<EditTextPreference>(PreferencesSource.keyRotatorAddress)?.apply {
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 {
setOnBindEditTextListener { it.inputType = InputType.TYPE_CLASS_NUMBER }
setOnPreferenceChangeListener { _, newValue ->
val portValue = newValue.toString()
if (portValue.isNotEmpty() && portValue.toInt() in 1024..65535) {
return@setOnPreferenceChangeListener true
} else {
showSnack(getString(R.string.tracking_rotator_port_invalid))
return@setOnPreferenceChangeListener false
}
}
}
}
private fun updatePositionFromQth(qthString: String): Boolean {
return if (preferences.updatePositionFromQTH(qthString)) {
showSnack(getString(R.string.pref_pos_success))
true
} else {
showSnack(getString(R.string.pref_pos_qth_error))
false
}
}
private fun updatePositionFromGPS() {
val locPermString = Manifest.permission.ACCESS_FINE_LOCATION
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) {
Snackbar.make(requireView(), message, Snackbar.LENGTH_SHORT).show()
}
}
@@ -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() }
}
})
}
}
@@ -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

-9
View File
@@ -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,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
</vector>
-9
View File
@@ -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>
-11
View File
@@ -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>
-23
View File
@@ -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>
-11
View File
@@ -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,6 +0,0 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:endColor="@color/greyDark"
android:startColor="@color/greyDark" />
</shape>
@@ -1,22 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="108dp"
android:height="108dp"
android:viewportWidth="271.2439"
android:viewportHeight="271.2439">
<group
android:translateX="1.3562195"
android:translateY="1.3562195">
<group
android:translateX="70.26573"
android:translateY="70.26573">
<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" />
</group>
</group>
</vector>
-9
View File
@@ -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,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>
-9
View File
@@ -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>
@@ -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>
-9
View File
@@ -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,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>
@@ -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="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>
@@ -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>
-9
View File
@@ -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,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z" />
</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>
-9
View File
@@ -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,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>
@@ -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="128"
android:viewportHeight="128">
<path
android:fillColor="@color/themeLight"
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"
tools:ignore="VectorPath" />
<path
android:fillColor="@color/themeLight"
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="@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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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/greySurface" />
<solid android:color="@color/greySurface" />
<corners android:radius="8dp" />
</shape>
-31
View File
@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main_root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/main_nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@+id/main_nav_bottom"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/main_nav_bottom"
android:layout_width="match_parent"
android:layout_height="48dp"
app:itemBackground="@color/greySurface"
app:labelVisibilityMode="labeled"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="@menu/nav_menu" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,86 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/sources_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/greyDark"
android:padding="4dp">
<TextView
android:id="@+id/sources_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="6dp"
android:text="@string/sources_title"
android:textSize="16sp"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/sources_warning"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:gravity="center"
android:text="@string/sources_https"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="@+id/sources_title"
app:layout_constraintTop_toBottomOf="@+id/sources_title" />
<ImageButton
android:id="@+id/sources_btn_add"
android:layout_width="48dp"
android:layout_height="48dp"
android:backgroundTint="@color/greySurface"
android:contentDescription="@string/placeholder"
android:src="@drawable/ic_add"
app:layout_constraintBottom_toBottomOf="@+id/sources_warning"
app:layout_constraintDimensionRatio="h,1:1"
app:layout_constraintEnd_toEndOf="@+id/sources_recycler"
app:layout_constraintTop_toTopOf="@+id/sources_title" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/sources_recycler"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sources_warning">
</androidx.recyclerview.widget.RecyclerView>
<Button
android:id="@+id/sources_btn_neg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:backgroundTint="@color/greySurface"
android:text="@string/btn_cancel"
android:textAllCaps="false"
android:textColor="@color/themeLight"
app:layout_constraintBaseline_toBaselineOf="@+id/sources_btn_pos"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/sources_btn_pos"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="4dp"
android:backgroundTint="@color/greySurface"
android:text="@string/btn_update"
android:textAllCaps="false"
android:textColor="@color/themeLight"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sources_recycler" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,105 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/entries_root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.cardview.widget.CardView
android:id="@+id/entries_bar"
style="@style/TopBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/entries_import_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:elevation="4dp"
android:text="@string/entries_update"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="@+id/entries_import_web"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/entries_import_web" />
<ImageButton
android:id="@+id/entries_import_web"
android:layout_width="64dp"
android:layout_height="48dp"
android:backgroundTint="@color/greyDark"
android:contentDescription="@string/placeholder"
android:elevation="4dp"
android:src="@drawable/ic_update_web"
app:layout_constraintBottom_toBottomOf="@+id/entries_import_file"
app:layout_constraintEnd_toStartOf="@+id/entries_import_file"
app:layout_constraintTop_toTopOf="@+id/entries_import_file"
tools:ignore="DuplicateSpeakableTextCheck" />
<ImageButton
android:id="@+id/entries_import_file"
android:layout_width="64dp"
android:layout_height="48dp"
android:layout_marginEnd="4dp"
android:backgroundTint="@color/greyDark"
android:contentDescription="@string/placeholder"
android:elevation="4dp"
android:src="@drawable/ic_update_file"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<SearchView
android:id="@+id/entries_search_bar"
android:layout_width="0dp"
android:layout_height="0dp"
android:iconifiedByDefault="false"
android:queryHint="@string/entries_search_hint"
app:layout_constraintBottom_toBottomOf="@+id/entries_select_mode"
app:layout_constraintEnd_toStartOf="@+id/entries_select_mode"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/entries_import_web" />
<ImageButton
android:id="@+id/entries_select_mode"
android:layout_width="64dp"
android:layout_height="48dp"
android:backgroundTint="@color/greySurface"
android:contentDescription="@string/placeholder"
android:src="@drawable/ic_satellite"
app:layout_constraintBottom_toBottomOf="@+id/entries_select_all"
app:layout_constraintEnd_toStartOf="@+id/entries_select_all"
app:layout_constraintTop_toTopOf="@+id/entries_select_all" />
<ImageButton
android:id="@+id/entries_select_all"
android:layout_width="64dp"
android:layout_height="48dp"
android:backgroundTint="@color/greySurface"
android:contentDescription="@string/placeholder"
android:src="@drawable/ic_select_all"
app:layout_constraintEnd_toEndOf="@+id/entries_import_file"
app:layout_constraintTop_toBottomOf="@+id/entries_import_file" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/entries_recycler"
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/entries_search_bar" />
<ProgressBar
android:id="@+id/entries_progress"
style="@android:style/Widget.DeviceDefault.Light.ProgressBar.Large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/entries_bar" />
</androidx.constraintlayout.widget.ConstraintLayout>
-132
View File
@@ -1,132 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/info_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".presentation.infoScreen.InfoFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/info_root"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/info_title_img"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:contentDescription="@string/placeholder"
android:src="@drawable/ic_nav_entries"
app:layout_constraintBottom_toBottomOf="@+id/info_version"
app:layout_constraintDimensionRatio="h,1:1"
app:layout_constraintEnd_toStartOf="@+id/info_title"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/info_title" />
<TextView
android:id="@+id/info_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:text="@string/app_name"
android:textColor="@color/themeLight"
android:textSize="48sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/info_title_img"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/info_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/info_version"
android:textColor="@color/greyLight"
android:textSize="22sp"
app:layout_constraintStart_toStartOf="@+id/info_title"
app:layout_constraintTop_toBottomOf="@+id/info_title" />
<TextView
android:id="@+id/info_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/info_subtitle"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="@+id/info_title"
app:layout_constraintStart_toStartOf="@+id/info_title_img"
app:layout_constraintTop_toBottomOf="@+id/info_version" />
<Button
android:id="@+id/info_btn_github"
style="@style/InfoButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:drawableStart="@drawable/ic_github"
android:text="@string/info_github"
app:layout_constraintEnd_toStartOf="@+id/info_btn_donate"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/info_subtitle" />
<Button
android:id="@+id/info_btn_fdroid"
style="@style/InfoButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:drawableStart="@drawable/ic_fdroid"
android:text="@string/info_fdroid"
app:layout_constraintBaseline_toBaselineOf="@+id/info_btn_donate"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/info_btn_donate" />
<Button
android:id="@+id/info_btn_donate"
style="@style/InfoButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableStart="@drawable/ic_bmc"
android:text="@string/info_support"
app:layout_constraintBaseline_toBaselineOf="@+id/info_btn_github"
app:layout_constraintEnd_toStartOf="@+id/info_btn_fdroid"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/info_btn_github" />
<TextView
android:id="@+id/info_thanks"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:gravity="center"
android:text="@string/info_thanks"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/info_btn_donate" />
<TextView
android:id="@+id/info_warranty"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:gravity="center"
android:text="@string/info_warranty"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/info_thanks"
app:layout_constraintStart_toStartOf="@+id/info_thanks"
app:layout_constraintTop_toBottomOf="@+id/info_thanks" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
-143
View File
@@ -1,143 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/map_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true">
<org.osmdroid.views.MapView
android:id="@+id/map_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</org.osmdroid.views.MapView>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/map_data_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="4dp"
android:background="@drawable/map_info_rounded"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/map_data_lat"
style="@style/MapInfoText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:text="@string/pat_osm_lat"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/map_data_lon"
style="@style/MapInfoText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="@string/pat_osm_lon"
app:layout_constraintStart_toStartOf="@+id/map_data_lat"
app:layout_constraintTop_toBottomOf="@+id/map_data_lat" />
<TextView
android:id="@+id/map_data_qth"
style="@style/MapInfoText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="@string/map_qth"
app:layout_constraintStart_toStartOf="@+id/map_data_lon"
app:layout_constraintTop_toBottomOf="@+id/map_data_lon" />
<TextView
android:id="@+id/map_data_alt"
style="@style/MapInfoText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/pat_altitude"
app:layout_constraintBaseline_toBaselineOf="@+id/map_data_lat"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/map_data_dst"
style="@style/MapInfoText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pat_distance"
app:layout_constraintBaseline_toBaselineOf="@+id/map_data_lon"
app:layout_constraintEnd_toEndOf="@+id/map_data_alt" />
<TextView
android:id="@+id/map_data_vel"
style="@style/MapInfoText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pat_osm_vel"
app:layout_constraintBaseline_toBaselineOf="@+id/map_data_qth"
app:layout_constraintEnd_toEndOf="@+id/map_data_dst" />
<TextView
android:id="@+id/map_data_name"
style="@style/MapInfoText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:text="@string/pat_osm_idName"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/map_data_qth" />
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageButton
android:id="@+id/map_btn_prev"
android:layout_width="52dp"
android:layout_height="52dp"
android:layout_marginStart="@dimen/fab_padding"
android:layout_marginBottom="@dimen/fab_padding"
android:backgroundTint="@color/greySurface"
android:contentDescription="@string/placeholder"
android:rotation="180"
android:src="@drawable/ic_next"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:ignore="DuplicateSpeakableTextCheck" />
<ImageButton
android:id="@+id/map_btn_next"
android:layout_width="52dp"
android:layout_height="52dp"
android:layout_marginEnd="@dimen/fab_padding"
android:layout_marginBottom="@dimen/fab_padding"
android:backgroundTint="@color/greySurface"
android:contentDescription="@string/placeholder"
android:src="@drawable/ic_next"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/map_copyright"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/map_copyright"
android:textColor="@color/themeLight"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,75 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/passes_root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.cardview.widget.CardView
android:id="@+id/passes_bar"
style="@style/TopBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/passes_timer"
style="@style/TimerText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="4dp"
android:text="@string/timer_zero"
android:textSize="40sp"
app:layout_constraintBottom_toBottomOf="@+id/passes_bar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/passes_bar"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/passes_refresh"
android:layout_width="64dp"
android:layout_height="48dp"
android:layout_marginEnd="4dp"
android:backgroundTint="@color/greyDark"
android:contentDescription="@string/placeholder"
android:elevation="4dp"
android:src="@drawable/ic_refresh"
app:layout_constraintEnd_toEndOf="@+id/passes_bar"
app:layout_constraintTop_toTopOf="@+id/passes_bar" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/passes_recycler"
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/passes_bar">
</androidx.recyclerview.widget.RecyclerView>
<ProgressBar
android:id="@+id/passes_progress"
style="@android:style/Widget.DeviceDefault.Light.ProgressBar.Large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@+id/passes_recycler"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/passes_bar" />
<TextView
android:id="@+id/passes_error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/passes_error"
android:textSize="20sp"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/passes_bar" />
</androidx.constraintlayout.widget.ConstraintLayout>
-144
View File
@@ -1,144 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/radar_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true">
<androidx.cardview.widget.CardView
android:id="@+id/radar_bar"
style="@style/TopBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/radar_timer"
style="@style/TimerText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="4dp"
android:text="@string/timer_zero"
android:textSize="40sp"
app:layout_constraintBottom_toBottomOf="@+id/radar_bar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/radar_bar"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:id="@+id/radar_frame"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/radar_bar" />
<TextView
android:id="@+id/radar_az"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:text="@string/pat_azimuth"
android:textSize="15sp"
app:layout_constraintStart_toStartOf="@+id/radar_frame"
app:layout_constraintTop_toTopOf="@+id/radar_frame" />
<TextView
android:id="@+id/radar_el"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:text="@string/pat_elevation"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="@+id/radar_frame"
app:layout_constraintTop_toTopOf="@+id/radar_frame" />
<TextView
android:id="@+id/radar_alt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:text="@string/pat_altitude"
android:textSize="15sp"
app:layout_constraintBottom_toBottomOf="@+id/radar_frame"
app:layout_constraintStart_toStartOf="@+id/radar_frame" />
<TextView
android:id="@+id/radar_dst"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:text="@string/pat_distance"
android:textSize="15sp"
app:layout_constraintBottom_toBottomOf="@+id/radar_frame"
app:layout_constraintEnd_toEndOf="@+id/radar_frame" />
<ImageButton
android:id="@+id/radar_notify_button"
android:layout_width="64dp"
android:layout_height="48dp"
android:layout_marginStart="4dp"
android:backgroundTint="@color/greyDark"
android:clickable="false"
android:contentDescription="@string/placeholder"
android:elevation="4dp"
android:src="@drawable/ic_notification"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@+id/radar_bar"
app:layout_constraintStart_toStartOf="@+id/radar_bar"
app:layout_constraintTop_toTopOf="@+id/radar_bar"
tools:ignore="DuplicateSpeakableTextCheck" />
<ImageButton
android:id="@+id/radar_to_map_button"
android:layout_width="64dp"
android:layout_height="48dp"
android:layout_marginEnd="4dp"
android:backgroundTint="@color/greyDark"
android:contentDescription="@string/placeholder"
android:elevation="4dp"
android:src="@drawable/ic_nav_map"
app:layout_constraintBottom_toBottomOf="@+id/radar_bar"
app:layout_constraintEnd_toEndOf="@+id/radar_bar"
app:layout_constraintTop_toTopOf="@+id/radar_bar"
tools:ignore="DuplicateSpeakableTextCheck" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/radar_recycler"
android:layout_width="0dp"
android:layout_height="0dp"
android:overScrollMode="never"
android:padding="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/radar_name" />
<TextView
android:id="@+id/radar_recycler_msg"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/trans_no_data"
android:textSize="15sp"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/radar_recycler" />
<TextView
android:id="@+id/radar_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:gravity="center"
android:text="@string/placeholder"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/radar_frame" />
</androidx.constraintlayout.widget.ConstraintLayout>
-11
View File
@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/entry_checkbox"
android:layout_width="match_parent"
android:layout_height="36dp"
android:background="@null"
android:ellipsize="end"
android:lines="1"
android:text="@string/placeholder"
android:textColor="@color/greyLight"
android:textSize="16sp" />
-87
View File
@@ -1,87 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/pass_geo_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/greyDark"
app:cardCornerRadius="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/pass_geo_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:selectableItemBackground">
<ImageView
android:id="@+id/pass_geo_bg"
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0.64"
android:contentDescription="@string/placeholder"
android:scaleType="centerCrop"
android:src="@drawable/card_pass_bg"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/pass_geo_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginTop="4dp"
android:text="@string/pass_satName"
android:textColor="@color/themeLight"
app:layout_constraintEnd_toStartOf="@+id/pass_geo_id"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/pass_geo_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:text="@string/pass_satId"
android:textColor="@color/themeLight"
app:layout_constraintBaseline_toBaselineOf="@+id/pass_geo_name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/pass_geo_name" />
<TextView
android:id="@+id/pass_geo_az"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginBottom="4dp"
android:text="@string/pat_azimuth"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/pass_geo_el"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="@+id/pass_geo_name"
app:layout_constraintTop_toBottomOf="@+id/pass_geo_name" />
<TextView
android:id="@+id/pass_geo_alt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pat_altitude"
app:layout_constraintBaseline_toBaselineOf="@+id/pass_geo_az"
app:layout_constraintEnd_toEndOf="@+id/pass_geo_el"
app:layout_constraintStart_toStartOf="@+id/pass_geo_az" />
<TextView
android:id="@+id/pass_geo_el"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pat_elevation"
app:layout_constraintBaseline_toBaselineOf="@+id/pass_geo_az"
app:layout_constraintEnd_toEndOf="@+id/pass_geo_id"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/pass_geo_az" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
-122
View File
@@ -1,122 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/pass_leo_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/greyDark"
app:cardCornerRadius="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/pass_leo_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:selectableItemBackground">
<ImageView
android:id="@+id/pass_leo_bg"
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0.64"
android:contentDescription="@string/placeholder"
android:scaleType="centerCrop"
android:src="@drawable/card_pass_bg"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/pass_leo_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginTop="6dp"
android:text="@string/pass_satName"
android:textColor="@color/themeLight"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/pass_leo_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:text="@string/pass_satId"
android:textColor="@color/themeLight"
app:layout_constraintBaseline_toBaselineOf="@+id/pass_leo_name"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/pass_leo_aos"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="@string/pass_aosAz"
app:layout_constraintEnd_toStartOf="@+id/pass_leo_los"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="@+id/pass_leo_name"
app:layout_constraintTop_toBottomOf="@+id/pass_leo_name" />
<TextView
android:id="@+id/pass_leo_elev"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pass_maxEl"
app:layout_constraintBaseline_toBaselineOf="@+id/pass_leo_start"
app:layout_constraintEnd_toEndOf="@+id/pass_leo_end"
app:layout_constraintStart_toStartOf="@+id/pass_leo_start" />
<TextView
android:id="@+id/pass_leo_los"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pass_losAz"
app:layout_constraintBaseline_toBaselineOf="@+id/pass_leo_aos"
app:layout_constraintEnd_toEndOf="@+id/pass_leo_id"
app:layout_constraintStart_toEndOf="@+id/pass_leo_aos" />
<TextView
android:id="@+id/pass_leo_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="@string/pass_startTime"
app:layout_constraintEnd_toStartOf="@+id/pass_leo_end"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="@+id/pass_leo_aos"
app:layout_constraintTop_toBottomOf="@+id/pass_leo_aos" />
<TextView
android:id="@+id/pass_leo_tca"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pass_tcaAz"
app:layout_constraintBaseline_toBaselineOf="@+id/pass_leo_aos"
app:layout_constraintEnd_toEndOf="@+id/pass_leo_los"
app:layout_constraintStart_toStartOf="@+id/pass_leo_aos" />
<TextView
android:id="@+id/pass_leo_end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pass_endTime"
app:layout_constraintBaseline_toBaselineOf="@+id/pass_leo_start"
app:layout_constraintEnd_toEndOf="@+id/pass_leo_los"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/pass_leo_start" />
<ProgressBar
android:id="@+id/pass_leo_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/pass_leo_end"
app:layout_constraintStart_toStartOf="@+id/pass_leo_start"
app:layout_constraintTop_toBottomOf="@+id/pass_leo_elev" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
-40
View File
@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/source_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="4dp"
android:paddingBottom="4dp">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/source_input"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/sources_hint"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/source_btn"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/source_url"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inputType="textUri" />
</com.google.android.material.textfield.TextInputLayout>
<ImageButton
android:id="@+id/source_btn"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:backgroundTint="@color/greyDark"
android:contentDescription="@string/placeholder"
android:src="@drawable/ic_map_sat"
app:layout_constraintBottom_toBottomOf="@+id/source_input"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/source_input" />
</androidx.constraintlayout.widget.ConstraintLayout>
-111
View File
@@ -1,111 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/trans_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="@color/greySurface"
app:cardCornerRadius="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/trans_root"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/trans_guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<TextView
android:id="@+id/trans_desc"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="2dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="2dp"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:text="@string/trans_description"
android:textSize="15sp"
app:layout_constraintEnd_toStartOf="@+id/trans_uplink_img"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toEndOf="@+id/trans_downlink_img"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/trans_downlink_img"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginStart="4dp"
android:contentDescription="@string/placeholder"
android:rotation="90"
app:layout_constraintBottom_toBottomOf="@+id/trans_desc"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/trans_desc"
app:srcCompat="@drawable/ic_arrow" />
<TextView
android:id="@+id/trans_downlink"
style="@style/TimerText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/trans_link_low"
android:textSize="24sp"
app:layout_constraintEnd_toStartOf="@+id/trans_guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/trans_desc" />
<ImageView
android:id="@+id/trans_uplink_img"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginEnd="4dp"
android:contentDescription="@string/placeholder"
android:rotation="270"
app:layout_constraintBottom_toBottomOf="@+id/trans_desc"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/trans_desc"
app:srcCompat="@drawable/ic_arrow" />
<TextView
android:id="@+id/trans_uplink"
style="@style/TimerText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/trans_link_low"
android:textSize="24sp"
app:layout_constraintBaseline_toBaselineOf="@+id/trans_downlink"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/trans_guideline" />
<TextView
android:id="@+id/trans_mode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginBottom="4dp"
android:text="@string/trans_mode"
android:textSize="15sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/trans_inverted"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/trans_downlink" />
<TextView
android:id="@+id/trans_inverted"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:text="@string/trans_inverted"
android:textSize="15sp"
app:layout_constraintBaseline_toBaselineOf="@+id/trans_mode"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/trans_mode" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
-23
View File
@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/nav_entries"
android:icon="@drawable/ic_nav_entries"
android:title="@string/menu_entries" />
<item
android:id="@+id/nav_passes"
android:icon="@drawable/ic_nav_passes"
android:title="@string/menu_passes" />
<item
android:id="@+id/nav_map"
android:icon="@drawable/ic_nav_map"
android:title="@string/menu_map" />
<item
android:id="@+id/nav_settings"
android:icon="@drawable/ic_nav_settings"
android:title="@string/menu_prefs" />
<item
android:id="@+id/nav_info"
android:icon="@drawable/ic_nav_info"
android:title="@string/menu_about" />
</menu>
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Loaded 100 of 5775 files, more files were not shown because too many files have changed in this diff. Show more