Compare commits

...
Author SHA1 Message Date
atsunatsu cd3eded77c chore: bump version to 4.4.6-ba7opf.9.11 2026-09-08 06:03:35 +08:00
atsunatsu 7d7f8f1eda feat(map): expanded QSO list — drop ordinal prefix, add per-QSO date
Expanded per-callsign rows previously read '2 · FO-29 · CW · V/U · 11:30Z'
(ordinal + summary, no date). Now each expanded line is
'yyyy-MM-dd · FO-29 · CW · V/U · 11:30Z' — the first QSO's date already
lives in the header row, so only subsequent QSOs carry a date prefix.
2026-09-08 05:57:27 +08:00
atsunatsu 11b46c6710 feat(map): tap worked cells from GRID_ZOOM_SUB (lines visible), labels from zoom 6.5
- Tap-to-show-QSOs now works as soon as 4-char grid lines appear (zoom 6),
  not only when 4-char labels show (was zoom 7). Selection outline already
  used GRID_ZOOM_SUB, so feedback is immediate.
- 4-char labels now appear from zoom 6.5 (was 7.0), so the sub-square name
  shows shortly after the lines appear instead of a full zoom level later.
2026-09-08 05:54:06 +08:00
atsunatsu da1facac64 fix(map): world-repeat copies no longer repaint in place — alpha stacking made worked cells brighter and opaque
Pixel analysis of the user's screenshot: worked-cell green measured RGB(57,160,74)
against background RGB(16,16,15), which matches alpha≈178 — i.e. the alpha=90
fill painted ~3 times in place. Root cause: at low zoom colRepeats>0 and every
turn loop shifted the cell by dLon=turn*360°, but projectionToX() normalizes
longitude back into [-180,180), so all turns collapsed onto the SAME screen x
and each cell/line/label was painted 2*colRepeats+1 times at one spot.

Fix: keep the turn offset as a pixel shift (turn * worldWidthPx) on the x
coordinate in ALL turn loops (worked fills at both zooms, vertical lines,
horizontal lines, labels). World-repeat copies now land on their true screen
positions instead of stacking on top of each other.
2026-09-08 05:46:08 +08:00
atsunatsu cb1f341ed9 fix(map): field-zoom grid lines no longer cut through worked cells — root cause of 'green too bright'
The previous fixes only handled sub-square zoom lines. At field zoom the 20°x10°
grid lines were drawn unconditionally: cellLabel(zoom<6) returns 2-char fields
which never match the 4-char workedGrids set, so every field line crossed every
worked 2°x1° square. Bright-yellow (alpha 160) lines through the green fill is
what read as 'green got brighter' — the fill color itself never changed.

Now both line passes iterate the finer 2°x1° sub-grid (forced 4-char labels via
GRID_ZOOM_SUB) and skip segments bordering worked cells, at field zoom and
sub-square zoom alike. Revert the alpha softening (c2e496c4) — the fill was
never the problem; also restores worked-cell labels (7afce7ee revert).
2026-09-08 05:28:12 +08:00
atsunatsu c2e496c406 fix(map): soften worked-cell fill at sub-square zoom, restore cell labels
Reverting the label-skip from 7afce7ee: worked cells keep their 4-char
labels. The 'green too bright' report is an area effect, not an overlay
overdraw — the same alpha=90 fill that reads as a small patch at field zoom
covers half the viewport at sub-square zoom. Use a softer fill (alpha 50)
for the sub-square branch, field zoom unchanged.
2026-09-08 05:02:05 +08:00
atsunatsu 7afce7ee68 fix(map): skip labels on worked cells — bright-yellow 4-char text on green fill read as 'green too bright'
The previous fix skipped grid LINES through worked cells, but each worked cell
still got a bright-yellow (alpha 220 + black shadow) 4-char label drawn dead
center on the green fill at zoom>=7. Field zoom never shows this because 2-char
labels sit at field centers and almost never overlap a worked 2x1 square. Now
worked cells stay completely label-free, matching the field-zoom look.
2026-09-08 04:47:43 +08:00
atsunatsu 86048ee1fd fix(lotw): run report fetch on Dispatchers.IO — main-thread blocking caused ANR/crash
fetchReportBody is a synchronous HttpURLConnection call (up to 30s connect +
120s read) but was invoked directly from a Main-dispatcher viewModelScope
coroutine: the whole UI froze for the duration of the request, then Android
killed the app with an ANR dialog. Wrap both fetch methods in
withContext(Dispatchers.IO). Bug present since the endpoint was introduced.
2026-09-08 04:32:21 +08:00
atsunatsu 42a6ffa4c9 chore: bump version to 4.4.6-ba7opf.9.9 2026-09-08 04:17:52 +08:00
atsunatsu cf1d8e6539 LoTW sync: distinguish failure causes (bad credentials / rate limit / timeout / network)
Rate limit classification per ARRL policy (one download in progress per user
id; HTTP 503 'Page request limit' observed on throttled pulls) and the HTML
login-error page returned for wrong credentials. Settings screen now shows a
specific message per cause instead of one generic 'check callsign/password/network'.
2026-09-08 04:07:03 +08:00
atsunatsu ab8b072f1f fix(map): worked cells at sub-square zoom stay line-free — grid lines skip worked segments
Lines (alpha 160 yellow) were drawn AFTER the worked fills (alpha 90 green),
so at zoom>=6 every worked cell's four borders got yellow-on-green
composited — the cells looked brighter than the same cells at field zoom
(no square lines there). Segments bordering a worked cell are now skipped
in both meridian and parallel passes.
2026-09-08 03:44:21 +08:00
atsunatsu 1d4036e3d0 fix(map): vertical grid lines vanish at global views — bbox now anchored on view center, not left/right edge pixels
The antimeridian unwrap (span > 180 => shift left edge +360) misfired on
every global view (span is legitimately > 180): the range inverted and the
meridian loop drew nothing. Anchor the bbox on the view-center longitude
plus half viewport width in degrees instead; continuous unwrapped range
handles both antimeridian crossing and world repeats.
2026-09-08 03:25:12 +08:00
atsunatsu ab22244f86 fix(map): grid-mode field center offset by -90/-180 base
Maidenhead field index 0 spans -90..-80 lat / -180..-160 lon, so the
field-center must subtract those bases. Without them, entering grid mode
centered a hemisphere away (OL62 -> 115N/290E instead of 25N/110E).
2026-09-08 03:19:40 +08:00
atsunatsu 3b0874d707 chore: bump version to 4.4.6-ba7opf.9.8 2026-09-08 02:56:16 +08:00
atsunatsu 2c5b1479f1 test(lotw): VUCC up to 4 grids in one contact; clarify comment 2026-09-08 02:51:24 +08:00
atsunatsu 39d4498a54 feat(map): tap worked grid at zoom>=7 to show per-grid satellite QSO dialog
- LoTW sync now stores per-QSO detail (call/time/sat/mode/bands) grouped by
  gridsquare (PROP_MODE=SAT only), single JSON blob in SharedPreferences
- GridQso model derives band label (V/U, U/V, V/A...) from BAND_RX/BAND
- Tap detection via osmdroid MapEventsOverlay; selected grid outlined green
- Centered dialog: calls sorted by first-contact time, first-QSO summary,
  expandable call rows list subsequent QSOs in that grid; tap outside closes
2026-09-08 02:47:54 +08:00
atsunatsu 9ed2aebc9d fix(lotw): only collect satellite QSO grids (PROP_MODE=SAT); keep both grids of VUCC_GRIDS pairs 2026-09-08 02:09:25 +08:00
atsunatsu 72d5e13acf tune(map): 4-char sub-square labels at zoom>=7; zoom 6 shows grid lines only 2026-09-08 01:55:40 +08:00
atsunatsu 736dc23aae chore(map): drop unused FIELD_ZOOM_LABELS constant 2026-09-08 01:52:01 +08:00
atsunatsu 052ba79c94 fix(map): field labels visible at all zooms (pixel-size gated), not gated by FIELD_ZOOM_LABELS 2026-09-08 01:51:14 +08:00
atsunatsu cb648caf65 tune(map): raise thresholds — sub-square grid lines at zoom>=6 (was 5), field labels at zoom>=4 (was 3) 2026-09-08 01:48:24 +08:00
atsunatsu 05170541ed fix(map): grid mode centering via view.post (first-layout reset); antimeridian unwrap of visible bbox fixes Pacific-centered vanishing grid; tile meridians/fills across world repeats at low zoom 2026-09-08 01:39:21 +08:00
atsunatsu e75d2dc410 chore: bump version to 4.4.6-ba7opf.9.7 2026-09-08 00:27:17 +08:00
atsunatsu d55a07bc26 feat(map): fill individual worked 2x1 degree squares green at field zoom without square grid lines 2026-09-08 00:14:05 +08:00
atsunatsu c372386a77 fix(data): LoTW sync resilience — longer timeouts (30s/120s), gzip transfer, follow redirects; ARRL server-side latency is the bottleneck 2026-09-08 00:05:22 +08:00
atsunatsu de6ade025c feat(settings): LoTW dialog — hint rewritten without Wavelog, confirm button is now Sync, remove separate sync button 2026-09-07 23:29:19 +08:00
atsunatsu 2eb6d6cd5b fix(map): stop satellite tracking animation in grid mode so it stays centered on the local grid 2026-09-07 22:54:19 +08:00
atsunatsu c09f718aae feat: mutual chip uses theme primary yellow, plain text distance; top bar text indent; low-zoom meridians via direct Mercator math (bypass osmdroid wrap bug); remove Wavelog sync entry — LoTW is the sole grid source (replace instead of merge) 2026-09-07 19:10:44 +08:00
atsunatsu a9fe0b74e9 feat: mutual match screen target-grid chip (needle bearing + grid + distance); grid mode centering no longer swallowed when station position is not yet available 2026-09-07 17:54:14 +08:00
atsunatsu 2c9a5b376f feat: release-notes body in update checker; grid mode centers on station grid on every entry; own grid square drawn with thick outline; low-zoom meridian vanishing while panning fixed 2026-09-07 15:06:33 +08:00
atsunatsu 422e480b2f release: v4.4.6-ba7opf.9.3 2026-09-07 14:07:57 +08:00
atsunatsu efd6931abb fix: LoTW sync — force full report with qso_qslsince (LoTW default only returns new confirmations since last query) and accept lowercase <eoh> header 2026-09-07 13:51:41 +08:00
atsunatsu c7ca77a943 fix: sync dialogs use typed values directly (save+sync in one step); LoTW card wording; center map on station grid when entering grid mode; center grid labels in cells; update-check falls back to GitHub accelerator mirrors 2026-09-07 07:22:41 +08:00
atsunatsu be6473e692 feat: LoTW confirmed-grids sync card — fetch confirmed QSO grids directly from ARRL LoTW (lotwreport.adi) and merge with Wavelog grids; credentials stored locally 2026-09-07 06:55:42 +08:00
atsunatsu 75d162301b fix: meridians vanish at low zoom (osmdroid clips Mercator X; normalize longitude to view center); hide satellite layer and bottom info card in grid mode 2026-09-07 06:28:58 +08:00
atsunatsu 07c09ec3e9 fix: VersionComparator parses multi-level suffixes (4.4.6-ba7opf.9.1) by comparing all numeric segments, not just the last one 2026-09-07 06:11:31 +08:00
atsunatsu 513d50e56a chore: bump version to 4.4.6-ba7opf.9.1 2026-09-07 06:01:45 +08:00
atsunatsu 366edd8bbb fix: correct longitude bounds in Maidenhead grid overlay (left/right were swapped, so meridians and worked-grid fills never drew) 2026-09-07 06:01:10 +08:00
atsunatsu 13df0dfcec fix: Wavelog sync via v1 API logbook_get_worked_grids (3.0.2 has no API v2) 2026-09-07 02:11:40 +08:00
atsunatsu eb1a5d51a2 chore: bump version to 4.4.6-ba7opf.9 2026-09-07 02:02:36 +08:00
atsunatsu 4e3547649f feat: Maidenhead grid mode with worked-grid highlights from Wavelog (API v2 grid=all) 2026-09-07 01:42:42 +08:00
atsunatsu 7abbeb6eb3 fix: update check via GitHub web endpoint, avoiding API 60 req/hour rate limit 2026-09-06 21:33:41 +08:00
atsunatsu 5c73d5ed7a feat: default TLE source order with LAPAN-A2 (Kaggle) first, R4UAB second 2026-09-06 20:18:04 +08:00
atsunatsu a663ef7f1b build: read release signing credentials from local.properties/env instead of hardcoding
Passwords for the release keystore were committed in plaintext to a
public repo. Move them to gitignored local.properties with an
environment-variable fallback. Keystore file itself stays in $HOME.
2026-09-05 07:57:33 +08:00
atsunatsu 1f1e1fb144 fix: keep update install action visible 2026-09-01 11:03:47 +08:00
atsunatsu 5166daa297 fix: localize AMSAT date labels in Chinese 2026-09-01 10:18:56 +08:00
atsunatsu 604c2ec59d feat: check for updates page in settings
Add a full-screen update checker accessible from a button at the
bottom of the settings page. It queries the fork's GitHub latest
release and can download + install the APK in place.

- UpdateRepository (core/data): fetch releases/latest via GitHub API,
  parse tag/description/APK asset, stream APK download to cacheDir
- VersionComparator (core/domain): compare 4.4.6-ba7opf.N style tags
  (base version first, then build number; v-prefix tolerant) + tests
- IMainContainer/MainContainer: inject updateRepo
- UpdateCheckerScreen (feature/settings): shows current version,
  checks for updates, displays latest version + release notes, and a
  Download & install button; FileProvider hands the APK to the
  system installer
- SettingsScreen: bottom entry button, full-screen page toggle
- AndroidManifest: REQUEST_INSTALL_PACKAGES permission + FileProvider
- i18n strings (en/zh)
2026-08-29 16:25:51 +08:00
atsunatsu 1904a6b3e6 feat: drag reorder handle in data sources dialog
Port the upstream drag-reorder pattern (dragHandle/draggedVisual/
autoScroll/DragRowState) so custom data source rows can be reordered
by long-pressing the handle icon, replacing the old move-up button.

- Add 40dp drag-handle box at row start with long-press gesture
- Live reorder while dragging (neighbour squeeze + spring settle)
- Edge auto-scroll during drag
- Keep per-source enabled checkbox + HTTP status label

This file also carries the #242 data-source dialog UI (enabled
checkbox, status code label, restore-default button) which shares
the same sourceSection function and cannot be cleanly split.
2026-08-29 16:25:09 +08:00
atsunatsu 7f390117b8 feat: data source toggles + HTTP status codes (upstream #242)
Port upstream PR #242 data layer: per-source enabled toggles and HTTP
status codes for custom data sources.

- IRemoteSource: NetworkResult(code, stream) + getNetworkStream
- RemoteSource: return HTTP status code (200 ok / error code / ERR)
- ISettingsRepo: dataSourcesStatus StateFlow<Map<String,Int>>
- SettingsRepo: persist enabled flags (single JSON per key) + status
- DatabaseRepo: filter by enabled toggles, write back status codes
- Settings.kt: DataSourcesSettings + satelliteEnabled/transceiversEnabled
- Sync test fakes (AmSatRepositoryTest, DatabaseRepoTest, Fakes.kt)

Fork-specific features preserved: builtinTypesByUrl reverse lookup,
satnogs migration, map-type data source structure.
2026-08-29 16:21:59 +08:00
atsunatsu 346803fe6e fix: normalize Maidenhead grid input to uppercase in mutual pass
- onStationAGrid/onStationBGrid store the trimmed upper-cased grid instead
  of the raw input (ol63 used to stay lowercase in the UI)
- drop the local latLonToGrid/gridToLatLon copies and reuse the domain
  QthConverter, removing the drift risk between two implementations
- inject computeDispatcher (default Dispatchers.Default, no behaviour
  change) so the pass computation is unit-testable on a test scheduler
- add MutualViewModelTest (15 cases): init prefill, grid/lat-lon linkage,
  error paths, main-list reuse, fallback search, sorting, clearError
2026-08-29 10:55:15 +08:00
atsunatsu 47b87a1604 refactor: consolidate Maidenhead conversion into domain QthConverter (4/6/8-char)
- QthConverter.qthToPosition now accepts 4/6/8-char locators (previously
  6-char only: 4-char returned null, 8-char was silently truncated)
- positionToQth now emits upper-case sub-square letters (IO91VL) so all
  callers render grids consistently
- Extended QthConverterTest: 4/6/8-char parsing, odd-length and out-of-range
  rejection, lower-case input normalisation
2026-08-29 10:54:55 +08:00
atsunatsu 935fe6d99f chore: bump version to 4.4.6-ba7opf.6 2026-08-28 15:39:17 +08:00
atsunatsu 69af1c38bb fix: use real SatNOGS TLE source so newly launched satellites appear
- Point the built-in SatNOGS source at db.satnogs.org/api/tle/?format=3le
  (the CelesTrak satnogs group is a separate, slower subset); rename the
  old CelesTrak source to 'CelesTrak SatNOGS'.
- Add a one-time migration in SettingsRepo: installed apps that already
  persisted a satelliteUrls list containing the old CelesTrak SatNOGS URL
  get the real SatNOGS TLE URL inserted right after it.
- Dedupe imported satellites by catnum and radios by uuid in
  DatabaseRepo.updateFromRemote so multiple sources don't insert the
  same primary key twice.
- Test: importing JAMX-0825b (NORAD 98248) from the real SatNOGS source.
2026-08-28 15:23:23 +08:00
atsunatsu 5c35f2418d chore: bump version to 4.4.6-ba7opf.5 2026-08-28 01:07:26 +08:00
atsunatsu 2aa9858660 feat: open map from radar header
Replace the radar page calendar action with the map icon and route the action through the Passes map flow so the Passes navigation item remains selected.
2026-08-28 00:42:00 +08:00
atsunatsu bc6e065804 feat: prefetch AMSAT status on foreground
Start AMSAT status prefetch when the app enters foreground and share the in-flight request with the status page fetch path to avoid duplicate network requests.
2026-08-28 00:41:32 +08:00
atsunatsu 7924a6f69e feat: cache AMSAT status while app is foreground 2026-08-23 19:27:16 +08:00
atsunatsu 9a2a715d1f chore: bump version to 4.4.6-ba7opf.3 2026-08-22 21:15:41 +08:00
atsunatsu ee32c7a1eb feat: confirm AMSAT report uploads 2026-08-22 20:50:39 +08:00
atsunatsu 1d54ac4718 style: color code AMSAT upload status chips 2026-08-22 19:43:46 +08:00
atsunatsu 433ce85561 feat: upload AMSAT status reports 2026-08-22 19:14:24 +08:00
atsunatsu cebe3b7ec4 refactor: move map access under passes 2026-08-22 18:35:53 +08:00
atsunatsu d8f60cf270 docs: add Mutual Pass and AMSAT status features to README 2026-08-20 14:28:09 +08:00
atsunatsu 4a3332fdb5 chore: remove dist/ from tracking, add to .gitignore 2026-08-19 22:19:29 +08:00
atsunatsu 9e7fdbfe71 fix: passband RX update now uses full Doppler round-trip instead of pure passband mapping
When adjusting RX in passband mode (via +/- buttons or slider), the old
code used TransponderMapper.mapDownlinkToUplink (pure passband mapping
without Doppler compensation), producing ~10kHz error at LEO velocities.

Fix: use DopplerFrequencyCalculator.computeUplinkFromDownlinkWithOffset
for the full TX-from-RX round-trip, matching the RX→PASSBAND button
onClick logic. Also fixed frame mismatch: position now uses txLow/txRange
(Doppler-shifted frame) consistently.
2026-08-19 22:19:14 +08:00
atsunatsu 463df58eb9 fix: keep calculator page content composed while scrolling 2026-08-12 17:50:10 +08:00
atsunatsu 58b8801c0c fix: preserve calculator frequencies while scrolling 2026-08-11 23:42:06 +08:00
atsunatsu 2581ba92c0 feat: remember per-satellite doppler offset in calculator
Store the offset (kHz) per NORAD catalog id in SharedPreferences so
each satellite's last entered offset is restored when reopening the
frequency calculator.
2026-08-10 15:11:02 +08:00
atsunatsu e5131ebb72 fix: amsat icon position, swap mutual icon to radio-tower
- Adjust ic_satellite_alt translateY from -226.13 to -230.5 so icon
  is centered vertically (not clipped at bottom)
- Replace ic_match with ic_radio_tower (MDI) for Mutual page
2026-08-09 01:53:16 +08:00
atsunatsu f9accdd517 feat: add AMSAT status page to bottom navigation bar
- Add ic_satellite_alt.xml icon (from upstream author rt-bishop)
- Add Screen.AMSAT to Navigation.kt
- Add AMSAT entry in MainScreen.kt nav bar + NavDisplay entry
- Wire up feature:status module dependency in ApplicationPlugin
- Chinese/English nav_amsat strings already exist from earlier port
2026-08-09 01:03:30 +08:00
atsunatsu ee3c1e7058 feat: add AMSAT satellite status tracking page (#234)
Port upstream PR #234 to fork main:
- New feature:status module (SatStatusScreen, SatStatusViewModel)
- New AmSatRepository + IAmSatRepository + SatStatus models
- RemoteSource: AMSAT API methods (getAmSatCatalog, getAmSatReports)
- MainContainer/IMainContainer: DI injection
- MainTheme: AMSAT status colors (tertiary slots)
- Chinese translations added for all AMSAT strings
- 26 unit tests passing

Note: navigation entry not included — upstream PR #234 intentionally
left it out pending UI restructuring. Add AMSAT to your nav bar or
menu as desired.
2026-08-08 22:13:56 +08:00
atsunatsu a08a28df39 fix: recalculate passes when station position changes
Use combine(selectedIds, stationPosition) in SatelliteRepo.initRepository
so that passes are recalculated on either satellite selection change or
station position change. StateFlow already emits distinct values.
2026-08-08 20:30:08 +08:00
atsunatsu 759a6de2c6 chore: replace workflow badge with release version badge 2026-08-08 15:58:11 +08:00
atsunatsu ba5a984c16 fix: broaden linear transponder matching and deduplicate same-range entries
- isNamedLinearTransponder: match ' Lin' prefix (AO-7 style) and
  add fallback branch (hasLinearName && hasLinearMode) for entries
  with 'Linear'/'Lin' but no 'transponder' word (AO-7, AO-73)
- add deduplicateTransponders: merge entries sharing the same
  uplink/downlink frequency range, preferring non-CW entry
  (AO-7 Mode A had separate SSB and CW entries; JO-97 had
  separate CW and SSB Transponder entries)
- apply dedup in CalculatorPage filter chain
- 6 new tests, 23 total, all passing
2026-08-08 15:42:33 +08:00
atsunatsu 083b8a2847 Merge pull request #5 from MCKero6423/feat/thanks-bg7nta
Add BG7NTA (CW decoding feature) to the thanks list
2026-08-08 12:44:05 +08:00
mckero e8ebfb1ef3 feat(credits): add BG7NTA to the thanks list
Add BG7NTA (CW decoding feature) to the in-app credits/thanks section
across all three locale files (en, zh, tr).
2026-08-08 03:52:24 +00:00
atsunatsu 12eed1d9a9 i18n: use string resources for mutual match page (zh/en) 2026-08-07 21:27:01 +08:00
atsunatsu b3859fbece i18n: translate '友台' to 'opposite' for clarity 2026-08-07 20:06:43 +08:00
atsunatsu f40a67e1b7 revert: remove fldigi CW decoder migration, keep Morse Expert 2026-08-07 20:01:53 +08:00
atsunatsu 16dce5df2a chore: remove .cxx build artifacts from git tracking 2026-08-07 19:59:03 +08:00
atsunatsu 80a58bed61 i18n: localize mutual match page to English 2026-08-07 19:53:36 +08:00
atsunatsu a8afc63bba fix: default CW decoder to ham radio QSO mode 2026-08-07 18:43:01 +08:00
atsunatsu dbf9a2a65f fix: native decoder crash on 64-bit devices; restore calculator layout 2026-08-07 11:25:42 +08:00
atsunatsu ddecaa6017 docs: update README for BA7OPF fork features 2026-08-06 13:19:32 +08:00
atsunatsu 418a05e343 fix(i18n): update Chinese station position wording 2026-08-04 21:03:35 +08:00
atsunatsu c80e351212 fix: correct linear calculator offset mapping 2026-08-04 18:41:53 +08:00
atsunatsu a333192df5 feat: add CW decoder and linear calculator page 2026-08-04 17:49:28 +08:00
atsunatsu a7b41c95c7 fix: improve match header and daily sun times
Match page UI:
- Always show a meaningful status line in the top bar instead of leaving the second row blank on first entry.
- Add a compact status chip for waiting, calculating, result, no-match, and error states.
- Remove the duplicate intro card so the first screen starts directly with station inputs.

Pass list sun times:
- Compute sunrise/sunset from each visible date group's 00:00 in the selected timezone.
- Avoid using an arbitrary pass AOS as the rise/set search start, which could jump later-day headers to the following day's events.
2026-08-03 21:38:57 +08:00
atsunatsu 2298d8eea4 feat: add mutual radar overlay arrows 2026-08-03 17:27:48 +08:00
atsunatsu 1813e1bfa4 fix: refine mutual pass matching and radar overlay 2026-08-03 13:04:42 +08:00
atsunatsu bd09ad22ff chore: remove .hermes from git tracking, add to gitignore 2026-08-03 08:55:04 +08:00
atsunatsu 7e90071c4d fix: revert radar pager to Transceivers tab, default minElev to 0
- Radar pager now defaults to Transceivers tab (initialPage=0)
- Transceivers don't auto-expand (selectedUuid defaults to null)
- Mutual page default minElev changed from settings value to 0.0
- Display filters out portions below the minElev threshold
2026-08-03 03:58:45 +08:00
atsunatsu d1cc944be2 feat: filter mutual page curves to minElev window (satlover.de style)
Elevation curve chart and mutual radar plot now only show the portion
where both stations are above their respective minimum elevation.
The curves are filtered at display time, the pass search still uses
the 0° horizon boundary for consistency with the Passes page.
2026-08-03 03:47:25 +08:00
atsunatsu fb28283679 feat: default pager to SSTV, remove radar plot drag, filter B track by elevation>0 2026-08-03 03:44:37 +08:00
atsunatsu 81ae8772d2 feat: add 'current exact position' button for station A
Removed the automatic posA=stationPos override. Added a button
'当前精确位置' below the grid input that fills in the exact station
position from settings (lat/lon + grid). Users can now freely edit
the position fields and use the button when they want the exact
position.
2026-08-03 03:32:03 +08:00
atsunatsu 571145838d fix: gridToLatLon normalization bug + bidirectional grid/latlon sync
Root cause: gridToLatLon used (lon+180)%360-180 and (lat+90)%180-90
for normalization, treating grid values as from prime meridian/equator
when they are actually from IDL/South Pole. A 6-char grid like OL62AA
(112°E, 22°N) was returning -67.96°, -67.98° (Atlantic Ocean).

Also added bidirectional sync: entering a grid auto-fills lat/lon,
entering lat/lon auto-fills the 6-char grid, so users can verify.
2026-08-03 03:27:15 +08:00
atsunatsu 017b178f2e fix: use refineEdge with exact station position to get correct AOS/LOS boundaries 2026-08-03 03:15:39 +08:00
atsunatsu 670444858c fix: use getElevation for elevation sampling (same function as getLeoPass)
getFullPosition and getElevation both call calculateObs internally,
but the elevation values reported by the user (-60°) suggest they may
differ. Now elevation is computed via getElevation (same as getLeoPass),
while getFullPosition is only used for azimuth.
2026-08-03 03:13:11 +08:00
atsunatsu 7bdff5df81 fix: use passes list with corrected station position, fallback to independent search 2026-08-03 03:08:35 +08:00
atsunatsu df083840ac fix: use independent search algorithm directly, no passes list reuse
The passes list AOS/LOS times can be stale or computed for a different
station position, causing elevation curves to show -60° at pass start.
The independent search (refineEdge + sampleMutualPass) always computes
elevation for the actual positions. With posA now set to the exact
station position, the pass times should match the main page.
2026-08-03 03:03:08 +08:00
atsunatsu a0279bac17 fix: use pass AOS/LOS times directly without refineEdge
The refineEdge function re-computed boundaries using the grid-center
position (posA), which differs from the exact station position that
the passes list was computed with. This caused all passes to be
filtered out. Now we trust the pass list's AOS/LOS times directly
and just sample the elevation/azimuth curves.
2026-08-03 02:47:09 +08:00
atsunatsu 3d630041e1 chore: add debug info when mutual pass search finds nothing
Shows pass count and satellite count in the error message to help
diagnose why the main pass list yields no results.
2026-08-03 02:27:01 +08:00
atsunatsu ea92d3c150 fix: add fallback pass search when main pass list yields no results
If the main pass list (satelliteRepo.passes) is empty or the common
window check filters everything out, fall back to the independent
search algorithm so the user always gets results.
2026-08-03 02:23:51 +08:00
atsunatsu 86ee3dcbac fix: reuse main page pass list for mutual search
findMutualPasses was computing passes independently with its own
search algorithm, which produced different results from the main
page's getLeoPass. Now it directly reuses satelliteRepo.passes,
which is the same list shown on the main passes page. The only
additional computation is refining the common window for station B
and sampling elevation/azimuth curves.
2026-08-03 02:16:15 +08:00
atsunatsu 43788ba7a7 fix: mutual pass search now matches main radar pass definition
Root cause: mutual search used the min-elevation threshold (default 10°)
as the AOS/LOS boundary, while the main radar uses the 0° horizon
(getLeoPass). With identical stations the windows therefore never
matched. Also, an in-progress pass was counted as a new one.

- AOS/LOS boundaries now at the 0° horizon (refined to 1s)
- Skip in-progress mutual windows at search start (like getLeoPass)
- Filter requires BOTH stations to reach their min elevation
- Default min elevation follows the main radar passes setting
2026-08-03 02:04:13 +08:00
atsunatsu 1a3f1bb34c fix: radar track rendering bugs
- Elevation ring labels were inverted (90/60/30 from outer to inner);
  now 30/60/90 correctly from outer ring to center
- Split track paths at the 0/360° azimuth wrap in both MutualRadarView
  and main RadarView, so passes crossing due north no longer draw a
  line straight across the plot
2026-08-03 01:53:37 +08:00
atsunatsu 998c98267b fix: draw full station-B track on main radar, live dot on top
Track line is no longer time-limited (always shows the whole mutual
arc). The station-B live position is drawn separately on the line
using the same pulsing-dot mode as the local station.
2026-08-03 01:29:17 +08:00
atsunatsu 28fa373db0 feat: bidirectional drag sync between elevation curve and radar track
- ElevationCurveChart is now controlled (progress + onProgressChange)
- MutualRadarView shows shared time-cursor positions for both stations
  and is draggable/tappable to move the cursor
- MutualPassCard owns a single dragProgress feeding both charts, so
  dragging either chart moves the other in sync
2026-08-03 00:58:09 +08:00
atsunatsu 4ca33c37ad feat: keep mutual state across nav + overlay station-B track on main radar
- MutualViewModel is Activity-scoped, so returning from Radar keeps query results
- TrackSampleData gains time field for live cut-off
- Removed standalone mutual card from radar page
- RadarViewCompose draws optional dashed station-B track + current dot
- RadarScreen builds trackB from mutual data up to current time
2026-08-03 00:55:05 +08:00
atsunatsu bd42e643ea feat: dual-station radar track plot (satlover/satmatch style)
- MutualPass now carries TrackSample (azimuth/elevation for both stations)
- New MutualRadarView: polar plot with elevation rings (30/60/90),
  cardinal spokes, solid A-track vs dashed B-track, AOS/LOS markers
- Shown in expanded mutual pass card and radar page mutual card
2026-08-03 00:21:20 +08:00
atsunatsu 4baf3821e5 fix: elevation curve x-axis float precision loss
Absolute epoch millis (~1.7e12) exceeds Float precision (ULP ~131s
at that magnitude), so 5s-spaced samples collapsed onto identical
x coordinates, producing a stepped/jagged curve. Use Long relative
time deltas (sample - start) before converting to Float.
2026-08-02 23:36:59 +08:00
atsunatsu d7e1ce11c9 fix: smooth dual-station elevation curve
- Fix cubic Bezier control points (proper Catmull-Rom to Bezier conversion)
- Refine AOS/LOS more robustly by walking from edge into the pass window
- Still 5s sampling for smooth curves
2026-08-02 23:07:28 +08:00
atsunatsu 5de370983d fix: lock screen orientation to portrait 2026-08-02 22:19:37 +08:00
atsunatsu f272e268ab fix: smooth elevation curves using cubic Bezier instead of lineTo
Replace Path.lineTo() with Path.cubicTo() using Catmull-Rom
to Bezier conversion for smooth elevation curves.
2026-08-02 22:10:17 +08:00
atsunatsu e59507a4d3 fix: smoother elevation curves with refined AOS/LOS and 5s sampling
- Reduce sample interval from 10s to 5s for smoother curves
- Add refineEdge() to find exact AOS/LOS at 1s resolution
- Curve now starts/ends at the correct horizon-crossing points
2026-08-02 21:59:51 +08:00
atsunatsu 0bae306312 fix: default station A grid, find all mutual passes, color toArgb()
- Pre-fill station A grid from settingsRepo.stationPosition
- Find ALL mutual passes per satellite (not just first)
- Fix Color.hashCode() -> Color.toArgb() for native canvas paint
- Use 2min gap between pass searches to avoid duplicates
2026-08-02 21:44:31 +08:00
atsunatsu 65f61a5147 fix: mutual pass elevation unit (radians->degrees) + grid input
Bug: OrbitalObject.getElevation() returns radians, but
MutualViewModel was comparing it directly with degree values
from the slider (10-90), causing no matches to be found.

Fix: add elevationDeg() helper that converts to degrees.

Also add Maidenhead grid square input support (4/6/8 chars)
alongside existing lat/lon fields. Grid takes priority when
filled.
2026-08-02 21:36:44 +08:00
atsunatsu 1064dc739e feat: add mutual pass query with dual-station elevation curve
Port satlover.de dual-station pass matching feature:
- New feature/mutual module with mutual pass data model
- MutualViewModel: compute overlapping passes for two stations
- ElevationCurveChart: Canvas-based dual elevation curve with drag
- MutualScreen: input form + results with expandable cards
- Navigation: add Mutual tab to bottom navigation bar
- i18n: add Chinese/English strings for new feature
2026-08-02 21:22:42 +08:00
atsunatsu 8aedd38b51 fix: consistent Chinese date format for pass headers and sun times
Extract dateFormat() helper to ensure computeSunTimes and
groupPasses use identical date format, fixing sunrise/sunset
display bug.

For Chinese locale: "2026年8月1日 星期六"
For English locale: "Sat, 01 Aug 2026"
2026-08-02 20:28:56 +08:00
atsunatsu ad8f08a577 fix: revert DateFormat.FULL to SimpleDateFormat with locale
DateFormat.FULL may cause inconsistent date labels between
computeSunTimes and groupPasses, leading to missing or
wrong sunrise/sunset times. Revert to the original pattern
"EEE, dd MMM yyyy" but with Locale.getDefault() so day
names follow the system language.
2026-08-02 20:26:01 +08:00
atsunatsu 2afce54472 fix: restore satellite catalog number in satellite selection list
Keep number removed from pass list (Components.kt) and
pass detail (PassesScreen.kt) as requested, but restore
it in the satellite selection list (SatellitesScreen.kt).
2026-08-02 20:09:51 +08:00
atsunatsu 8e50fcf8fb style: use primary color for satellite name (same as former number)
Use MaterialTheme.colorScheme.primary for satellite name
text in all three lists (Components, SatellitesScreen,
PassesScreen), matching the color that was previously
used for the catalog number.
2026-08-02 19:58:00 +08:00
atsunatsu e11b03d56b refactor: remove satellite catalog number from UI
Remove the NORAD catalog number (e.g. '44444 - ') prefix
from satellite names in three places:
- Pass list items (Components.kt)
- Satellite selection list (SatellitesScreen.kt)
- Pass detail view (PassesScreen.kt)

The number is still accessible via satellite details if needed.
2026-08-02 19:48:14 +08:00
atsunatsu fa55b82a5a fix(i18n): use locale-aware full date format for Chinese convention
Replace SimpleDateFormat("EEE, dd MMM yyyy") with
DateFormat.getDateInstance(DateFormat.FULL, locale) which
automatically uses the correct format for each locale:

- Chinese: "2026年8月1日 星期六"
- English: "Saturday, August 1, 2026"
- Other locales: their respective conventions
2026-08-02 19:34:43 +08:00
atsunatsu d57832b0d4 fix(i18n): use system locale for date formatting instead of English
PassesViewModel and PassesScreen used Locale.ENGLISH for
SimpleDateFormat, causing day-of-week names like 'Sun' to
always show in English. Changed to Locale.getDefault() so
the device's language setting is respected.

Affects: 'EEE, dd MMM yyyy' date labels in the pass list
(group headers and sun times), and 'HH:mm:ss' time format.
2026-08-02 19:30:42 +08:00
atsunatsu 1d0e8921a9 i18n(zh): complete Chinese translation with new features
- Add Doppler calculator strings (多普勒频率计算器)
- Add CW decoder strings (CW 解码器、开始、停止、清空)
- Add CAT radio control strings (电台控制、CAT 电台控制等)
- Add data import error messages
- Update outro thanks with xdsopl and Robot36 contributors
- Preserve all existing 154 lines of translation
- 20 new strings added
2026-08-02 19:23:17 +08:00
atsunatsu 23d95b8241 fix(cw): fix timing analysis - process per spectrogram column
- Add newColumnCount tracking to CwSpectrogram
- CwDecoder now processes each new column individually for timing
- Each column = 8ms at 8000 Hz sample rate
- Proper per-column iteration through spectrogram history
2026-08-01 19:52:39 +08:00
atsunatsu 8af80866bc feat(cw): v3 spectrogram-based multi-channel Bayesian decoder
Complete rewrite inspired by Morse Expert / CW Skimmer (VE3NEA):
- CwFFT: radix-2 FFT (256-point) for time-frequency analysis
- CwSpectrogram: sliding-window waterfall (40 cols x 33 bins, 8ms resolution)
- CwBayesianDecoder: Gaussian probability replaces hard dit/dash thresholds
- CwChannelTracker: multi-channel peak detection (up to 3 signals)
- CwDecoder: integrates all components, monitors 200-1200 Hz simultaneously

Key advantages over v2 (ggmorse):
- Frequency-agnostic: full spectrum monitored, not locked to one tone
- Multi-channel: tracks multiple signals in parallel
- Bayesian: probability-based decisions, not hard ratios
- Doppler tolerant: frequency drift just moves energy between bins
2026-08-01 19:41:32 +08:00
atsunatsu b9e5ff70f5 feat(cw): add continuous pitch tracking for Doppler drift
- After initial pitch lock, re-scan every ~8 seconds in a narrow
  ±100 Hz window around the current pitch estimate
- Only re-lock if pitch change > 20 Hz (avoids jitter)
- Helps maintain lock when Doppler tracking has residual error
2026-08-01 19:25:27 +08:00
atsunatsu c176d6ad88 feat(cw): port ggmorse algorithms - auto pitch/speed detection, adaptive threshold
- Add CwResampler: linear resampler (downsample to 4 kHz base rate)
- Add CwFilter: first-order IIR high-pass (200 Hz) + low-pass (1200 Hz)
- Add CwGoertzel: running Goertzel filter for tone tracking
- Add CwPitchDetector: DFT-based pitch detection (200-1200 Hz, 10 Hz steps)
- Rewrite CwDecoder: auto pitch detection, auto speed estimation (5-55 WPM),
  adaptive threshold with exponential moving average, resampled 4 kHz pipeline
- 22 unit tests covering resampler, filter, Goertzel, pitch detector, decoder state
- All existing UI/ViewModel code unchanged (same class interface)
2026-08-01 19:22:14 +08:00
atsunatsu 5a6b162e4f fix(cw): add microphone permission check before starting decoder
- Dispatch CwPermissionResult alongside SstvPermissionResult
- Add requestMicPermission callback through TransceiversPage chain
- Check cw.hasPermission before starting audio capture
- Prevent crash when RECORD_AUDIO not granted
2026-07-31 19:00:56 +08:00
atsunatsu 33712c29fc feat(cw): add built-in CW Morse code decoder for linear transponders
- Add CwDsp with FIR bandpass filter, envelope detection, Goertzel tone detector
- Add CwDecoder with real-time Morse timing analysis and character lookup
- Add CW state/actions to RadarState, wire into RadarViewModel
- Add collapsible CW decoder panel to transceivers page
- 19 unit tests covering DSP, Morse table, and decoder state
- Shares IAudioCapture with SSTV, auto-stops SSTV when CW starts
2026-07-31 18:44:49 +08:00
atsunatsu eac1e6e273 feat: add Doppler frequency calculator for linear transponders (issue #91)
- Add DopplerFrequencyCalculator utility with passband mapping + Doppler correction
- Add MHz input UI with real-time 1Hz refresh during pass
- Add kHz offset for downlink frequency correction
- TX/RX fields on same row for compact layout
- 40 unit tests covering linear/FM/inverted transponders
2026-07-31 14:17:19 +08:00
Arty Bishop f585372594 Added cleartext traffic support for custom TLE URLs #227 2026-07-30 17:15:02 +02:00
Arty Bishop 4585332b1a Fixed manual OMM (.csv) data import, tweaked messaging 2026-07-30 15:56:50 +02:00
Arty Bishop 6d2b0ced49 Implemented AOS window and elevation highlight filters 2026-07-30 13:28:37 +02:00
Arty Bishop e86bc2c700 Added a few tweaks to SSTV sensitivity and reception 2026-07-30 12:01:54 +02:00
Arty Bishop 9a52fdfde5 Added support for Icom IC-705 CAT (#229) 2026-07-30 11:43:03 +02:00
Rui Oliveira 07504a2a95 Add the option to override the frequency from the radio 2026-07-24 20:29:51 +01:00
Rui Oliveira 81525b6dd3 Map AFSK (e.g. ISS APRS) to FM 2026-07-24 20:10:37 +01:00
Rui Oliveira c7089ab314 Fix setting the mode (e.g. FM, USB, LSB) 2026-07-24 20:10:10 +01:00
Rui Oliveira 77dfb7bb05 Remove PTT-aware logic, which is not necessary
I was setting the Tx frequency only when we were
in Tx mode, but the Icom 705 will accept the
command to set the Tx frequency even when in Rx mode, so
there is no need to check the PTT state before sending the command.
2026-07-24 20:09:43 +01:00
Rui Oliveira 58fb688593 Fix bugs in setting the Tx frequency
Uses 0x25 01 command to set the Tx frequency,
which is the correct command when in split mode.
2026-07-24 20:09:01 +01:00
Rui Oliveira dc596b4348 Fix bugs in setting Rx frequency 2026-07-24 20:08:46 +01:00
Rui Oliveira f9b1a97a37 Refactor "Disconnect" and "Track" logic 2026-07-24 20:08:23 +01:00
Rui Oliveira d700432829 Add initial support for Icom IC-705 radio
- Added support for Icom IC-705 radio model with split mode functionality.
- Updated RadioTrackingService to handle single-radio split mode and dual-radio configurations.
- Modified MainContainer to provide appropriate radio controllers based on selected model.
- Enhanced SettingsRepo to include split mode preference in radio control settings.
- Updated UI in SettingsDialog to allow toggling of split mode for IC-705 and adjust device selection accordingly.
- Improved handling of baud rates based on selected radio model.
- Added extended operations in IRadioController for IC-705 specific commands.
2026-07-24 20:07:11 +01:00
Arty Bishop b17ea2d918 v4.4.3 - Added required tweaks to support Android 17 (API 37) 2026-06-27 14:15:05 +01:00
Arty Bishop 976fdfd949 Added Star History graph to README, tweaked AGENTS 2026-06-27 12:31:24 +01:00
dependabot[bot] d8db258bd8 Bump actions/checkout from 6 to 7 (#221)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-24 16:38:32 +01:00
Arty Bishop 7b6fb5eb8f v4.4.2 - Fixed transceivers retention, various minor tweaks 2026-06-21 15:19:47 +01:00
Arty Bishop f4aef4f4f0 Fixed SSTV frequency display and "All" category retention 2026-06-21 15:10:28 +01:00
Arty Bishop 9147323db2 Cleaned up SensorsRepo class, removed deprecated calls 2026-06-21 15:03:45 +01:00
Arty Bishop 81d397a09d v4.4.1 - Continuous SSTV decoding, frequency display 2026-06-07 17:23:19 +01:00
Arty Bishop ec1a55c93d Added initial code for possible future deeplink nav support 2026-06-07 16:01:23 +01:00
Arty Bishop e5033cb2e9 Added continuous SSTV decoding of manually selected type 2026-06-06 19:20:35 +01:00
Arty Bishop 39b786ae82 v4.4.0 - Implemented CAT and SSTV support in RadarScreen 2026-06-02 20:40:06 +01:00
Arty Bishop 79809aaec0 Added SSTV image decoding functionality to RadarScreen 2026-06-02 20:06:22 +01:00
Arty Bishop f7f5a73c4c Added colored elevation and decay check to satellite passes 2026-05-30 15:35:52 +01:00
Arty Bishop 18c8ac1822 Merged RadarScreen and RadioControlScreen functionality 2026-05-30 14:14:40 +01:00
Arty Bishop 22607e7710 v4.3.2 - Implemented Swipe-to-Focus behavior, minor fixes 2026-05-30 13:16:14 +01:00
Arty Bishop ad0ff6859e Updated release workflow to avoid third-party dependencies 2026-05-25 18:30:49 +01:00
Arty Bishop a39b1716e7 Added consistent pass selection to Radar and Map screens 2026-05-25 18:30:49 +01:00
Arty Bishop 8a2339cfbf Implemented Swipe-to-Focus behavior for satellite passes 2026-05-25 18:30:48 +01:00
Arty Bishop 821fa0dfe7 v4.3.1 - Zipped custom sources handling, translation fixes 2026-05-04 16:55:15 +01:00
Arty Bishop ce8c2a4834 Added zipped data sources handling to DatabaseRepo 2026-05-04 15:36:42 +01:00
Arty Bishop 8cc4ef5610 Tweaked passes list to show DeepSpace ones at the top 2026-05-04 15:36:42 +01:00
Mubi-Baihua dde22faf46 Added minor tweaks to Chinese translation (#216) 2026-05-02 19:32:18 +01:00
Arty Bishop 7ad81e32e1 v4.3.0 - Sticky header, sun/moon positions, red night mode 2026-04-30 19:32:17 +01:00
Arty Bishop 5af963f3c9 Added several tweaks to sunrise/sunset time calculations 2026-04-28 21:00:43 +01:00
Arty Bishop bfb9fb6ca8 Tweaked ViewModel retention to mirror the Nav2 behavior 2026-04-26 14:44:29 +01:00
Emre Can AkdaşandArty Bishop df24c66ef3 Fixes for Turkish translation, by Emre Can Akdaş (TA3ECR)
Co-authored-by: Arty Bishop <44072814+rt-bishop@users.noreply.github.com>
2026-04-26 14:42:16 +01:00
Arty Bishop b166e896c2 Added current moon/sun positions to the RadarScreen 2026-04-26 14:32:16 +01:00
Arty Bishop 18f8ab517d Added current moon/sun positions to the MapScreen 2026-04-26 00:11:08 +01:00
Arty Bishop d3b8e951dd Added red night mode filter overlay for the whole app 2026-04-25 19:22:32 +01:00
Arty Bishop 6e5a3cb0bb Added sticky header with a date and sunrise/sunset time 2026-04-25 19:20:22 +01:00
Arty Bishop 31d329a19b Added CelestialComputer, extracted common functionality 2026-04-25 18:18:00 +01:00
Arty Bishop 3df358b88c v4.2.2 - Kotlin Serialization, Navigation3, translation fixes 2026-04-24 17:24:00 +01:00
Arty Bishop 7d5bca300d Migrated to Compose Navigation3, updated dependencies 2026-04-24 17:20:23 +01:00
Arty Bishop fb2cd85b11 Migrated to KotlinX Serialization library for JSON parsing 2026-04-24 17:18:06 +01:00
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
Arty Bishop 1156824463 v2.6.0 - Minor fixes release, closed issues #77 and #80 2022-01-09 17:39:37 +00:00
Arty Bishop f48a46d0e4 Fixed csv data parser bug, now showing correct data 2022-01-08 12:29:16 +00:00
Arty Bishop 738aa1132a v2.5.9 - Hotfix for update crash 2021-12-11 15:00:20 +00:00
Arty Bishop 80e49ad85a v2.5.8 - Fixed update crash, redundant components removed 2021-12-10 08:27:12 +00:00
Arty Bishop f9c4650ace Restored missing app icons for low-res/api screens 2021-12-05 12:08:04 +00:00
Arty Bishop 891721bf45 Extensions.kt cleanup, dependencies update 2021-11-28 12:17:00 +00:00
Arty Bishop f694eb4f4d Info layout fixes, text input outline color fix 2021-11-20 12:35:58 +00:00
Arty Bishop f3257660ca Bottom bar text size, map zoom level, pass ripple fixes 2021-11-18 12:27:43 +00:00
Arty Bishop 7f5d205e4b Simplified satellite entry item layout, enabled R8 full mode 2021-11-17 19:40:26 +00:00
Arty Bishop 524a791629 Matched CSV and TLE parsed epoch and eccentricity data 2021-11-16 18:07:56 +00:00
Arty Bishop 1d0716d47f Removed redundant material components 2021-11-09 14:56:05 +01:00
Arty Bishop 04029d50db Added RemoteSource.kt exception catch, dependencies update 2021-11-09 11:48:29 +01:00
Arty Bishop 5ba7ee746c v2.5.7 - Fixed checkbox bug and github actions regression 2021-10-29 19:07:18 +01:00
Arty Bishop a6f772f4de Fixed satellite selection checkbox click bug 2021-10-29 18:32:15 +01:00
Arty Bishop f8f7aba3a8 v2.5.6 - APIv21, issues #68 #69, grid layout, async update 2021-10-28 21:46:02 +01:00
Arty Bishop 88c7bb6bca Removed all java dependencies from domain.predict package 2021-10-28 19:32:21 +01:00
Arty Bishop 8ef0d103e4 Added ability to toggle from pass view to map view #68 2021-10-27 21:47:02 +01:00
Arty Bishop 96ae6338a7 Fixed Redmi dark theme colors bug, minor layouts cleanup 2021-10-27 21:47:02 +01:00
Arty Bishop 9b76d42707 Fetching satellite data asynchronously, increased build speed 2021-10-27 21:47:02 +01:00
Arty Bishop 5998a7e66d Added DataParser.kt with tests, removed unused classes 2021-10-27 21:47:02 +01:00
Arty Bishop c8f7a29812 Swapped RetrofitRemoteSource.kt for a simplified one 2021-10-27 21:47:02 +01:00
Arty Bishop 13f1cc6455 Added OMM to TLE conversion function 2021-10-27 21:47:02 +01:00
Arty Bishop bf72cd2061 Further code refactoring and cleanup 2021-10-27 21:47:02 +01:00
Arty Bishop 253321f3c2 Added database migration, removed moshi and Converters.kt 2021-10-27 21:47:02 +01:00
Arty Bishop 9baaf38a49 Minor strings fixes
Everything seems great, just slightly adjusted the strings variables to avoid any conflict with the Ru locale. I'll add the proper translation with the feature fully implemented and v2.5.6 release.
2021-10-27 21:47:02 +01:00
Kamil Wargula 5c5f08afc0 Add notification button and dialog on Radar Fragment 2021-10-27 21:47:02 +01:00
Arty Bishop 5f4c63ee49 Moved sources urls to database storage 2021-10-27 21:47:02 +01:00
Arty Bishop af7e027350 Switched to GridLayoutManager in entries recycler, classes moved 2021-10-27 21:47:02 +01:00
Arty Bishop db8fe3ce8d Fixed antenna rotator control issue #69 2021-10-27 21:47:02 +01:00
Arty Bishop 55a827330f Moved and renamed several classes 2021-10-27 21:47:02 +01:00
Arty Bishop 6fa63fdcff Added coroutine exception handler to EntriesViewModel.kt 2021-10-27 21:47:02 +01:00
Arty Bishop b6d019ace7 Renamed and moved several classes and interfaces 2021-10-27 21:47:02 +01:00
Arty Bishop 84240c94c6 Added frequency reporting to PassReporter.kt 2021-10-27 21:47:02 +01:00
Arty Bishop 09f35579dc Switched to SplashScreen API 2021-10-27 21:47:02 +01:00
Arty Bishop 8c7e2c9bb0 Extracted rotation data reporting to a separate class 2021-10-27 21:47:02 +01:00
Arty Bishop f3d64d23b2 Reverted API version to v21, Android 5.0 2021-10-27 21:47:02 +01:00
Arty Bishop 37ede31227 v2.5.5 - Fixed data update crash, issues #66 and #67 2021-10-27 21:47:01 +01:00
Arty Bishop 08b4274ca2 Now correctly using the hours ahead setting #66 2021-09-18 17:03:42 +01:00
Arty Bishop fa0f300dcd Fixed broken pass progress indicator #67 2021-09-18 17:03:41 +01:00
Arty Bishop 1fafd04190 Moved and renamed several classes 2021-09-18 16:48:05 +01:00
Arty Bishop f295b3b224 Fully extracted satellite data calculation to Predictor class 2021-09-18 12:41:41 +01:00
Arty Bishop e6d0671816 v2.5.4 - Added radar sweep animation, fixed update issue 2021-09-17 10:10:20 +01:00
Arty Bishop 4bf2e6c768 Added database migration and schema export, fixed crash 2021-09-17 09:49:12 +01:00
Arty Bishop 94751519ac Moved and refactored several classes 2021-09-16 10:33:21 +01:00
Arty Bishop 673474a51f Removed redundant StationPos.kt class 2021-09-16 10:14:18 +01:00
Arty Bishop a82915c0ca Further changes to Look4Sat Predictor.kt core class 2021-09-16 10:03:58 +01:00
Arty Bishop bceeb01194 Further refactoring of Look4Sat core module classes 2021-09-15 09:58:40 +01:00
Arty Bishop 2c757a6b35 Added unit tests for sat params parsing 2021-09-12 15:40:05 +01:00
Arty Bishop 9a4de03cac Renamed and refactored several predict4kotlin classes 2021-09-12 12:21:23 +01:00
Arty Bishop c38fe8b10f Bumped SDK version, further changes to predict4kotlin 2021-09-10 17:33:42 +01:00
Arty Bishop 60a67f86b6 Moved TLE import and Satellite creation to TLE companion object 2021-09-08 18:07:04 +01:00
Arty Bishop ae21e9e5ca Further changes to predict4kotlin classes 2021-09-06 22:39:10 +01:00
Arty Bishop 964fdfbc05 Set exported flag to AndroidManifest.xml 2021-09-05 20:40:30 +01:00
Arty Bishop dc92bf1ab1 Various changes to predict4kotlin classes 2021-09-05 20:36:50 +01:00
Arty Bishop 5460ed2a6c Added PassInfoView.kt radar sweep animation 2021-09-05 19:39:50 +01:00
Arty Bishop 187791f895 Reverted item_pass_leo.xml, updated Extensions.kt 2021-09-04 14:23:13 +01:00
Arty Bishop 0638539f3c Dependencies update, gradle version bumped 2021-08-28 14:05:58 +01:00
Yuhui Su 69d256e008 Fixed the overlapping and blocked texts on the "passes" page #65
* fixed the blocked time string
* fixed the overlapped texts for items on the 'passes' page
2021-08-23 12:25:33 +01:00
Arty Bishop d603a343ca Bumped minSdk to 23 (Android 6.0), fixed AMSat SSL issue 2021-06-20 14:50:29 +01:00
Arty Bishop a26f881160 Dependencies update, fixed issue #58 2021-05-31 09:42:31 +01:00
Arty Bishop eb1ac3341f v2.5.2 - Fixed aiming icon issue #57 and list scrolling issue #59 2021-05-19 09:39:01 +01:00
Arty Bishop ae1fbb1687 Fixed tle update triggered crash 2021-05-19 09:35:52 +01:00
Arty Bishop a0a95ce372 Reverted TLE source update screen, fixed location setting bug #53 #54 2021-05-18 10:24:26 +01:00
Arty Bishop fb2008b07a v2.5.0 - Minor fixes, updated screenshots, version bumped 2021-05-17 09:53:34 +01:00
Arty Bishop 74528f4a14 Added missing copyright notice 2021-05-16 19:33:20 +01:00
Arty Bishop 2d45fd614e Added pass direction arrow to radar view #52 2021-05-16 19:13:11 +01:00
Arty Bishop 1da35e901a Added custom source TLE update dialog 2021-05-16 19:13:11 +01:00
Arty Bishop 5de8787368 Swapped the splash dialog for immediate initial setup 2021-05-16 19:13:11 +01:00
Arty Bishop e3e621e55f Removed sat icon from pass info view 2021-05-16 19:13:11 +01:00
Arty Bishop fdb5752408 Moved and renamed various classes to simplify structure 2021-05-16 19:13:11 +01:00
Arty Bishop c91c58ac69 Removed interacting use cases for the sake of code simplicity 2021-05-16 19:13:10 +01:00
Arty Bishop 0110de8104 Repositories refactoring and merge 2021-05-16 19:13:10 +01:00
Arty Bishop 1a59b5e3e1 Extracted station position management to a separate class 2021-05-16 19:13:10 +01:00
Arty Bishop 5ef79148bd Updated dependencies and gradle version 2021-05-16 19:13:10 +01:00
Arty Bishop 1806e0d147 Added mockito dependency, fixed radar view update bug #51 2021-05-16 19:13:10 +01:00
Arty Bishop 4cfab17413 Further fixes to MapFragment.kt and MapViewModel.kt 2021-04-27 21:06:37 +01:00
Arty Bishop 48758b4f3d Minor fixes to MapFragment.kt and MapViewModel.kt 2021-04-25 15:44:29 +01:00
Arty Bishop fe69e90749 Added orientation pointer to PolarView.kt, changed drawn satellite icon 2021-04-25 10:19:43 +01:00
Arty Bishop 3c29dafd51 Added OMM class for future celestrak compatibility, fixes to PolarView 2021-04-24 17:49:55 +01:00
Arty Bishop 9ffb793dfd Minor refactoring and classes move 2021-04-24 16:47:42 +01:00
Arty Bishop e9a6471b6d Implemented Clean architecture, removed redundant dependencies 2021-04-23 12:59:18 +01:00
Arty Bishop 53fcf4790a Added framework DataSources, DataMapper and Models 2021-04-23 10:19:51 +01:00
Arty Bishop 865ecfb5bf Added SatelliteRepo basic interactors 2021-04-22 13:20:42 +01:00
Arty Bishop d0569f4645 Created satellite DataSources and Repository 2021-04-22 12:50:18 +01:00
Arty Bishop 3db474c763 Created a core module, basic clean architecture setup 2021-04-21 18:27:56 +01:00
Arty Bishop 5f7c3e3786 v2.4.0 - Showing azimuth for respective maxElevation 2021-04-18 19:58:08 +01:00
Arty Bishop e27224d9a8 Added rotator ip address and port checks and error messages 2021-04-18 18:40:23 +01:00
Arty Bishop e5e5fe7ee7 Fixes to the Az-El message sent via socket to rotctld 2021-04-18 17:45:38 +01:00
Arty Bishop 18820b79f4 Removed sat track tail from the map, minor refactoring 2021-04-17 12:46:51 +01:00
Arty Bishop a205ebfa2b Added rotator control function to PolarViewModel.kt 2021-04-14 18:32:57 +01:00
Arty Bishop 703dc17794 Added rotator control preferences to settings screen 2021-04-14 18:32:57 +01:00
Arty Bishop 046ec8ee8c Removed hardcoded screen orientation and redundant network state permission from manifest 2021-04-14 18:31:57 +01:00
Arty Bishop 7cfe44ed13 Merge pull request #49 from yix/master
Add sat name to the polar view
2021-04-14 17:21:56 +01:00
Alex M 5352bd5182 Add sat name to the polar view 2021-04-10 12:08:03 -04:00
Arty Bishop db710c55c4 Fixed transmitters doppler frequency update logic 2021-04-05 19:32:07 +01:00
Arty Bishop 555c2fe626 Extracted passes ticking logic from PassesAdapter.kt 2021-04-05 12:06:06 +01:00
Arty Bishop 7b4f58dd78 Removed deprecated 'artifact' input in github workflow 2021-04-05 09:03:21 +01:00
Arty Bishop b2deab0d7c Fixed recent InflationException, version code bumped 2021-04-05 08:52:03 +01:00
Arty Bishop 39c3a69a11 Minor fixes to various classes, version code bumped 2021-04-04 20:57:50 +01:00
Arty Bishop 8c67ec4808 Extracted sensor listening logic from PolarFragment.kt to Orientation.kt 2021-04-03 17:22:01 +01:00
Arty Bishop c2216e4917 Fixed behaviour of passes recycler ticking and refreshing 2021-04-03 13:47:02 +01:00
Arty Bishop cfd1a2f2b8 Second attempt for correct passes AsyncListDiffer 2021-04-02 22:22:36 +01:00
Arty Bishop af49acf4d9 Temporarily reverted Passes AsyncDiffer 2021-04-02 21:01:53 +01:00
Arty Bishop 7e97cbea1b AsyncListDiffer try for satellite passes 2021-04-02 20:29:57 +01:00
Arty Bishop 6b3a3b31d6 Final satellite entries refactoring, hopefully 2021-03-30 21:43:20 +01:00
Arty Bishop 2044ea273b Now sorting sat items list via LiveData switchMap 2021-03-29 23:11:08 +01:00
Arty Bishop aef560942f Minor visual changes to map info 2021-03-28 17:47:20 +01:00
Arty Bishop 2ba06c779f Further modification of entries selection logic 2021-03-28 11:02:08 +01:00
Arty Bishop 040ad844f5 Now clearing satellite transceivers data on update 2021-03-27 19:32:47 +00:00
Arty Bishop 76dce66a4a Further refactoring of entries selection and filtering logic 2021-03-27 18:57:08 +00:00
Arty Bishop 8d8e0bb7ac Added sat items filtering by transmitter mode 2021-03-27 16:54:03 +00:00
Arty Bishop 995953b823 Fixed sensor error when user holds the phone upright 2021-03-26 09:16:59 +00:00
Arty Bishop e8208b7934 Added SatItem and SatTrans relation to retrieve trans modes 2021-03-26 08:40:28 +00:00
Arty Bishop de11eb98c0 Minor fix to PolarFragment.kt sensor rotation event 2021-03-25 08:53:39 +00:00
Arty Bishop a8b43ca3fb Fixed satellite track show issue, minor Map fixes 2021-03-24 10:17:10 +00:00
Arty Bishop 6f8db0b9b1 Simplified transmitters, added database migration 2021-03-23 10:28:51 +00:00
Arty Bishop a882a73b8e Simplified SatPass.kt, predictor now returns this class 2021-03-23 10:03:42 +00:00
Arty Bishop 33d25d3c25 Replaced original SatPassTime class with my own 2021-03-22 21:37:20 +00:00
Arty Bishop 37c5ebbf2e Fragments and adapters refactoring 2021-03-21 21:16:52 +00:00
Arty Bishop f04e54d59b Separated satellite map logic from the passes screen 2021-03-21 19:53:59 +00:00
Arty Bishop 332b6f666c Added several extension functions 2021-03-21 19:48:20 +00:00
Arty Bishop e0f900366a Added PolarViewModel, removed SharedViewModel 2021-03-20 17:32:26 +00:00
Arty Bishop 4edf316b5e Added PassesViewModel.kt 2021-03-20 16:44:40 +00:00
Arty Bishop d32e5a5ce3 Fixed SelectAllItems button behaviour 2021-03-20 15:34:42 +00:00
Arty Bishop 46bf23171f Switched to using the AsyncListDiffer in EntriesAdapter.kt 2021-03-20 14:36:20 +00:00
Arty Bishop ca3a9e900f Added satellite passes repository 2021-03-18 22:32:05 +00:00
Arty Bishop f370aef0a7 Created separate ViewModel for EntriesFragment.kt 2021-03-18 21:50:14 +00:00
Arty Bishop af7a4d03d2 Switched to using the NavResult Api for tle sources list 2021-03-18 21:43:30 +00:00
Arty Bishop 0ddd0286a5 Minor refactoring of EntriesAdapter.kt 2021-03-18 21:09:24 +00:00
Arty Bishop 46f1ea9880 Changes to DAO and file structure 2021-03-18 20:40:09 +00:00
Arty Bishop 31ed6230ba Swapped Entries StateFlow for SharedFlow 2021-03-17 20:47:24 +00:00
Arty Bishop c981f0900a Moved sat data update back to SharedViewModel.kt 2021-03-16 20:02:28 +00:00
Arty Bishop d013afbf50 Added isNetworkAvailable extension function 2021-03-15 18:10:05 +00:00
Arty Bishop a6d63748fb Switched to concurrent satellite data update 2021-03-15 17:42:43 +00:00
Arty Bishop 5b1bf943e9 Now using repository external scope for sat data update 2021-03-14 20:33:28 +00:00
Arty Bishop 2427f8cbf5 Added GitHub release creation to default action workflow 2021-03-12 14:22:09 +00:00
Arty Bishop cf41889017 Added release signing config 2021-03-11 22:14:04 +00:00
Arty Bishop 8134db90d6 Swapped SingleLiveEvent for MutableStateFlow 2021-03-11 21:34:57 +00:00
Arty Bishop d493e3e87b Fixed IllegalArgException on pass to polar navigation. Triggering github actions on tags push. 2021-03-09 09:33:03 +00:00
Arty Bishop c37537bd67 Added NavController navigateSafe extension function 2021-03-09 09:10:22 +00:00
Arty Bishop 62026abd71 Added deploy bundle workflow triggered on master branch push 2021-03-08 22:28:09 +00:00
Arty Bishop 41a34ce524 Added workflow badge to README.md 2021-03-07 20:19:54 +00:00
Arty Bishop 6e06e16493 Added gradlew executable bit to default workflow 2021-03-07 20:10:29 +00:00
Arty Bishop d3def31c82 Added Look4Sat CI workflow 2021-03-07 20:05:41 +00:00
Arty Bishop 6c78d17faf v2.2.1 - Hotfix for ClassCastException in SharedPrefs 2021-03-07 18:01:11 +00:00
Arty Bishop dbae12a4e4 Switched to GPL v3, added privacy policy 2021-03-07 13:19:52 +00:00
Arty Bishop 3722ec3b05 Fixed select all logic in Entries adapter 2021-03-07 11:35:34 +00:00
Arty Bishop 44910389e3 Added zipped TLE import from Mike McCants' web site 2021-03-06 18:10:15 +00:00
Arty Bishop 0dfd7e6250 Added room type converter for Satellite objects 2021-03-06 17:08:36 +00:00
Arty Bishop 4705ff6915 Using passes screen as a startDestination 2021-03-06 16:28:45 +00:00
Arty Bishop ab811e4ead Switched to SatItems in Entries recycler Adapter 2021-03-06 15:42:35 +00:00
Arty Bishop 8df2334cd6 Removed redundant data class modifiers, minor cleanup 2021-03-06 15:31:47 +00:00
Arty Bishop b169af4883 Now correctly popping back stack on PolarView pass end 2021-03-06 12:58:38 +00:00
Arty Bishop 06633625ba Back to single repo, using retrofit to fetch satellite data 2021-03-05 09:21:17 +00:00
Arty Bishop ef51f671b7 Now storing tle sources in shared prefs, restoring defaults. Added DB migration. 2021-03-03 20:58:09 +00:00
Arty Bishop a15e789e70 Added app-wide supervisor scope to CoroutinesModule.kt 2021-03-02 07:54:06 +00:00
Arty Bishop 31fe18e4b5 Using default TLE sources if empty sources list passed for update 2021-03-01 20:57:58 +00:00
Arty Bishop 07db0378c5 Fixed NullPointerException in EntriesFragment's update from file result 2021-03-01 20:47:17 +00:00
Arty Bishop 4c83cd2e21 Code refactoring, moving classes to new packages 2021-02-28 19:00:08 +00:00
Arty Bishop 4fa2f13834 Cleaned up dependencies, injecting Moshi in RoomConverters 2021-02-28 18:58:54 +00:00
Arty Bishop decc24ea2a v2.1.0 - Various fixes, version code bumped 2021-02-28 13:23:49 +00:00
Arty Bishop 38d21b98a0 Swapped the onboarding activity for a simple dialog 2021-02-28 13:05:38 +00:00
Arty Bishop 7d970b9287 Added DispatchersModule.kt and CoroutineTestRule.kt for coroutine dispatchers injection 2021-02-27 17:33:01 +00:00
Arty Bishop 386c087946 Switched to using the new Activity Result API 2021-02-27 17:14:13 +00:00
Arty Bishop bfeeff5372 Cleaned up the code in PassPredictor class 2021-02-27 14:47:26 +00:00
Arty Bishop 7be9965352 Removed deprecated permission request handler 2021-02-27 11:44:41 +00:00
Arty Bishop f6ca069896 Fixed issue #44, now accepting all valid locators 2021-02-27 11:03:35 +00:00
Arty Bishop 68fbd4fc1a Added several utility extension functions 2021-02-27 09:56:08 +00:00
Arty Bishop a8f3c5eb77 Using liveData builder for appTimer, hilt update 2021-02-26 19:15:47 +00:00
Arty Bishop c929e94d0e Switched to using Kotlin Flow in repository, zoom fix 2021-02-26 18:54:46 +00:00
Arty Bishop 271719c651 Fixed satellite selection clearing bug #46 2021-02-26 18:39:11 +00:00
Arty Bishop bf1ce64553 Stopped clearing satellite entries on each TLE update #39 2021-02-26 18:36:08 +00:00
Arty Bishop 9199765c61 Now setting a correct min zoom level in MapFragment.kt #42 2021-02-26 18:29:43 +00:00
Arty Bishop 124def913a Planted a debug tree with Timber 2021-02-26 18:19:01 +00:00
Arty Bishop 0c961005fc Using native OSM LatLon clipping functions 2021-02-26 17:57:17 +00:00
Arty Bishop c06b54a3c3 Added continuous horizontal map scrolling #41 2021-02-26 17:54:41 +00:00
Arty Bishop 0438b8a51f Removed redundant interfaces, simplified the code 2021-02-25 21:07:31 +00:00
Arty Bishop 02dd0a5a78 Added QthConverter.kt class and unit tests 2021-02-25 20:50:53 +00:00
Arty Bishop 68f124ca98 Removed redundant FastLane files 2021-02-25 20:42:01 +00:00
Arty Bishop f1a78a1f47 Dependencies update 2021-02-25 20:38:06 +00:00
Arty Bishop b2285a691d v2.0.1 - Timer font reverted, version code bumped 2020-12-01 23:50:43 +00:00
Arty Bishop 791a9c206f Reverted LED type font to the regular bold one 2020-12-01 23:13:55 +00:00
Arty Bishop 2ec3826d63 Update README.md 2020-11-29 18:03:29 +00:00
Arty Bishop 24ab1b0990 v2.0.0 - New BottomNavigation UI, OSM MapView, major code refactoring 2020-11-29 17:36:43 +00:00
Arty Bishop 44497b8c3d Fixes to strings.xml, new screenshots, app description and README.md 2020-11-29 17:10:04 +00:00
Arty Bishop c58997460c Added show text labels on the map switch to preference.xml 2020-11-28 18:35:54 +00:00
Arty Bishop f055d96e5a Further changes to MapViewModel.kt, removed bottomNav ripple effect 2020-11-28 16:46:11 +00:00
Arty Bishop 4556d2d975 Further refactor of MapFragment.kt and MapViewModel.kt 2020-11-28 13:56:03 +00:00
Arty Bishop 62f8a4a534 Minor fixes to resources and UI 2020-11-27 23:34:04 +00:00
Arty Bishop e9eba47429 Added more satellite info to the MapFragment.kt 2020-11-27 17:05:51 +00:00
Arty Bishop 2fac5814d8 Changed FABs to normal size, removed old screenshots 2020-11-26 21:19:20 +00:00
Arty Bishop b850c7a41d Added MapViewModel.kt, cleaned up MapFragment.kt 2020-11-26 19:27:46 +00:00
Arty Bishop 19a0649cb3 Added satellite selection buttons to MapFragment.kt 2020-11-26 13:07:02 +00:00
Arty Bishop 59771bd0b3 Minor changes to view holders and PolarFragment.kt 2020-11-25 21:58:51 +00:00
Arty Bishop fde0159a21 Changed GEO sat item view holder 2020-11-25 21:29:11 +00:00
Arty Bishop 4c3731c23e Removed possible leak in PassesFragment.kt 2020-11-25 21:02:44 +00:00
Arty Bishop 4f206abdc6 Now resetting the AOS timer on passes recalculation 2020-11-25 20:42:35 +00:00
Arty Bishop 45b51ad65a Changed LEO sat item view holder 2020-11-25 20:21:36 +00:00
Arty Bishop 2fbb2a2733 Packages and classes renaming 2020-11-24 21:25:26 +00:00
Arty Bishop b91a7c1ff8 Finished splash activity and UI 2020-11-24 14:39:33 +00:00
Arty Bishop e524a62b77 Libs and gradle wrapper update, build.gradle cleanup 2020-11-24 14:34:44 +00:00
Arty Bishop e391ef6109 Added station location entry from QTH locator 2020-11-20 12:37:15 +00:00
Arty Bishop 328810fccf Removed unnecessary permissions from Manifest 2020-11-20 11:31:00 +00:00
Arty Bishop 2d898f1c7b Changed default snackbar style 2020-11-20 10:20:36 +00:00
Arty Bishop 8d123441b1 Added fine location permission check 2020-11-19 20:46:37 +00:00
Arty Bishop 58839c3714 Added basic onboarding activity 2020-11-19 15:18:14 +00:00
Arty Bishop 64357697bd Restored the splash theme/screen 2020-11-18 11:41:43 +00:00
Arty Bishop e59cf9e58a Removed adapter leaks in EntriesFragment.kt 2020-11-17 20:32:40 +00:00
Arty Bishop e5b9c7a132 Added LeakCanary library, removed Write_Ext_Storage permission as obsolete 2020-10-28 21:40:03 +00:00
Arty Bishop b7f7f0b477 Cleaned up the MapFragment.kt, changed satTrack color 2020-10-28 21:38:20 +00:00
Arty Bishop ca92f8581e Added Maidenhead locator conversion functions and tests #2 2020-10-27 08:12:49 +00:00
Arty Bishop e06b1bcb11 Cleaned up preference.xml, added setPositionFromGPS button 2020-10-26 22:35:37 +00:00
Arty Bishop cfdba41ed5 Using action navigating to sources dialog, removed unnecessary scopes 2020-10-25 20:07:11 +00:00
Arty Bishop a7eb805f6d Calculating doppler only for active leo satellites. Fixes to EntriesFragment states, etc. 2020-10-24 23:21:23 +01:00
Arty Bishop 7eb388f63a Navigating to SourcesDialog.kt using Jetpack Navigation 2020-10-24 21:32:20 +01:00
Arty Bishop 04fc4fd3aa Added a "buy me a coffee" button to InfoFragment.kt 2020-10-24 20:26:38 +01:00
Arty Bishop 1c766abd24 Changes to strings.xml and InfoFragment.kt 2020-10-19 23:17:09 +01:00
Arty Bishop 9930fc9bde Showing error message in passes fragment when no satellites selected 2020-10-18 21:05:32 +01:00
Arty Bishop 628a01b530 Added entries update states and error handling 2020-10-18 20:40:58 +01:00
Arty Bishop e9424ad997 Now showing progress bar while calculating passes 2020-10-18 15:22:13 +01:00
Arty Bishop 8b1901fd48 Removed safeargs plugin as it's not being used 2020-10-18 14:53:45 +01:00
Arty Bishop 0a124a36e6 Added polar fragment aos/los timer 2020-10-18 12:31:27 +01:00
Arty Bishop 8eb551fde6 Minor fixes 2020-10-18 11:37:01 +01:00
Arty Bishop ad073724cb Changes to bottom navigation menu icons 2020-10-17 21:39:06 +01:00
Arty Bishop 68bae19b1a Changes to SourcesDialog and adapter 2020-10-17 21:13:15 +01:00
Arty Bishop 810fa5b85c Optimized transmitters adapter, changes to the recycler and ViewHolders 2020-10-17 19:48:28 +01:00
Arty Bishop 69b3e4f70b Added custom recycler divider, further changes to ViewHolders 2020-10-17 14:52:19 +01:00
Arty Bishop 1c6d22ccf9 Changes to LEO sat ViewHolder 2020-10-14 22:41:01 +01:00
Arty Bishop 886a27b016 Even further changes to layout components and colors 2020-10-14 19:35:14 +01:00
Arty Bishop 5dba44c1db Further changes to the theme and colors 2020-10-13 20:11:03 +01:00
Arty Bishop 54e03e1de2 Wrapper and dependencies update 2020-10-12 23:46:25 +01:00
Arty Bishop 2413ec2be1 Fixed OSM latitude error crash 2020-10-12 23:46:24 +01:00
Arty Bishop 4e9b312ae5 Cleared colors and styles, basic application-wide dark theme setup 2020-10-12 22:45:21 +01:00
Arty Bishop b071131ba3 Colors, styles and resources cleanup 2020-10-11 23:16:58 +01:00
Arty Bishop 57eb0a7a38 Added Event.kt class for exceptions handling and showing 2020-10-10 22:30:24 +01:00
Arty Bishop 753a3c823f Fixed too many sql error on selection update 2020-10-10 21:47:44 +01:00
Arty Bishop c6aae706f9 Removed pass prefs dialog, strings cleanup 2020-10-10 16:54:40 +01:00
Arty Bishop 6631f1e745 Moved pass prefs to preferences screen 2020-10-10 16:54:40 +01:00
Arty Bishop 46e92424d4 Swapped the passes countdown timer for the coroutine LiveData trigger 2020-10-10 16:54:40 +01:00
Arty Bishop 648da0094a Now updating sources and entries on first launch 2020-10-10 16:54:40 +01:00
Arty Bishop d776b87232 Dependencies cleanup and update 2020-10-10 16:54:40 +01:00
Arty Bishop 81074941b0 Switched to multiple repositories 2020-10-10 16:54:40 +01:00
Arty Bishop 1c7ce3f726 Switched from Dagger to android Hilt DI 2020-10-10 16:54:40 +01:00
Arty Bishop 4e0aa1481b Optimized PassesAdapter and ViewHolders 2020-10-06 23:24:17 +01:00
Arty Bishop 0ba26d4d75 Now triggering passes calculation on PassesFragment start 2020-10-06 23:24:17 +01:00
Arty Bishop c4abf214f6 Now observing transmitters as LiveData from the database 2020-10-06 23:24:17 +01:00
Arty Bishop 71c9e2c298 Fixed moshi transmitter adapter issue 2020-10-06 23:24:17 +01:00
Arty Bishop 0ef2a09e8a Restored entries selection restoration =) 2020-10-06 23:24:17 +01:00
Arty Bishop 63a759e499 Created additional fastlane files 2020-10-06 23:24:17 +01:00
Arty Bishop 52c77181c8 Removed unused resources and strings 2020-10-06 23:24:17 +01:00
Arty Bishop ad194ba5df Switched from Gson to Moshi converter, removed OkHttp dependency 2020-10-06 23:24:17 +01:00
Arty Bishop 75374ca4d9 Further code cleanup, injected PrefsManager, libs update 2020-10-06 23:24:17 +01:00
Arty Bishop 1d753ba81f Restored passes calculation functionality 2020-10-06 23:24:17 +01:00
Arty Bishop e38ee6384e Fixed SourcesDialog behaviour, code cleanup 2020-10-06 23:24:17 +01:00
Arty Bishop f89a462216 Fixed satellite entries search behaviour 2020-10-06 23:24:17 +01:00
Arty Bishop 747a7620b0 Moved the unnecessary code out of MainActivity.kt 2020-10-06 23:24:17 +01:00
Arty Bishop 3935765491 Added entries recycler item divider, further fixes to the UI 2020-10-06 23:24:17 +01:00
Arty Bishop 704dac6a75 Added view rotation animator extension to Utilities.kt 2020-10-06 23:24:17 +01:00
Arty Bishop 8164384498 Added PassPrefs object, cleaned up the code in SharedViewModel and PrefsManager 2020-10-06 23:24:17 +01:00
Arty Bishop 6b39a6759a Further code refactoring in Repository.kt and EntriesFragment.kt 2020-10-06 23:24:16 +01:00
Arty Bishop 58fe352e30 Restored SourcesDialog and FileDialog in EntriesFragment 2020-10-06 23:24:16 +01:00
Arty Bishop 41cf2f3a5d Moved the "keep screen on" attr from activity to layouts 2020-10-06 23:24:16 +01:00
Arty Bishop a7bccd3b53 Added several utility extension functions, further code cleanup 2020-10-06 23:24:16 +01:00
Arty Bishop f943268035 Removed unnecessary preferences and keys 2020-10-06 23:24:16 +01:00
Arty Bishop 3cd8516d88 Added main timer font. Changes to layouts and resources. 2020-10-06 23:24:16 +01:00
Arty Bishop 4b050e21f0 Further refactoring and code cleanup 2020-10-06 23:24:16 +01:00
Arty Bishop b87f2a6e72 Code refactoring, move and cleanup 2020-10-06 23:24:16 +01:00
Arty Bishop d4ac615a8d Added NavActivity as a future replacement for the MainActivity 2020-10-06 23:24:16 +01:00
Arty Bishop 9e557ebcf5 Added ISS passes issue screenshots 2020-10-06 23:22:48 +01:00
5904 changed files with 44033 additions and 7584 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
+5
View File
@@ -0,0 +1,5 @@
# GitHub Copilot Instructions
Read `AGENTS.md` first, then `CLAUDE.md`.
`AGENTS.md` contains the architecture, module boundaries, implementation details, conventions, and gotchas.
+6
View File
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
+81
View File
@@ -0,0 +1,81 @@
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@v7
- name: Setup Java
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '21'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
- name: Assemble Artifacts
run: ./gradlew assembleRelease bundleRelease
- name: Sign APK
run: |
echo "${{ secrets.KEY_STORE }}" | base64 -d > keystore.jks
APK=$(find app/build/outputs/apk/release -name "*.apk" | head -1)
BUILD_TOOLS=$(ls -d ${ANDROID_HOME}/build-tools/*/ | sort -V | tail -1)
${BUILD_TOOLS}apksigner sign \
--ks keystore.jks \
--ks-pass pass:${{ secrets.KEY_STORE_PASSWORD }} \
--ks-key-alias ${{ secrets.KEY_ALIAS }} \
--key-pass pass:${{ secrets.KEY_PASSWORD }} \
--out app/build/outputs/apk/release/look4sat.apk \
"$APK"
rm keystore.jks
- name: Sign Bundle
run: |
echo "${{ secrets.KEY_STORE }}" | base64 -d > keystore.jks
AAB=$(find app/build/outputs/bundle/release -name "*.aab" | head -1)
jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA-256 \
-keystore keystore.jks \
-storepass ${{ secrets.KEY_STORE_PASSWORD }} \
-keypass ${{ secrets.KEY_PASSWORD }} \
"$AAB" ${{ secrets.KEY_ALIAS }}
rm keystore.jks
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
- name: Deploy Bundle to Google Play
run: |
gem install multi_json
gem install fastlane --no-document
AAB=$(find app/build/outputs/bundle/release -name "*.aab" | head -1)
echo '${{ secrets.SERVICE_ACCOUNT_JSON }}' > service_account.json
fastlane supply \
--aab "$AAB" \
--json_key service_account.json \
--package_name com.rtbishop.look4sat \
--track production \
--skip_upload_images true \
--skip_upload_screenshots true \
rm service_account.json
- 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
+13
View File
@@ -1,6 +1,9 @@
# Built application files
*.ap_
# Hermes AI plans and metadata
.hermes/
# Files for the ART/Dalvik VM
*.dex
@@ -15,6 +18,7 @@ out/
# Gradle files
.gradle/
build/
.cxx/
# Local configuration file (sdk path, etc)
local.properties
@@ -38,6 +42,9 @@ captures/
# Keystore files
# Uncomment the following line if you do not want to check your keystore files in.
#*.jks
/*.properties
/keystore.properties
/app/keystore.jks
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
@@ -56,3 +63,9 @@ fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
/app/release/app-release.aab
/app/release/app-release.apk
/app/release/output-metadata.json
/app/release/
/.kotlin/sessions/
dist/
+122
View File
@@ -0,0 +1,122 @@
# Look4Sat AI Agent Instructions
This is the canonical project guide for all AI assistants working on Look4Sat.
All assistant-specific files (`CLAUDE.md`, `.github/copilot-instructions.md`) point here.
---
## Project Overview
Look4Sat is an open-source, fully offline Android satellite tracker and pass predictor. It tracks 9000+ active
satellites using TLE/OMM data from Celestrak/SatNOGS, calculates orbital positions via SGP4/SDP4 models, and displays
passes relative to the user's location. Features include polar radar visualization, SSTV image decoding, satellite
ground track mapping, and pass predictions up to 10 days ahead. No ads, no tracking, no network required after initial
data download.
## Architecture
**MVI (Model-View-Intent)** with unidirectional data flow:
- `State` data class → exposed via `StateFlow` from ViewModel
- `Action` sealed interface → user intents dispatched to ViewModel's `onAction()`
- Jetpack Compose UI observes state and recomposes reactively
**Clean Architecture layers:**
| Module | Responsibility |
|----------------------|---------------------------------------------------------------------|
| `app` | Entry point. Aggregates all modules |
| `core:data` | Android library. Room DB, OkHttp networking, repo implementations |
| `core:domain` | Pure Kotlin (JVM). Orbital math (SGP4/SDP4), models, repo contracts |
| `core:presentation` | Android library. Compose theme, shared UI components, NavKeys |
| `feature:map` | OSMDroid map with ground tracks |
| `feature:passes` | Pass predictions and upcoming events |
| `feature:radar` | Polar radar view of satellite positions, SSTV image decoding |
| `feature:satellites` | Satellite list, filtering, selection |
| `feature:settings` | User preferences |
- `feature:*` modules depend only on `core:domain` + `core:presentation`. Features never depend on each other.
## Build & Run
```shell
# Debug build
./gradlew assembleDebug
# Release build (minified, shrunk resources)
./gradlew assembleRelease
# Run tests
./gradlew test
```
- **Min SDK**: 24 | **Target SDK**: 36 | **JDK**: 17
- **Gradle**: Uses version catalog (`gradle/libs.versions.toml`) + convention plugins in `build-logic/`
## Key Libraries
- **Compose** (BOM 2026.05.01) + Material3 Adaptive
- **Navigation3** (type-safe, uses `@Serializable` NavKeys)
- **Room** (KSP code generation) for local satellite/TLE storage
- **OkHttp** 5.x for TLE downloads
- **OSMDroid** for map rendering
- **Kotlin Serialization** for navigation args and data parsing
- **Coroutines** + `StateFlow` for async/reactive patterns
## Conventions
- **Minimal dependencies**: Avoid adding libraries when a simple manual solution exists. Fewer deps = less maintenance.
- **DI**: Manual — ViewModels use companion `factory()` methods with `IMainContainer` interface.
- **Navigation**: Type-safe Compose Navigation3 with `@Serializable` data classes as nav keys.
- **State naming**: `<Feature>State` data class + `<Feature>Action` sealed interface per feature.
- **No feature-to-feature deps**: All cross-feature communication goes through core layers.
- **Localization**: 7 languages (en, es, ru, si, tr, uk, zh).
## Data Formats & Migration
**TLE vs. OMM/CSV format:**
Look4Sat supports both TLE and OMM (Orbit Mean-Elements Message) formats for backward compatibility:
- **TLE format**: Traditional 3-line element format (deprecated). NORAD catalog numbers are 5-digit integers, which
are running out of space. Celestrak has signaled that TLE format will eventually be phased out.
- **OMM/CSV format**: The future standard. CSV files contain the same orbital parameters as TLE but use ISO 8601
timestamps and support larger NORAD IDs. Celestrak and SatNOGS already provide OMM data in CSV format.
**Current implementation:**
- `DataParser.kt` handles both `parseTLEStream()` and `parseCSVStream()` seamlessly
- TLE data is downloaded from configured sources and stored in Room database
- When downloading satellite data, the app automatically detects format and parses accordingly
- Both formats produce identical `OrbitalData` objects, ensuring transparent format switching
**Migration path:**
As NORAD catalog space becomes constrained, OMM/CSV will become the primary format. Look4Sat is already positioned
to handle this transition without code changes — existing users can continue using TLE files while new sources
transition to OMM/CSV automatically.
## Code Style
- Prefer **short, focused functions** — single responsibility, easy to read.
- **Exceptions**: Composable functions and math-heavy algorithms (SGP4/SDP4) may be longer.
- Strict code style — no dead code, no unused imports, consistent formatting.
## Roadmap
- **KMP migration**: `core:domain` is to become a fully shareable KMM module. Keep it pure Kotlin/JVM.
## Gotchas
- Orbital math lives in `core:domain/predict/` — it's dense vector math (SGP4/SDP4). Tread carefully.
- TLE/OMM data must be refreshed weekly for accurate predictions (satellite orbits decay). TLE format is legacy and
will eventually be deprecated in favor of OMM/CSV as NORAD catalog numbers approach the 5-digit limit.
- SSTV decoding in `feature:radar` is experimental; image quality depends on signal strength during satellite pass.
- `build-logic/convention/` contains all shared Gradle configuration — edit there, not in individual modules.
- ProGuard is enabled for release builds — don't add reflection-based libs or any other dependencies without asking.
## Copilot Working Mode: Code-Only
- Default to code changes only. Provide explanations in chat only.
- If documentation seems useful, ask first before creating files.
- Do NOT create any `.md` documentation files unless explicitly requested.
- Do NOT add README, guides, summaries, migration notes, or how-to files unless asked.
- Prefer minimal diffs focused on requested implementation.
- Default validation is static checks (`get_errors`). Do NOT run Gradle compile/test tasks unless explicitly requested.
+5
View File
@@ -0,0 +1,5 @@
# CLAUDE.md
Read `AGENTS.md` first, then follow the instructions there.
`AGENTS.md` contains the architecture, module boundaries, implementation details, conventions, and gotchas.
+584 -249
View File
@@ -1,281 +1,622 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Version 3, 29 June 2007
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
TERMS AND CONDITIONS
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
0. Definitions.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
"This License" refers to version 3 of the GNU General Public License.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
A "covered work" means either the unmodified Program or a work based
on the Program.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
1. Source Code.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
The Corresponding Source for a work in source code form is that
same work.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
2. Basic Permissions.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
13. Use with the GNU Affero General Public License.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
14. Revised Versions of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
NO WARRANTY
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
15. Disclaimer of Warranty.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
@@ -287,15 +628,15 @@ free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
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,
@@ -303,37 +644,31 @@ the "copyright" line and a pointer to where the full notice is found.
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, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
+28 -23
View File
@@ -1,33 +1,38 @@
# Look4Sat: Radio satellite tracker
# Look4Sat-BA7OPF
[<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/)
[<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)
[![Release](https://img.shields.io/github/v/release/atsunatsu/Look4Sat)](https://github.com/atsunatsu/Look4Sat/releases)
Amateur radio and weather satellite tracker and passes predictor for Android.
**BA7OPF 定制版** — 基于 [rt-bishop/Look4Sat](https://github.com/rt-bishop/Look4Sat) 的业余无线电卫星追踪器,增加了线性卫星频率计算器等功能。
The very app creation and design is hugely inspired by the open-source [Gpredict](http://gpredict.oz9aec.net/) desktop satellite tracking application, created by [Alexandru Csete, OZ9AEC](https://github.com/csete) and contributors, supported by the [Libre Space Foundation](https://libre.space/).
## 本仓库特色功能
The [Libre Space Foundation](https://libre.space/) team is also behind the epic [SatNOGS](https://satnogs.org/) project that provides an extremely easy to use [API](https://db.satnogs.org/api/) and [DB](https://db.satnogs.org/) with a huge amount of information about satellites, their telemetry and transmitters, which the app uses under the hood.
- **双站过境匹配(Mutual Pass)** — 输入友台经纬度/网格,筛选双方同时可见的卫星过境,在地图上同时显示双方仰角曲线和地面轨迹,支持一键跳转雷达页查看详情
- **线性卫星转发器频率计算器** — 在雷达页的 Calculator 标签页中,支持 TX/RX 双向多普勒频率计算,以及下行频率偏移(offset)输入,方便操作带偏移的线性卫星
- **CW 解码器** — 集成 Morse Expert 解码引擎,支持瀑布图、实时解码文本
- **Passband 模式** — 支持通过位置滑块(Passband)自动计算 TX/RX 频率,避免切换时跳变
- **AMSAT 状态页** — 实时查看卫星的业余无线电转发器状态(开启/关闭/待机),支持 72 小时历史记录回放
- **中文界面优化** — 翻译修正、UI 布局调整,系统语言自动切换
For TLE data calculation and passes prediction Look4Sat uses the mavenized version of [predict4java](https://github.com/davidmoten/predict4java) library, created by [David A. B. Johnson, G4DPZ](https://github.com/g4dpz) and [Dave Moten](https://github.com/davidmoten). Thank you guys for your hard work making this lib efficient and easy to use!
## 上游仓库
Also, I'd like to mention Dr T.S. Kelso for his invaluable contribution to the industry and for his [Celestrak](https://celestrak.com) website, that stores, updates and provides access to TLE data.
本仓库是 [rt-bishop/Look4Sat](https://github.com/rt-bishop/Look4Sat) 的分支,上游仓库的原始功能包括:
The app is built using Dagger2, Retrofit2, Kotlin and Kotlin coroutines, Architecture Components and Jetpack Navigation.
- 基于 Celestrak / SatNOGS 数据的 9000+ 活跃卫星追踪
- SGP4/SDP4 轨道预测,10 天过境预报
- 极坐标雷达图、地面轨迹图
- SSTV 图像解码
- 无广告、无跟踪、完全离线
<p float="left">
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/1.png" width="250"/>
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/2.png" width="250"/>
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/3.png" width="250"/>
</p>
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/4.png" width="750">
## 许可证
## Main features:
GNU General Public License v3.0。详见 [LICENSE](LICENSE)。
* Calculating satellite passes for up to one week (168 hours)
* Calculating passes for the current or manually entered location
* 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
* Offline first: passes predictions are made offline.
Weekly updates of TLEs and transceivers DB are recommended.
## Star History
<a href="https://star-history.dera.page/#atsunatsu/Look4Sat&type=timeline&legend=top-left">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://star-history.dera.page/svg?repos=atsunatsu/Look4Sat&type=timeline&theme=dark&legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://star-history.dera.page/svg?repos=atsunatsu/Look4Sat&type=timeline&legend=top-left" />
<img alt="Star History Chart" src="https://star-history.dera.page/svg?repos=atsunatsu/Look4Sat&type=timeline&legend=top-left" />
</picture>
</a>
+1
View File
@@ -1 +1,2 @@
/build
/release
-92
View File
@@ -1,92 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: "androidx.navigation.safeargs.kotlin"
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.rtbishop.look4sat"
minSdkVersion 21
targetSdkVersion 29
versionCode 18
versionName '1.4.8'
}
buildTypes {
release {
postprocessing {
removeUnusedCode true
removeUnusedResources true
obfuscate false
optimizeCode true
}
}
}
configurations {
all {
exclude module: 'commons-logging'
}
}
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
viewBinding = true
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "com.google.android.material:material:$material_version"
implementation "androidx.appcompat:appcompat:$app_compat_version"
implementation "androidx.legacy:legacy-support-v4:$support_lib_version"
implementation "androidx.constraintlayout:constraintlayout:$constraint_layout_version"
implementation "androidx.core:core-ktx:$ktx_version"
implementation "androidx.preference:preference:$preference_version"
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
implementation "androidx.navigation:navigation-runtime-ktx:$navigation_version"
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "com.google.dagger:dagger:$dagger_version"
kapt "com.google.dagger:dagger-compiler:$dagger_version"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation "org.osmdroid:osmdroid-android:$openStreetMap_version"
implementation "com.github.davidmoten:predict4java:$predict4java_version"
}
+36
View File
@@ -0,0 +1,36 @@
import java.io.FileInputStream
import java.util.Properties
plugins {
alias(libs.plugins.convention.applicationPlugin)
}
// Release signing credentials live in local.properties (gitignored) or
// environment variables — never hardcode passwords in VCS.
val releaseProps = Properties().apply {
val propsFile = rootProject.file("local.properties")
if (propsFile.exists()) FileInputStream(propsFile).use { load(it) }
}
fun releaseCred(name: String): String =
releaseProps.getProperty(name) ?: System.getenv(name) ?: ""
android {
namespace = libs.versions.packageName.get()
defaultConfig {
applicationId = "cn.ba7opf.look4sat"
ndk { abiFilters.add("armeabi-v7a") }
}
signingConfigs {
create("release") {
storeFile = file(System.getProperty("user.home") + "/my-release-key.jks")
storePassword = releaseCred("RELEASE_STORE_PASSWORD")
keyAlias = releaseCred("RELEASE_KEY_ALIAS").ifEmpty { "look4sat" }
keyPassword = releaseCred("RELEASE_KEY_PASSWORD")
}
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
}
}
}
-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
+44 -34
View File
@@ -1,50 +1,60 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.rtbishop.look4sat">
<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_LOCAL_NETWORK" />
<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" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application
android:name="com.rtbishop.look4sat.Look4SatApp"
android:name=".MainApplication"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round">
<activity
android:name="com.rtbishop.look4sat.ui.MainActivity"
android:configChanges="orientation|screenSize"
android:label="@string/app_name"
android:theme="@style/AppTheme.Splash">
android:name=".MainActivity"
android:exported="true"
android:screenOrientation="portrait"
android:theme="@style/Theme.Look4Sat.SplashScreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- <intent-filter android:autoVerify="true">-->
<!-- <action android:name="android.intent.action.VIEW" />-->
<!-- <category android:name="android.intent.category.DEFAULT" />-->
<!-- <category android:name="android.intent.category.BROWSABLE" />-->
<!-- <data android:scheme="https" />-->
<!-- <data android:host="github.com" />-->
<!-- <data android:pathPattern="/rt-bishop/Look4Sat/passes.*" />-->
<!-- </intent-filter>-->
</activity>
</application>
<meta-data
android:name="android.telephony.PROPERTY_SATELLITE_DATA_OPTIMIZED"
android:value="com.rtbishop.look4sat" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

@@ -1,31 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat
import android.app.Application
import com.rtbishop.look4sat.dagger.AppComponent
import com.rtbishop.look4sat.dagger.DaggerAppComponent
class Look4SatApp : Application() {
val appComponent: AppComponent by lazy {
DaggerAppComponent.factory().create(applicationContext)
}
}
@@ -0,0 +1,83 @@
/*
* 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.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.Paint
import android.os.Bundle
import android.view.View
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.lifecycleScope
import com.rtbishop.look4sat.core.domain.repository.IContainerProvider
import com.rtbishop.look4sat.core.presentation.MainTheme
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
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)
observeNightFilterState()
setContent {
MainTheme(isDarkTheme = true) { NavRoot() }
}
}
private fun observeNightFilterState() {
val mainContainer = (applicationContext as IContainerProvider).getMainContainer()
lifecycleScope.launch {
mainContainer.settingsRepo.otherSettings
.map { it.stateOfNightMode }
.distinctUntilChanged()
.collect { nightMode -> applyNightFilter(nightMode) }
}
}
private fun applyNightFilter(enabled: Boolean) {
if (enabled) {
val nightMatrix = ColorMatrix(
floatArrayOf(
1f, 0f, 0f, 0f, 0f, // R → R
0f, 0f, 0f, 0f, 0f, // G → 0
0f, 0f, 0f, 0f, 0f, // B → 0
0f, 0f, 0f, 1f, 0f // A → A
)
)
window.decorView.setLayerType(View.LAYER_TYPE_HARDWARE, Paint().apply {
colorFilter = ColorMatrixColorFilter(nightMatrix)
})
} else {
window.decorView.setLayerType(View.LAYER_TYPE_NONE, null)
}
}
}
@@ -0,0 +1,82 @@
/*
* 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.Activity
import android.app.Application
import android.app.Application.ActivityLifecycleCallbacks
import android.os.Bundle
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)
clearAmSatCacheOnAppBackground()
// trigger automatic update every 48 hours
container.appScope.launch { checkAutoUpdate() }
// load satellite data on every app start
container.appScope.launch { container.satelliteRepo.initRepository() }
}
private fun clearAmSatCacheOnAppBackground() {
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
private var startedActivityCount = 0
override fun onActivityStarted(activity: Activity) {
if (startedActivityCount == 0) {
container.appScope.launch { container.amSatRepo.prefetchStatus() }
}
startedActivityCount += 1
}
override fun onActivityStopped(activity: Activity) {
startedActivityCount = (startedActivityCount - 1).coerceAtLeast(0)
if (startedActivityCount == 0) container.amSatRepo.clearStatusCache()
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) = Unit
override fun onActivityResumed(activity: Activity) = Unit
override fun onActivityPaused(activity: Activity) = Unit
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit
override fun onActivityDestroyed(activity: Activity) = Unit
})
}
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,313 @@
/*
* 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.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.togetherWith
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
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.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
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.ViewModelStoreOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.ui.NavDisplay
import com.rtbishop.look4sat.core.domain.repository.IContainerProvider
import com.rtbishop.look4sat.core.domain.repository.MutualPassData
import com.rtbishop.look4sat.core.presentation.DeeplinkResolver
import com.rtbishop.look4sat.core.presentation.ElevationThresholds
import com.rtbishop.look4sat.core.presentation.LocalElevationThresholds
import com.rtbishop.look4sat.core.presentation.RadarDestination
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.feature.map.MapDestination
import com.rtbishop.look4sat.feature.mutual.MutualScreen
import com.rtbishop.look4sat.feature.mutual.MutualViewModel
import com.rtbishop.look4sat.feature.passes.PassesDestination
import com.rtbishop.look4sat.feature.radar.RadarDestination
import com.rtbishop.look4sat.feature.satellites.SatellitesDestination
import com.rtbishop.look4sat.feature.settings.SettingsDestination
import com.rtbishop.look4sat.feature.status.SatStatusDestination
@Composable
fun NavRoot(deeplink: String? = null) {
val rootBackStack = rememberNavBackStack(Screen.Passes)
val deeplinkResolver = DeeplinkResolver()
var openMapRequest by remember { mutableIntStateOf(0) }
LaunchedEffect(deeplink) {
deeplink?.let { rootBackStack.add(deeplinkResolver.resolve(it)) }
}
val navigateBack: () -> Unit = { rootBackStack.removeLastOrNull() }
val navigateToRadar: () -> Unit = { rootBackStack.add(RadarDestination) }
val navigateToMap: () -> Unit = {
while (rootBackStack.size > 1) rootBackStack.removeAt(rootBackStack.size - 1)
openMapRequest += 1
}
// Incoming screen slides in from the right, outgoing drifts left at 1/3 speed (API35+ style)
val pushTransition = slideInHorizontally(tween(300)) { it } togetherWith
slideOutHorizontally(tween(300)) { -it / 3 }
// Reverse: outgoing slides out to the right, incoming drifts in from the left
val popTransition = slideInHorizontally(tween(300)) { -it / 3 } togetherWith
slideOutHorizontally(tween(300)) { it }
NavDisplay(
modifier = Modifier.fillMaxSize(),
backStack = rootBackStack,
onBack = navigateBack,
transitionSpec = { pushTransition },
popTransitionSpec = { popTransition },
predictivePopTransitionSpec = { popTransition },
entryDecorators = listOf(
rememberSaveableStateHolderNavEntryDecorator(),
rememberViewModelStoreNavEntryDecorator()
),
entryProvider = entryProvider {
entry<Screen.Passes> {
MainScreen(
navigateToRadar = navigateToRadar,
openMapRequest = openMapRequest,
onOpenMapRequestHandled = { openMapRequest = 0 }
)
}
entry<RadarDestination> {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
RadarDestination(navigateUp = navigateBack, navigateToMap = navigateToMap)
}
}
}
)
}
@Composable
fun MainScreen(
navigateToRadar: () -> Unit = {},
openMapRequest: Int = 0,
onOpenMapRequestHandled: () -> Unit = {}
) {
val backStack = rememberNavBackStack(Screen.Passes)
val currentKey = backStack.lastOrNull()
val navigateBack: () -> Unit = { backStack.removeLastOrNull() }
val navigateToMap: () -> Unit = {
while (backStack.size > 1) backStack.removeAt(backStack.size - 1)
backStack.add(Screen.Map)
}
LaunchedEffect(openMapRequest) {
if (openMapRequest > 0) {
navigateToMap()
onOpenMapRequestHandled()
}
}
val fadeTransition = fadeIn(animationSpec = tween(350)) togetherWith fadeOut(animationSpec = tween(350))
val navItems = listOf(Screen.Satellites, Screen.Passes, Screen.AMSAT, Screen.Mutual, Screen.Settings)
val context = LocalContext.current
val container = (context.applicationContext as IContainerProvider).getMainContainer()
val trackingState by container.radioTrackingService.state.collectAsStateWithLifecycle()
val otherSettings by container.settingsRepo.otherSettings.collectAsStateWithLifecycle()
// Activity-scoped so the mutual query results survive navigation to Radar and back
val mutualViewModel: MutualViewModel = viewModel(
viewModelStoreOwner = context as ViewModelStoreOwner,
factory = MutualViewModel.factory(container)
)
CompositionLocalProvider(
LocalElevationThresholds provides ElevationThresholds(
low = otherSettings.lowElevation,
high = otherSettings.highElevation
)
) {
NavigationSuiteScaffold(
navigationSuiteItems = {
navItems.forEach { screen ->
val isSelected = when (currentKey) {
is Screen.Satellites -> screen is Screen.Satellites
is Screen.Passes -> screen is Screen.Passes
is Screen.Map -> screen is Screen.Passes
is Screen.AMSAT -> screen is Screen.AMSAT
is Screen.Mutual -> screen is Screen.Mutual
is Screen.Settings -> screen is Screen.Settings
else -> false
}
item(
icon = { Icon(painterResource(screen.iconResId), stringResource(screen.titleResId)) },
label = { Text(stringResource(screen.titleResId)) },
selected = isSelected,
onClick = {
if (isSelected && !(currentKey is Screen.Map && screen is Screen.Passes)) return@item
while (backStack.size > 1) backStack.removeAt(backStack.size - 1)
if (screen !is Screen.Passes) backStack.add(screen)
}
)
}
},
navigationSuiteColors = NavigationSuiteDefaults.colors(
navigationRailContainerColor = MaterialTheme.colorScheme.surfaceContainer
),
layoutType = when {
!hasEnoughHeight() && hasEnoughWidth() -> NavigationSuiteType.NavigationRail
!hasEnoughWidth() -> NavigationSuiteType.ShortNavigationBarCompact
else -> NavigationSuiteType.ShortNavigationBarMedium
}
) {
Column {
NavDisplay(
backStack = backStack,
modifier = Modifier.weight(1f),
onBack = navigateBack,
transitionSpec = { fadeTransition },
popTransitionSpec = { fadeTransition },
predictivePopTransitionSpec = { fadeTransition },
entryDecorators = listOf(
// Required for saving Compose state per entry
rememberSaveableStateHolderNavEntryDecorator(),
// Required for ViewModel scoping per entry
rememberViewModelStoreNavEntryDecorator()
),
entryProvider = entryProvider {
entry<Screen.Satellites> {
SatellitesDestination(navigateUp = navigateBack)
}
entry<Screen.Passes> {
PassesDestination(
navigateToRadar = { catNum, aosTime ->
container.setMutualPassData(MutualPassData())
container.satelliteRepo.selectPass(catNum, aosTime)
navigateToRadar()
},
navigateToMap = navigateToMap
)
}
entry<Screen.Map> {
MapDestination()
}
entry<Screen.Mutual> {
MutualScreen(
viewModel = mutualViewModel,
navigateToRadar = { catNum, aosTime, pass ->
container.setMutualPassData(pass ?: MutualPassData())
container.satelliteRepo.selectPass(catNum, aosTime)
navigateToRadar()
}
)
}
entry<Screen.AMSAT> { SatStatusDestination() }
entry<Screen.Settings> {
SettingsDestination()
}
}
)
// Radio tracking status banner
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) {
container.setMutualPassData(MutualPassData())
container.satelliteRepo.selectPass(pass.catNum, pass.aosTime)
navigateToRadar()
}
}
.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
)
}
}
}
}
}
}
@@ -1,50 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.dagger
import android.content.Context
import com.rtbishop.look4sat.dagger.modules.*
import com.rtbishop.look4sat.ui.MainActivity
import com.rtbishop.look4sat.ui.fragments.MapOsmFragment
import com.rtbishop.look4sat.ui.fragments.MapViewFragment
import com.rtbishop.look4sat.ui.fragments.PassListFragment
import com.rtbishop.look4sat.ui.fragments.PolarViewFragment
import dagger.BindsInstance
import dagger.Component
import javax.inject.Singleton
@Singleton
@Component(
modules = [NetworkModule::class, PersistenceModule::class,
RepoModule::class, UtilityModule::class, ViewModelModule::class]
)
interface AppComponent {
@Component.Factory
interface Factory {
fun create(@BindsInstance context: Context): AppComponent
}
fun inject(mainActivity: MainActivity)
fun inject(mapViewFragment: MapViewFragment)
fun inject(passListFragment: PassListFragment)
fun inject(polarViewFragment: PolarViewFragment)
fun inject(mapOsmFragment: MapOsmFragment)
}
@@ -1,25 +0,0 @@
package com.rtbishop.look4sat.dagger
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import javax.inject.Inject
import javax.inject.Provider
import javax.inject.Singleton
@Singleton
class ViewModelFactory @Inject constructor(
private val creators: Map<Class<out ViewModel>,
@JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
val creator = creators[modelClass] ?: creators.entries.firstOrNull {
modelClass.isAssignableFrom(it.key)
}?.value ?: throw IllegalArgumentException("Unsupported ViewModel class: $modelClass")
try {
@Suppress("UNCHECKED_CAST")
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
@@ -1,14 +0,0 @@
package com.rtbishop.look4sat.dagger
import androidx.lifecycle.ViewModel
import dagger.MapKey
import kotlin.reflect.KClass
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
@@ -1,56 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.dagger.modules
import com.rtbishop.look4sat.network.RemoteDataSource
import com.rtbishop.look4sat.network.RemoteSource
import com.rtbishop.look4sat.network.TransmittersApi
import dagger.Module
import dagger.Provides
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Singleton
@Module
class NetworkModule {
@Singleton
@Provides
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient()
}
@Singleton
@Provides
fun provideRemoteDataSource(api: TransmittersApi, client: OkHttpClient): RemoteSource {
return RemoteDataSource(api, client)
}
@Singleton
@Provides
fun provideTransmittersApi(): TransmittersApi {
return Retrofit.Builder()
.baseUrl("https://db.satnogs.org/api/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(TransmittersApi::class.java)
}
}
@@ -1,71 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.dagger.modules
import android.content.Context
import androidx.room.Room
import com.rtbishop.look4sat.persistence.LocalDataSource
import com.rtbishop.look4sat.persistence.LocalSource
import com.rtbishop.look4sat.persistence.SatelliteDb
import com.rtbishop.look4sat.persistence.dao.EntriesDao
import com.rtbishop.look4sat.persistence.dao.SourcesDao
import com.rtbishop.look4sat.persistence.dao.TransmittersDao
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
@Module
class PersistenceModule {
@Singleton
@Provides
fun provideEntriesDao(db: SatelliteDb): EntriesDao {
return db.entriesDao()
}
@Singleton
@Provides
fun provideTransmittersDao(db: SatelliteDb): TransmittersDao {
return db.transmittersDao()
}
@Singleton
@Provides
fun providesSourcesDao(db: SatelliteDb): SourcesDao {
return db.sourcesDao()
}
@Singleton
@Provides
fun provideLocalDataSource(
entriesDao: EntriesDao,
transmittersDao: TransmittersDao,
sourcesDao: SourcesDao
): LocalSource {
return LocalDataSource(entriesDao, transmittersDao, sourcesDao)
}
@Singleton
@Provides
fun provideSatelliteDb(context: Context): SatelliteDb {
return Room.databaseBuilder(context, SatelliteDb::class.java, "satDb")
.build()
}
}
@@ -1,31 +0,0 @@
package com.rtbishop.look4sat.dagger.modules
import android.content.ContentResolver
import android.content.Context
import com.rtbishop.look4sat.network.RemoteSource
import com.rtbishop.look4sat.persistence.LocalSource
import com.rtbishop.look4sat.repo.DefaultRepository
import com.rtbishop.look4sat.repo.Repository
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
@Module
class RepoModule {
@Singleton
@Provides
fun provideContentResolver(context: Context): ContentResolver {
return context.applicationContext.contentResolver
}
@Singleton
@Provides
fun provideDefaultRepository(
resolver: ContentResolver,
localSource: LocalSource,
remoteSource: RemoteSource
): Repository {
return DefaultRepository(resolver, localSource, remoteSource)
}
}
@@ -1,54 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.dagger.modules
import android.content.Context
import android.content.SharedPreferences
import android.location.LocationManager
import androidx.preference.PreferenceManager
import com.rtbishop.look4sat.utility.PrefsManager
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
@Module
class UtilityModule {
@Singleton
@Provides
fun provideSharedPreferences(context: Context): SharedPreferences {
return PreferenceManager.getDefaultSharedPreferences(context)
}
@Singleton
@Provides
fun provideLocationManager(context: Context): LocationManager {
return context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
}
@Singleton
@Provides
fun providePrefsManager(
sharedPreferences: SharedPreferences,
locationManager: LocationManager
): PrefsManager {
return PrefsManager(sharedPreferences, locationManager)
}
}
@@ -1,22 +0,0 @@
package com.rtbishop.look4sat.dagger.modules
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.rtbishop.look4sat.dagger.ViewModelFactory
import com.rtbishop.look4sat.dagger.ViewModelKey
import com.rtbishop.look4sat.ui.SharedViewModel
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoMap
@Module
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(SharedViewModel::class)
abstract fun bindSharedViewModel(viewModel: SharedViewModel): ViewModel
@Binds
abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
@@ -1,7 +0,0 @@
package com.rtbishop.look4sat.data
sealed class Result<out T : Any> {
data class Success<out T : Any>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
object InProgress : Result<Nothing>()
}
@@ -1,12 +0,0 @@
package com.rtbishop.look4sat.data
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.github.amsacode.predict4java.TLE
@Entity(tableName = "entries")
data class SatEntry(val tle: TLE, var isSelected: Boolean = false) {
@PrimaryKey
var catNum: Int = tle.catnum
var name: String = tle.name
}
@@ -1,11 +0,0 @@
package com.rtbishop.look4sat.data
import org.osmdroid.api.IGeoPoint
import org.osmdroid.views.overlay.OverlayItem
data class SatItem(
val name: String,
val description: String,
val position: IGeoPoint,
val pass: SatPass
) : OverlayItem(name, description, position)
@@ -1,32 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.data
import com.github.amsacode.predict4java.SatPassTime
import com.github.amsacode.predict4java.TLE
import com.rtbishop.look4sat.utility.PassPredictor
data class SatPass(
val tle: TLE,
val predictor: PassPredictor,
val pass: SatPassTime,
var progress: Int = 0,
var active: Boolean = false
)
@@ -1,7 +0,0 @@
package com.rtbishop.look4sat.data
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "sources")
data class TleSource(@PrimaryKey var url: String = String())
@@ -1,38 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.data
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.google.gson.annotations.SerializedName
@Entity(tableName = "transmitters")
data class Transmitter(
@PrimaryKey @SerializedName("uuid") val uuid: String,
@SerializedName("description") val description: String,
@SerializedName("alive") val isAlive: Boolean,
@SerializedName("uplink_low") val uplinkLow: Long?,
@SerializedName("uplink_high") val uplinkHigh: Long?,
@SerializedName("downlink_low") val downlinkLow: Long?,
@SerializedName("downlink_high") val downlinkHigh: Long?,
@SerializedName("mode") val mode: String?,
@SerializedName("invert") val isInverted: Boolean,
@SerializedName("norad_cat_id") val catNum: Int
)
@@ -1,53 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.network
import android.util.Log
import com.rtbishop.look4sat.data.TleSource
import com.rtbishop.look4sat.data.Transmitter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.InputStream
import javax.inject.Inject
class RemoteDataSource @Inject constructor(
private val api: TransmittersApi,
private val client: OkHttpClient
) : RemoteSource {
override suspend fun fetchTleStreams(urlList: List<TleSource>): List<InputStream> {
val streams = mutableListOf<InputStream>()
withContext(Dispatchers.IO) {
urlList.withIndex().forEach {
val request = Request.Builder().url(it.value.url).build()
val stream = client.newCall(request).execute().body?.byteStream()
stream?.let { inputStream -> streams.add(inputStream) }
}
}
Log.d("myTag", urlList.toString())
return streams
}
override suspend fun fetchTransmitters(): List<Transmitter> {
return api.fetchTransmitters()
}
}
@@ -1,12 +0,0 @@
package com.rtbishop.look4sat.network
import com.rtbishop.look4sat.data.TleSource
import com.rtbishop.look4sat.data.Transmitter
import java.io.InputStream
interface RemoteSource {
suspend fun fetchTleStreams(urlList: List<TleSource>): List<InputStream>
suspend fun fetchTransmitters(): List<Transmitter>
}
@@ -1,29 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.network
import com.rtbishop.look4sat.data.Transmitter
import retrofit2.http.GET
interface TransmittersApi {
@GET("transmitters/")
suspend fun fetchTransmitters(): List<Transmitter>
}
@@ -1,84 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.persistence
import com.rtbishop.look4sat.data.SatEntry
import com.rtbishop.look4sat.data.TleSource
import com.rtbishop.look4sat.data.Transmitter
import com.rtbishop.look4sat.persistence.dao.EntriesDao
import com.rtbishop.look4sat.persistence.dao.SourcesDao
import com.rtbishop.look4sat.persistence.dao.TransmittersDao
import javax.inject.Inject
class LocalDataSource @Inject constructor(
private val entriesDao: EntriesDao,
private val transmittersDao: TransmittersDao,
private val sourcesDao: SourcesDao
) : LocalSource {
override suspend fun insertEntries(entries: List<SatEntry>) {
entriesDao.insertEntries(entries)
}
override suspend fun getAllEntries(): List<SatEntry> {
return entriesDao.getAllEntries()
}
override suspend fun getSelectedEntries(): List<SatEntry> {
return entriesDao.getSelectedEntries()
}
override suspend fun updateEntriesSelection(catNumList: List<Int>) {
entriesDao.clearEntriesSelection()
catNumList.forEach { entriesDao.updateEntrySelection(it) }
}
override suspend fun clearEntries() {
entriesDao.clearEntries()
}
////////////////////////////////////////////////////////////////////////////////////////////////
override suspend fun insertTransmitters(transmitters: List<Transmitter>) {
transmittersDao.insertTransmitters(transmitters)
}
override suspend fun getTransmittersByCatNum(catNum: Int): List<Transmitter> {
return transmittersDao.getTransmittersByCatNum(catNum)
}
override suspend fun clearTransmitters() {
transmittersDao.clearTransmitters()
}
////////////////////////////////////////////////////////////////////////////////////////////////
override suspend fun insertSources(sources: List<TleSource>) {
sourcesDao.insertSources(sources)
}
override suspend fun getSources(): List<TleSource> {
return sourcesDao.getSources()
}
override suspend fun clearSources() {
sourcesDao.clearSources()
}
}
@@ -1,34 +0,0 @@
package com.rtbishop.look4sat.persistence
import com.rtbishop.look4sat.data.SatEntry
import com.rtbishop.look4sat.data.TleSource
import com.rtbishop.look4sat.data.Transmitter
interface LocalSource {
suspend fun insertEntries(entries: List<SatEntry>)
suspend fun getAllEntries(): List<SatEntry>
suspend fun getSelectedEntries(): List<SatEntry>
suspend fun updateEntriesSelection(catNumList: List<Int>)
suspend fun clearEntries()
////////////////////////////////////////////////////////////////////////////////////////////////
suspend fun insertTransmitters(transmitters: List<Transmitter>)
suspend fun getTransmittersByCatNum(catNum: Int): List<Transmitter>
suspend fun clearTransmitters()
////////////////////////////////////////////////////////////////////////////////////////////////
suspend fun insertSources(sources: List<TleSource>)
suspend fun getSources(): List<TleSource>
suspend fun clearSources()
}
@@ -1,46 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.persistence
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.rtbishop.look4sat.data.SatEntry
import com.rtbishop.look4sat.data.TleSource
import com.rtbishop.look4sat.data.Transmitter
import com.rtbishop.look4sat.persistence.dao.EntriesDao
import com.rtbishop.look4sat.persistence.dao.SourcesDao
import com.rtbishop.look4sat.persistence.dao.TransmittersDao
import com.rtbishop.look4sat.utility.Converters
@Database(
entities = [Transmitter::class, SatEntry::class, TleSource::class],
version = 1,
exportSchema = false
)
@TypeConverters(Converters::class)
abstract class SatelliteDb : RoomDatabase() {
abstract fun entriesDao(): EntriesDao
abstract fun transmittersDao(): TransmittersDao
abstract fun sourcesDao(): SourcesDao
}
@@ -1,48 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.persistence.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.rtbishop.look4sat.data.SatEntry
@Dao
interface EntriesDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertEntries(entries: List<SatEntry>)
@Query("SELECT * FROM entries ORDER BY name ASC")
suspend fun getAllEntries(): List<SatEntry>
@Query("SELECT * FROM entries WHERE isSelected = 1 ORDER BY name ASC")
suspend fun getSelectedEntries(): List<SatEntry>
@Query("UPDATE entries SET isSelected = 1 WHERE catNum == :catNum")
suspend fun updateEntrySelection(catNum: Int)
@Query("UPDATE entries SET isSelected = 0")
suspend fun clearEntriesSelection()
@Query("DELETE FROM entries")
suspend fun clearEntries()
}
@@ -1,20 +0,0 @@
package com.rtbishop.look4sat.persistence.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.rtbishop.look4sat.data.TleSource
@Dao
interface SourcesDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertSources(sources: List<TleSource>)
@Query("SELECT * FROM sources")
suspend fun getSources(): List<TleSource>
@Query("DELETE FROM sources")
suspend fun clearSources()
}
@@ -1,20 +0,0 @@
package com.rtbishop.look4sat.persistence.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.rtbishop.look4sat.data.Transmitter
@Dao
interface TransmittersDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertTransmitters(transmitters: List<Transmitter>)
@Query("SELECT * FROM transmitters WHERE isAlive = 1 and catNum = :catNum")
suspend fun getTransmittersByCatNum(catNum: Int): List<Transmitter>
@Query("DELETE FROM transmitters")
suspend fun clearTransmitters()
}
@@ -1,97 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.repo
import android.content.ContentResolver
import android.net.Uri
import com.github.amsacode.predict4java.TLE
import com.rtbishop.look4sat.data.SatEntry
import com.rtbishop.look4sat.data.TleSource
import com.rtbishop.look4sat.data.Transmitter
import com.rtbishop.look4sat.network.RemoteSource
import com.rtbishop.look4sat.persistence.LocalSource
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject
class DefaultRepository @Inject constructor(
private val resolver: ContentResolver,
private val localSource: LocalSource,
private val remoteSource: RemoteSource
) : Repository {
override suspend fun updateEntriesFromFile(tleUri: Uri) {
withContext(Dispatchers.IO) {
resolver.openInputStream(tleUri)?.use { stream ->
val tleList = TLE.importSat(stream)
val entries = tleList.map { SatEntry(it) }
localSource.clearEntries()
localSource.insertEntries(entries)
}
}
}
override suspend fun updateEntriesFromUrl(urlList: List<TleSource>) {
withContext(Dispatchers.IO) {
val streams = remoteSource.fetchTleStreams(urlList)
val entries = mutableListOf<SatEntry>()
streams.forEach {
val list = TLE.importSat(it).map { tle -> SatEntry(tle) }
entries.addAll(list)
}
localSource.clearEntries()
localSource.insertEntries(entries)
}
}
override suspend fun getAllEntries(): List<SatEntry> {
return localSource.getAllEntries()
}
override suspend fun getSelectedEntries(): List<SatEntry> {
return localSource.getSelectedEntries()
}
override suspend fun updateEntriesSelection(catNumList: List<Int>) {
localSource.updateEntriesSelection(catNumList)
}
////////////////////////////////////////////////////////////////////////////////////////////////
override suspend fun updateTransmitters() {
localSource.clearTransmitters()
localSource.insertTransmitters(remoteSource.fetchTransmitters())
}
override suspend fun getTransmittersByCatNum(catNum: Int): List<Transmitter> {
return localSource.getTransmittersByCatNum(catNum)
}
////////////////////////////////////////////////////////////////////////////////////////////////
override suspend fun updateSources(sources: List<TleSource>) {
localSource.clearSources()
localSource.insertSources(sources)
}
override suspend fun getSources(): List<TleSource> {
return localSource.getSources()
}
}
@@ -1,31 +0,0 @@
package com.rtbishop.look4sat.repo
import android.net.Uri
import com.rtbishop.look4sat.data.SatEntry
import com.rtbishop.look4sat.data.TleSource
import com.rtbishop.look4sat.data.Transmitter
interface Repository {
suspend fun updateEntriesFromFile(tleUri: Uri)
suspend fun updateEntriesFromUrl(urlList: List<TleSource>)
suspend fun getAllEntries(): List<SatEntry>
suspend fun getSelectedEntries(): List<SatEntry>
suspend fun updateEntriesSelection(catNumList: List<Int>)
////////////////////////////////////////////////////////////////////////////////////////////////
suspend fun updateTransmitters()
suspend fun getTransmittersByCatNum(catNum: Int): List<Transmitter>
////////////////////////////////////////////////////////////////////////////////////////////////
suspend fun updateSources(sources: List<TleSource>)
suspend fun getSources(): List<TleSource>
}
@@ -1,222 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.ui
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import com.rtbishop.look4sat.Look4SatApp
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.dagger.ViewModelFactory
import com.rtbishop.look4sat.data.Result
import com.rtbishop.look4sat.data.TleSource
import com.rtbishop.look4sat.databinding.ActivityMainBinding
import com.rtbishop.look4sat.databinding.DrawerHeaderBinding
import com.rtbishop.look4sat.ui.fragments.TleSourcesDialogFragment
import com.rtbishop.look4sat.utility.Extensions.toast
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import javax.inject.Inject
class MainActivity : AppCompatActivity(), TleSourcesDialogFragment.SourcesSubmitListener {
private val pickFileReqCode = 1001
private val permissionsReqCode = 1000
private val permissions = arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
@Inject
lateinit var viewModelFactory: ViewModelFactory
private lateinit var viewModel: SharedViewModel
private lateinit var mainBinding: ActivityMainBinding
private lateinit var drawerBinding: DrawerHeaderBinding
private lateinit var appBarConfig: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.AppTheme)
super.onCreate(savedInstanceState)
mainBinding = ActivityMainBinding.inflate(layoutInflater)
drawerBinding = DrawerHeaderBinding.inflate(layoutInflater, mainBinding.navView, true)
setContentView(mainBinding.root)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
(application as Look4SatApp).appComponent.inject(this)
viewModel = ViewModelProvider(this, viewModelFactory).get(SharedViewModel::class.java)
setupComponents()
setupObservers()
setupDrawer()
viewModel.setDefaultTleSources()
if (!hasPermissions(this, permissions)) {
ActivityCompat.requestPermissions(this, permissions, permissionsReqCode)
}
}
@SuppressLint("SourceLockedOrientationActivity")
private fun setupComponents() {
val navController = findNavController(R.id.nav_host)
val toolbar = mainBinding.includeAppBar.toolbar
setSupportActionBar(toolbar)
appBarConfig = AppBarConfiguration(
setOf(R.id.nav_pass_list, R.id.nav_map_view),
mainBinding.drawerLayout
)
setupActionBarWithNavController(navController, appBarConfig)
mainBinding.navView.setupWithNavController(navController)
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.nav_pass_list -> {
this.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
toolbar.visibility = View.VISIBLE
mainBinding.includeAppBar.toolbarLayout.visibility = View.VISIBLE
}
R.id.nav_map_view -> {
this.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
toolbar.visibility = View.GONE
mainBinding.includeAppBar.toolbarLayout.visibility = View.GONE
}
R.id.nav_map_osm -> {
this.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
toolbar.visibility = View.GONE
mainBinding.includeAppBar.toolbarLayout.visibility = View.GONE
}
else -> {
this.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
toolbar.visibility = View.VISIBLE
mainBinding.includeAppBar.toolbarLayout.visibility = View.GONE
}
}
}
}
private fun setupObservers() {
viewModel.getGSP().observe(this, Observer { result ->
when (result) {
is Result.Success -> {
drawerBinding.drawerLatValue.text =
String.format(getString(R.string.pat_location), result.data.latitude)
drawerBinding.drawerLonValue.text =
String.format(getString(R.string.pat_location), result.data.longitude)
}
is Result.Error -> {
when (result.exception) {
is SecurityException ->
getString(R.string.err_no_permissions).toast(this)
is IllegalArgumentException ->
getString(R.string.update_loc_failure).toast(this)
}
}
}
})
viewModel.getUpdateStatus().observe(this, Observer { result ->
when (result) {
is Result.Success -> getString(R.string.import_data_success).toast(this)
is Result.Error -> getString(R.string.import_data_failure).toast(this)
}
})
}
private fun setupDrawer() {
drawerBinding.drawerBtnLoc.setOnClickListener {
viewModel.updatePosition()
it.lockButton()
}
drawerBinding.drawerBtnTleFile.setOnClickListener {
val openFileIntent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
}
startActivityForResult(openFileIntent, pickFileReqCode)
mainBinding.drawerLayout.closeDrawers()
it.lockButton()
}
drawerBinding.drawerBtnTleUrl.setOnClickListener {
TleSourcesDialogFragment(viewModel.getTleSources()).apply {
setSourcesListener(this@MainActivity)
show(supportFragmentManager, "TleSourcesDialog")
}
mainBinding.drawerLayout.closeDrawers()
it.lockButton()
}
drawerBinding.drawerBtnGithub.setOnClickListener {
val gitHubUrl = "https://github.com/rt-bishop/LookingSat"
val githubIntent = Intent(Intent.ACTION_VIEW, Uri.parse(gitHubUrl))
startActivity(githubIntent)
}
}
override fun onSourcesSubmit(list: List<TleSource>) {
viewModel.updateSatData(list)
}
private fun View.lockButton() {
lifecycleScope.launch {
this@lockButton.isEnabled = false
delay(3000)
this@lockButton.isEnabled = true
}
}
private fun hasPermissions(context: Context, permissions: Array<String>): Boolean =
permissions.all {
ActivityCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
}
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host)
return navController.navigateUp(appBarConfig) || super.onSupportNavigateUp()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == pickFileReqCode && resultCode == RESULT_OK) {
data?.data?.also { uri ->
viewModel.updateEntriesFromFile(uri)
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
}
@@ -1,173 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.ui
import android.net.Uri
import androidx.lifecycle.*
import com.github.amsacode.predict4java.GroundStationPosition
import com.rtbishop.look4sat.data.Result
import com.rtbishop.look4sat.data.SatEntry
import com.rtbishop.look4sat.data.SatPass
import com.rtbishop.look4sat.data.TleSource
import com.rtbishop.look4sat.repo.Repository
import com.rtbishop.look4sat.utility.PassPredictor
import com.rtbishop.look4sat.utility.PrefsManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SharedViewModel @Inject constructor(
private val prefsManager: PrefsManager,
private val repository: Repository
) : ViewModel() {
private val _satPassList = MutableLiveData<Result<MutableList<SatPass>>>()
private val _updateStatus = MutableLiveData<Result<Int>>()
private val _gsp = MutableLiveData<Result<GroundStationPosition>>().apply {
value = Result.Success(prefsManager.getPosition())
}
private var calculationJob: Job? = null
fun getTransmittersForSat(id: Int) = liveData { emit(repository.getTransmittersByCatNum(id)) }
fun getPassList(): LiveData<Result<MutableList<SatPass>>> = _satPassList
fun getUpdateStatus(): LiveData<Result<Int>> = _updateStatus
fun getGSP(): LiveData<Result<GroundStationPosition>> = _gsp
fun getCompass() = prefsManager.getCompass()
fun getRefreshRate() = prefsManager.getRefreshRate()
fun getHoursAhead() = prefsManager.getHoursAhead()
fun getMinElevation() = prefsManager.getMinElevation()
fun setPositionFromPref() = _gsp.postValue(Result.Success(prefsManager.getPosition()))
suspend fun getAllEntries() = repository.getAllEntries()
fun setPassPrefs(hoursAhead: Int, minEl: Double) {
prefsManager.setHoursAhead(hoursAhead)
prefsManager.setMinElevation(minEl)
}
fun updatePosition() {
val lastLoc = prefsManager.getLastKnownLocation()
if (lastLoc == null) {
_gsp.postValue(Result.Error(Exception()))
} else {
val gsp = GroundStationPosition(lastLoc.latitude, lastLoc.longitude, lastLoc.altitude)
_gsp.postValue(Result.Success(gsp))
}
}
fun updateEntriesFromFile(uri: Uri) {
viewModelScope.launch(Dispatchers.IO) {
try {
val selected = repository.getSelectedEntries().map { it.catNum }
repository.updateEntriesFromFile(uri)
repository.updateEntriesSelection(selected)
_updateStatus.postValue(Result.Success(0))
} catch (e: Exception) {
_updateStatus.postValue(Result.Error(e))
}
}
}
fun updateSatData(list: List<TleSource>) {
viewModelScope.launch(Dispatchers.IO) {
try {
val selected = repository.getSelectedEntries().map { it.catNum }
repository.updateSources(list)
repository.updateEntriesFromUrl(list)
repository.updateTransmitters()
repository.updateEntriesSelection(selected)
_updateStatus.postValue(Result.Success(0))
} catch (e: Exception) {
_updateStatus.postValue(Result.Error(e))
}
}
}
fun updateEntriesSelection(catNumList: MutableList<Int>) {
viewModelScope.launch(Dispatchers.IO) {
repository.updateEntriesSelection(catNumList)
calculatePasses()
}
}
fun calculatePasses() {
_satPassList.postValue(Result.InProgress)
calculationJob?.cancel()
var passList = mutableListOf<SatPass>()
val dateNow = Date()
val gsp = prefsManager.getPosition()
calculationJob = viewModelScope.launch(Dispatchers.Default) {
repository.getSelectedEntries().forEach {
passList.addAll(getPassesForEntries(it, dateNow, gsp))
}
passList = filterAndSortPasses(passList, dateNow, getHoursAhead())
_satPassList.postValue(Result.Success(passList))
}
}
fun setDefaultTleSources() {
if (prefsManager.isFirstLaunch()) {
viewModelScope.launch {
val defSourcesList = listOf(
TleSource("https://celestrak.com/NORAD/elements/active.txt"),
TleSource("https://amsat.org/tle/current/nasabare.txt")
)
repository.updateSources(defSourcesList)
}
}
}
fun getTleSources(): List<TleSource> = runBlocking {
return@runBlocking repository.getSources()
}
private fun getPassesForEntries(
entry: SatEntry,
dateNow: Date,
gsp: GroundStationPosition
): MutableList<SatPass> {
val predictor = PassPredictor(entry.tle, gsp)
val passes = predictor.getPasses(dateNow, getHoursAhead(), true)
val passList = passes.map { SatPass(entry.tle, predictor, it) }
return passList as MutableList<SatPass>
}
private fun filterAndSortPasses(
passes: MutableList<SatPass>,
dateNow: Date,
hoursAhead: Int
): MutableList<SatPass> {
val dateFuture = Calendar.getInstance().let {
it.time = dateNow
it.add(Calendar.HOUR, hoursAhead)
it.time
}
passes.removeAll { it.pass.startTime.after(dateFuture) }
passes.removeAll { it.pass.endTime.before(dateNow) }
passes.removeAll { it.pass.maxEl < getMinElevation() }
passes.sortBy { it.pass.startTime }
return passes
}
}
@@ -1,66 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.ui.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.rtbishop.look4sat.data.SatEntry
import com.rtbishop.look4sat.databinding.ItemSatEntryBinding
class SatEntryAdapter(private var entries: MutableList<SatEntry>) :
RecyclerView.Adapter<SatEntryAdapter.SatEntryHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SatEntryHolder {
val binding = ItemSatEntryBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return SatEntryHolder(binding.root, binding)
}
override fun onBindViewHolder(holder: SatEntryHolder, position: Int) {
holder.bind(entries[position])
}
override fun getItemCount(): Int {
return entries.size
}
fun setEntries(list: MutableList<SatEntry>) {
entries = list
notifyDataSetChanged()
}
fun getEntries(): MutableList<SatEntry> {
return entries
}
inner class SatEntryHolder(itemView: View, private val binding: ItemSatEntryBinding) :
RecyclerView.ViewHolder(itemView) {
fun bind(satEntry: SatEntry) {
binding.satEntryCheckbox.text = satEntry.name
binding.satEntryCheckbox.isChecked = satEntry.isSelected
itemView.setOnClickListener {
satEntry.isSelected = binding.satEntryCheckbox.isChecked
}
}
}
}
@@ -1,156 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.ui.adapters
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.findNavController
import androidx.recyclerview.widget.RecyclerView
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.data.SatPass
import com.rtbishop.look4sat.databinding.ItemPassGeoBinding
import com.rtbishop.look4sat.databinding.ItemPassLeoBinding
import com.rtbishop.look4sat.ui.fragments.PassListFragmentDirections
import com.rtbishop.look4sat.utility.PrefsManager
import java.text.SimpleDateFormat
import java.util.*
class SatPassAdapter(
private var satPassList: MutableList<SatPass>,
private val prefsManager: PrefsManager
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
fun setList(list: MutableList<SatPass>) {
satPassList = list
notifyDataSetChanged()
}
fun updateRecycler() {
val timeNow = Date()
val iterator = satPassList.listIterator()
while (iterator.hasNext()) {
val satPass = iterator.next()
if (satPass.progress < 100) {
val timeStart = satPass.pass.startTime
val timeEnd = satPass.pass.endTime
if (timeNow.after(timeStart)) {
satPass.active = true
val index = satPassList.indexOf(satPass)
val deltaTotal = timeEnd.time.minus(timeStart.time)
val deltaNow = timeNow.time.minus(timeStart.time)
satPass.progress = ((deltaNow.toFloat() / deltaTotal.toFloat()) * 100).toInt()
notifyItemChanged(index)
}
} else {
val index = satPassList.indexOf(satPass)
iterator.remove()
notifyItemRemoved(index)
}
}
}
override fun getItemCount(): Int {
return satPassList.size
}
override fun getItemViewType(position: Int): Int {
return if (satPassList[position].tle.isDeepspace) 1
else 0
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == 0) {
val bindingLeo = ItemPassLeoBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
SatPassLeoHolder(bindingLeo.root, bindingLeo)
} else {
val bindingGeo = ItemPassGeoBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
SatPassGeoHolder(bindingGeo.root, bindingGeo)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder.itemViewType == 0) {
(holder as SatPassLeoHolder).bind(satPassList[position])
} else {
(holder as SatPassGeoHolder).bind(satPassList[position])
}
}
inner class SatPassLeoHolder(itemView: View, private val binding: ItemPassLeoBinding) :
RecyclerView.ViewHolder(itemView) {
fun bind(satPass: SatPass) {
val context: Context = itemView.context
if (satPass.active) binding.passLeoImg.setImageResource(R.drawable.ic_pass_active)
else binding.passLeoImg.setImageResource(R.drawable.ic_pass_inactive)
binding.passLeoSatName.text = satPass.tle.name
binding.passLeoSatId.text =
String.format(context.getString(R.string.pass_satId), satPass.tle.catnum)
binding.passLeoMaxEl.text =
String.format(context.getString(R.string.pat_elevation), satPass.pass.maxEl)
binding.passLeoAosAz.text =
String.format(context.getString(R.string.pass_aos_az), satPass.pass.aosAzimuth)
binding.passLeoLosAz.text =
String.format(context.getString(R.string.pass_los_az), satPass.pass.losAzimuth)
binding.passLeoProgress.progress = satPass.progress
val dateFormat =
SimpleDateFormat(context.getString(R.string.pass_dateTime), Locale.getDefault())
if (prefsManager.isTimeUTC()) dateFormat.timeZone = TimeZone.getTimeZone("UTC")
binding.passLeoAosTime.text = dateFormat.format(satPass.pass.startTime)
binding.passLeoLosTime.text = dateFormat.format(satPass.pass.endTime)
itemView.setOnClickListener {
val passIndex = satPassList.indexOf(satPass)
val action = PassListFragmentDirections.actionPassToPolar(passIndex)
itemView.findNavController().navigate(action)
}
}
}
inner class SatPassGeoHolder(itemView: View, private val binding: ItemPassGeoBinding) :
RecyclerView.ViewHolder(itemView) {
fun bind(satPass: SatPass) {
val context: Context = itemView.context
val satPos = satPass.predictor.getSatPos(satPass.pass.startTime)
val azimuth = satPos.azimuth * 180 / Math.PI
binding.passGeoName.text = satPass.tle.name
binding.passGeoId.text =
String.format(context.getString(R.string.pass_satId), satPass.tle.catnum)
binding.passGeoAz.text = String.format(context.getString(R.string.pat_azimuth), azimuth)
binding.passGeoEl.text =
String.format(context.getString(R.string.pat_elevation), satPass.pass.maxEl)
itemView.setOnClickListener {
val passIndex = satPassList.indexOf(satPass)
val action = PassListFragmentDirections.actionPassToPolar(passIndex)
itemView.findNavController().navigate(action)
}
}
}
}
@@ -1,91 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.ui.adapters
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.rtbishop.look4sat.data.TleSource
import com.rtbishop.look4sat.databinding.ItemTleSourceBinding
class TleSourcesAdapter(private var sources: MutableList<TleSource>) :
RecyclerView.Adapter<TleSourcesAdapter.TleSourceHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TleSourceHolder {
val binding = ItemTleSourceBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return TleSourceHolder(binding.root, binding, TleSourceWatcher())
}
override fun onBindViewHolder(holder: TleSourceHolder, position: Int) {
holder.textWatcher.updatePosition(position)
holder.bind(sources[position])
}
override fun getItemCount(): Int {
return sources.size
}
fun getSources(): MutableList<TleSource> {
return sources
}
fun setSources(list: MutableList<TleSource>) {
sources = list
notifyDataSetChanged()
}
inner class TleSourceHolder(
itemView: View,
val binding: ItemTleSourceBinding,
val textWatcher: TleSourceWatcher
) :
RecyclerView.ViewHolder(itemView) {
fun bind(source: TleSource) {
binding.tleSourceUrl.setText(source.url)
binding.tleSourceUrl.addTextChangedListener(textWatcher)
binding.tleSourceBtnDel.setOnClickListener {
sources.remove(source)
notifyItemRemoved(adapterPosition)
}
}
}
inner class TleSourceWatcher : TextWatcher {
private var position = 0
fun updatePosition(position: Int) {
this.position = position
}
override fun afterTextChanged(s: Editable?) {}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
sources[position].url = s.toString()
}
}
}
@@ -1,142 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.ui.adapters
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.data.Transmitter
import com.rtbishop.look4sat.databinding.ItemTransmitterBinding
import com.rtbishop.look4sat.utility.PassPredictor
import java.util.*
class TransmitterAdapter : RecyclerView.Adapter<TransmitterAdapter.TransmitterHolder>() {
private var transmittersList = emptyList<Transmitter>()
private lateinit var predictor: PassPredictor
fun setPredictor(predict: PassPredictor) {
predictor = predict
}
fun setList(list: List<Transmitter>) {
transmittersList = list
}
override fun getItemCount(): Int {
return transmittersList.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransmitterHolder {
val binding = ItemTransmitterBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return TransmitterHolder(binding.root, binding)
}
override fun onBindViewHolder(holder: TransmitterHolder, position: Int) {
holder.bind(transmittersList[position])
}
inner class TransmitterHolder(itemView: View, private val binding: ItemTransmitterBinding) :
RecyclerView.ViewHolder(itemView) {
fun bind(transmitter: Transmitter) {
val context: Context = itemView.context
val freqDivider = 1000000f
binding.transDescription.text = transmitter.description
if (transmitter.downlinkLow != null && transmitter.downlinkHigh == null) {
binding.transDownlink.text = String
.format(
context.getString(R.string.trans_down_low),
transmitter.downlinkLow / freqDivider
)
binding.transDownlinkDoppler.text = String
.format(
context.getString(R.string.trans_down_low_doppler),
predictor.getDownlinkFreq(transmitter.downlinkLow, Date()) / freqDivider
)
} else if (transmitter.downlinkLow != null && transmitter.downlinkHigh != null) {
binding.transDownlink.text = String
.format(
context.getString(R.string.trans_down_lowHigh),
transmitter.downlinkLow / freqDivider,
transmitter.downlinkHigh / freqDivider
)
binding.transDownlinkDoppler.text = String
.format(
context.getString(R.string.trans_down_lowHigh_doppler),
predictor.getDownlinkFreq(transmitter.downlinkLow, Date()) / freqDivider,
predictor.getDownlinkFreq(transmitter.downlinkHigh, Date()) / freqDivider
)
} else {
binding.transDownlink.text = context.getString(R.string.no_downlink)
binding.transDownlinkDoppler.text = context.getString(R.string.no_downlink_doppler)
}
if (transmitter.uplinkLow != null && transmitter.uplinkHigh == null) {
binding.transUplink.text = String.format(
context.getString(R.string.trans_up_low),
transmitter.uplinkLow / freqDivider
)
binding.transUplinkDoppler.text = String.format(
context.getString(R.string.trans_up_low_doppler),
predictor.getUplinkFreq(transmitter.uplinkLow, Date()) / freqDivider
)
} else if (transmitter.uplinkLow != null && transmitter.uplinkHigh != null) {
binding.transUplink.text = String.format(
context.getString(R.string.trans_up_lowHigh),
transmitter.uplinkLow / freqDivider,
transmitter.uplinkHigh / freqDivider
)
binding.transUplinkDoppler.text = String.format(
context.getString(R.string.trans_up_lowHigh_doppler),
predictor.getUplinkFreq(transmitter.uplinkLow, Date()) / freqDivider,
predictor.getUplinkFreq(transmitter.uplinkHigh, Date()) / freqDivider
)
} else {
binding.transUplink.text = context.getString(R.string.no_uplink)
binding.transUplinkDoppler.text = context.getString(R.string.no_uplink_doppler)
}
if (transmitter.mode != null) {
binding.transMode.text =
String.format(context.getString(R.string.trans_mode), transmitter.mode)
} else {
binding.transMode.text = context.getString(R.string.no_mode)
}
if (transmitter.isInverted) {
binding.transInverted.text = String.format(
context.getString(R.string.trans_inverted),
context.getString(R.string.btn_yes)
)
} else {
binding.transInverted.text = String.format(
context.getString(R.string.trans_inverted),
context.getString(R.string.btn_no)
)
}
}
}
}
@@ -1,39 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.ui.fragments
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.FragmentAboutBinding
class AboutFragment : Fragment(R.layout.fragment_about) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = FragmentAboutBinding.bind(view)
binding.tvAboutVersion.text =
String.format(getString(R.string.app_version), BuildConfig.VERSION_NAME)
binding.tvAboutMessage.movementMethod = LinkMovementMethod.getInstance()
}
}
@@ -1,339 +0,0 @@
package com.rtbishop.look4sat.ui.fragments
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.core.content.res.ResourcesCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import com.github.amsacode.predict4java.GroundStationPosition
import com.rtbishop.look4sat.Look4SatApp
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.dagger.ViewModelFactory
import com.rtbishop.look4sat.data.Result
import com.rtbishop.look4sat.data.SatItem
import com.rtbishop.look4sat.data.SatPass
import com.rtbishop.look4sat.databinding.FragmentMapOsmBinding
import com.rtbishop.look4sat.ui.MainActivity
import com.rtbishop.look4sat.ui.SharedViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.osmdroid.config.Configuration
import org.osmdroid.tileprovider.tilesource.OnlineTileSourceBase
import org.osmdroid.tileprovider.tilesource.TileSourcePolicy
import org.osmdroid.tileprovider.tilesource.XYTileSource
import org.osmdroid.util.BoundingBox
import org.osmdroid.util.GeoPoint
import org.osmdroid.views.CustomZoomButtonsController
import org.osmdroid.views.overlay.*
import java.util.*
import javax.inject.Inject
import kotlin.math.pow
import kotlin.math.sqrt
class MapOsmFragment : Fragment(R.layout.fragment_map_osm) {
@Inject
lateinit var modelFactory: ViewModelFactory
private lateinit var mainActivity: MainActivity
private lateinit var viewModel: SharedViewModel
private lateinit var binding: FragmentMapOsmBinding
private lateinit var trackPaint: Paint
private lateinit var footprintPaint: Paint
private lateinit var selectedPass: SatPass
private val dateNow = Date(System.currentTimeMillis())
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentMapOsmBinding.bind(view)
mainActivity = requireActivity() as MainActivity
(mainActivity.application as Look4SatApp).appComponent.inject(this)
viewModel = ViewModelProvider(mainActivity, modelFactory).get(SharedViewModel::class.java)
val prefs = PreferenceManager.getDefaultSharedPreferences(mainActivity.applicationContext)
Configuration.getInstance().load(mainActivity.applicationContext, prefs)
trackPaint = Paint().apply {
strokeWidth = 2f
style = Paint.Style.STROKE
color = ContextCompat.getColor(mainActivity, R.color.satTrack)
strokeCap = Paint.Cap.ROUND
strokeJoin = Paint.Join.ROUND
isAntiAlias = true
}
footprintPaint = Paint().apply {
style = Paint.Style.FILL_AND_STROKE
color = ContextCompat.getColor(mainActivity, R.color.satOsmTemp)
isAntiAlias = true
}
setupMapView()
setupObservers()
}
private fun setupMapView() {
binding.mapView.apply {
setMultiTouchControls(true)
setTileSource(getWikimediaTileSource())
minZoomLevel = 2.5
maxZoomLevel = 6.0
controller.setZoom(minZoomLevel)
zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER)
isHorizontalMapRepetitionEnabled = false
isVerticalMapRepetitionEnabled = false
overlayManager.tilesOverlay.loadingBackgroundColor = Color.TRANSPARENT
overlayManager.tilesOverlay.loadingLineColor = Color.TRANSPARENT
setScrollableAreaLimitDouble(BoundingBox(85.05, 180.0, -85.05, -180.0))
// apply filter
val tilesFilter = getColorFilter(Color.parseColor("#D50000"))
overlayManager.tilesOverlay.setColorFilter(tilesFilter)
// add overlays: 0 - GSP, 1 - SatTrack, 2 - SatFootprint, 3 - SatIcons
val layers = listOf(FolderOverlay(), FolderOverlay(), FolderOverlay(), FolderOverlay())
overlays.addAll(layers)
}
}
private fun getWikimediaTileSource(): OnlineTileSourceBase {
return XYTileSource(
"wikimedia",
2,
6,
256,
".png",
arrayOf("https://maps.wikimedia.org/osm-intl/"),
resources.getString(R.string.osmCopyright),
TileSourcePolicy(
1, TileSourcePolicy.FLAG_NO_BULK and TileSourcePolicy.FLAG_NO_PREVENTIVE and
TileSourcePolicy.FLAG_USER_AGENT_MEANINGFUL and TileSourcePolicy.FLAG_USER_AGENT_NORMALIZED
)
)
}
private fun setupObservers() {
viewModel.getGSP().observe(viewLifecycleOwner, { stationPosition ->
when (stationPosition) {
is Result.Success -> {
setupPosOverlay(stationPosition.data)
}
}
})
viewModel.getPassList().observe(viewLifecycleOwner, { satPasses ->
when (satPasses) {
is Result.Success -> {
if (satPasses.data.isNotEmpty()) {
val filteredPasses = satPasses.data.distinctBy { it.tle }
selectedPass = filteredPasses[0]
setupSatOverlay(filteredPasses)
}
}
}
})
}
private fun setupPosOverlay(gsp: GroundStationPosition) {
Marker(binding.mapView).apply {
setInfoWindow(null)
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
icon = ResourcesCompat.getDrawable(resources, R.drawable.ic_map_pos, mainActivity.theme)
position = GeoPoint(gsp.latitude, gsp.longitude)
binding.mapView.overlays[0] = this
binding.mapView.invalidate()
}
}
private fun setupSatOverlay(passList: List<SatPass>) {
lifecycleScope.launch {
while (true) {
dateNow.time = System.currentTimeMillis()
binding.mapView.overlays[3] = getSatIcons(passList)
binding.mapView.overlays[2] = getSatFootprint(selectedPass)
setSatInfo(selectedPass)
binding.mapView.invalidate()
delay(3000)
}
}
}
private suspend fun getSatIcons(passList: List<SatPass>): Overlay =
withContext(Dispatchers.Default) {
val icon =
ResourcesCompat.getDrawable(resources, R.drawable.ic_map_sat, mainActivity.theme)
val items = mutableListOf<SatItem>()
passList.forEach {
val satPos = it.predictor.getSatPos(dateNow)
var lat = Math.toDegrees(satPos.latitude)
var lon = Math.toDegrees(satPos.longitude)
if (lat > 85.05) lat = 85.05
else if (lat < -85.05) lat = -85.05
if (lon > 180.0) lon -= 360.0
SatItem(it.tle.name, it.tle.name, GeoPoint(lat, lon), it).apply {
markerHotspot = OverlayItem.HotspotPlace.CENTER
items.add(this)
}
}
val listener = object : ItemizedIconOverlay.OnItemGestureListener<SatItem> {
override fun onItemLongPress(index: Int, item: SatItem): Boolean {
return true
}
override fun onItemSingleTapUp(index: Int, item: SatItem): Boolean {
selectedPass = item.pass
setSatDetails(item.pass)
return true
}
}
return@withContext ItemizedIconOverlay(items, icon, listener, mainActivity)
}
private fun setSatDetails(satPass: SatPass) {
setSatInfo(satPass)
binding.mapView.overlays[1] = getSatTrack(satPass)
binding.mapView.overlays[2] = getSatFootprint(satPass)
binding.mapView.invalidate()
}
private fun setSatInfo(satPass: SatPass) {
val satPos = satPass.predictor.getSatPos(dateNow)
val satLat = Math.toDegrees(satPos.latitude).toFloat()
var satLon = Math.toDegrees(satPos.longitude).toFloat()
if (satLon > 180f) satLon -= 360f
binding.idName.text =
String.format(
mainActivity.getString(R.string.pat_osm_idName),
satPass.tle.catnum,
satPass.tle.name
)
binding.altitude.text =
String.format(mainActivity.getString(R.string.pat_altitude), satPos.altitude)
binding.distance.text =
String.format(mainActivity.getString(R.string.pat_distance), satPos.range)
binding.velocity.text =
String.format(
mainActivity.getString(R.string.pat_osm_vel),
getSatVelocity(satPos.altitude)
)
binding.latLon.text =
String.format(mainActivity.getString(R.string.pat_osm_latLon), satLat, satLon)
}
private fun getSatVelocity(satAlt: Double): Double {
val earthG = 6.674 * 10.0.pow(-11)
val earthM = 5.98 * 10.0.pow(24)
val radius = 6.37 * 10.0.pow(6) + satAlt * 10.0.pow(3)
return sqrt(earthG * earthM / radius) / 1000
}
private fun getSatTrack(pass: SatPass): Overlay {
val period = (24 * 60 / pass.tle.meanmo).toInt()
val positions = pass.predictor.getPositions(dateNow, 20, 0, period * 3)
val trackOverlay = FolderOverlay()
val trackPoints = mutableListOf<GeoPoint>()
var oldLon = 0.0
positions.forEach {
val newLat = Math.toDegrees(it.latitude)
var newLon = Math.toDegrees(it.longitude)
if (newLon > 180.0) newLon -= 360.0
if (oldLon < -170.0 && newLon > 170.0 || oldLon > 170.0 && newLon < -170.0) {
val currentPoints = mutableListOf<GeoPoint>()
currentPoints.addAll(trackPoints)
Polyline().apply {
outlinePaint.set(trackPaint)
setPoints(currentPoints)
trackOverlay.add(this)
}
trackPoints.clear()
}
oldLon = newLon
trackPoints.add(GeoPoint(newLat, newLon))
}
Polyline().apply {
outlinePaint.set(trackPaint)
setPoints(trackPoints)
trackOverlay.add(this)
}
return trackOverlay
}
private fun getSatFootprint(pass: SatPass): Overlay {
val rangeCircle = pass.predictor.getSatPos(dateNow).rangeCircle
val rangePoints = mutableListOf<GeoPoint>()
var zeroPoint = GeoPoint(0.0, 0.0)
rangeCircle.withIndex().forEach {
var lat = it.value.lat
var lon = it.value.lon
if (lat > 85.05) lat = 85.05
else if (lat < -85.05) lat = -85.05
if (lon > 180.0) lon -= 360.0
if (it.index == 0) zeroPoint = GeoPoint(lat, lon)
rangePoints.add(GeoPoint(lat, lon))
}
rangePoints.add(zeroPoint)
return Polygon().apply {
fillPaint.set(footprintPaint)
outlinePaint.set(footprintPaint)
points = rangePoints
}
}
private fun getColorFilter(targetColor: Int): ColorMatrixColorFilter {
val newR = Color.red(targetColor) / 255f
val newG = Color.green(targetColor) / 255f
val newB = Color.blue(targetColor) / 255f
val negativeMatrix = ColorMatrix(
floatArrayOf(
-1f, 0f, 0f, 0f, 255f,
0f, -1f, 0f, 0f, 255f,
0f, 0f, -1f, 0f, 255f,
0f, 0f, 0f, 1f, 0f
)
)
val tintedMatrix = ColorMatrix(
floatArrayOf(
newR, newG, newB, 0f, 0f,
newR, newG, newB, 0f, 0f,
newR, newG, newB, 0f, 0f,
0f, 0f, 0f, 0f, 255f
)
)
tintedMatrix.preConcat(negativeMatrix)
return ColorMatrixColorFilter(tintedMatrix)
}
override fun onPause() {
binding.mapView.onPause()
super.onPause()
}
override fun onResume() {
super.onResume()
binding.mapView.onResume()
}
}
@@ -1,149 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.ui.fragments
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.github.amsacode.predict4java.GroundStationPosition
import com.github.amsacode.predict4java.TLE
import com.rtbishop.look4sat.Look4SatApp
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.dagger.ViewModelFactory
import com.rtbishop.look4sat.data.Result
import com.rtbishop.look4sat.data.SatPass
import com.rtbishop.look4sat.databinding.FragmentMapViewBinding
import com.rtbishop.look4sat.ui.MainActivity
import com.rtbishop.look4sat.ui.SharedViewModel
import com.rtbishop.look4sat.ui.views.MapView
import com.rtbishop.look4sat.utility.Extensions.toast
import com.rtbishop.look4sat.utility.PassPredictor
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.*
import javax.inject.Inject
class MapViewFragment : Fragment(R.layout.fragment_map_view) {
@Inject
lateinit var modelFactory: ViewModelFactory
private lateinit var mainActivity: MainActivity
private lateinit var viewModel: SharedViewModel
private lateinit var mapView: MapView
private lateinit var predictor: PassPredictor
private lateinit var selectedSat: TLE
private lateinit var binding: FragmentMapViewBinding
private var gsp = GroundStationPosition(0.0, 0.0, 0.0)
private var satPassList = emptyList<SatPass>()
private var checkedItem = 0
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentMapViewBinding.bind(view)
mainActivity = requireActivity() as MainActivity
(mainActivity.application as Look4SatApp).appComponent.inject(this)
viewModel = ViewModelProvider(mainActivity, modelFactory).get(SharedViewModel::class.java)
setupObservers(binding)
}
private fun setupObservers(binding: FragmentMapViewBinding) {
viewModel.getGSP().observe(viewLifecycleOwner, { result ->
when (result) {
is Result.Success -> gsp = result.data
}
})
viewModel.getPassList().observe(viewLifecycleOwner, { result ->
when (result) {
is Result.Success -> {
satPassList = result.data
setupMapView(binding)
}
}
})
}
private fun setupMapView(binding: FragmentMapViewBinding) {
if (satPassList.isNotEmpty()) {
satPassList = satPassList.distinctBy { it.tle }
satPassList = satPassList.sortedBy { it.tle.name }
binding.fabMap.setOnClickListener { showSelectSatDialog(satPassList) }
selectedSat = satPassList.first().tle
predictor = satPassList.first().predictor
mapView = MapView(mainActivity, gsp)
mapView.setList(satPassList)
mapView.setChecked(checkedItem)
binding.frameMap.addView(mapView)
refreshView()
} else {
binding.fabMap.setOnClickListener {
getString(R.string.err_no_sat_selected).toast(mainActivity)
}
}
}
private fun refreshView() {
lifecycleScope.launch {
while (true) {
mapView.invalidate()
setDataToTextViews()
delay(viewModel.getRefreshRate())
}
}
}
private fun showSelectSatDialog(list: List<SatPass>) {
val tleArray = arrayOfNulls<String>(list.size).apply {
list.withIndex().forEach {
this[it.index] = it.value.tle.name
}
}
val builder = AlertDialog.Builder(mainActivity)
builder.setTitle(getString(R.string.dialog_show_track))
.setSingleChoiceItems(tleArray, checkedItem) { dialog, which ->
checkedItem = which
selectedSat = list[which].tle
predictor = list[which].predictor
mapView.setChecked(checkedItem)
mapView.invalidate()
dialog.dismiss()
}
.create()
.show()
}
private fun setDataToTextViews() {
val currentTime = Date(System.currentTimeMillis())
val position = predictor.getSatPos(currentTime)
var lon = Math.toDegrees(position.longitude).toFloat()
val lat = Math.toDegrees(position.latitude).toFloat()
val rng = position.range
if (lon > 180f) lon -= 360f
binding.tvMapLat.text = String.format(mainActivity.getString(R.string.pat_latitude), lat)
binding.tvMapLon.text = String.format(mainActivity.getString(R.string.pat_longitude), lon)
binding.tvMapRng.text = String.format(mainActivity.getString(R.string.pat_distance), rng)
}
}
@@ -1,281 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.ui.fragments
import android.os.Bundle
import android.os.CountDownTimer
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.ImageButton
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.SimpleItemAnimator
import com.rtbishop.look4sat.Look4SatApp
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.dagger.ViewModelFactory
import com.rtbishop.look4sat.data.Result
import com.rtbishop.look4sat.data.SatEntry
import com.rtbishop.look4sat.data.SatPass
import com.rtbishop.look4sat.databinding.FragmentPassListBinding
import com.rtbishop.look4sat.ui.MainActivity
import com.rtbishop.look4sat.ui.SharedViewModel
import com.rtbishop.look4sat.ui.adapters.SatPassAdapter
import com.rtbishop.look4sat.utility.Extensions.toast
import com.rtbishop.look4sat.utility.PrefsManager
import kotlinx.coroutines.launch
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class PassListFragment : Fragment(R.layout.fragment_pass_list) {
@Inject
lateinit var modelFactory: ViewModelFactory
@Inject
lateinit var prefsManager: PrefsManager
private lateinit var viewModel: SharedViewModel
private lateinit var satPassAdapter: SatPassAdapter
private lateinit var btnPassPrefs: ImageButton
private lateinit var aosTimer: CountDownTimer
private lateinit var aosTimerText: TextView
private lateinit var mainActivity: MainActivity
private var isTimerSet: Boolean = false
private var satPassList: MutableList<SatPass> = mutableListOf()
private var _binding: FragmentPassListBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentPassListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mainActivity = activity as MainActivity
(mainActivity.application as Look4SatApp).appComponent.inject(this)
viewModel = ViewModelProvider(mainActivity, modelFactory).get(SharedViewModel::class.java)
satPassAdapter = SatPassAdapter(satPassList, prefsManager)
aosTimerText = mainActivity.findViewById(R.id.toolbar_timer)
btnPassPrefs = mainActivity.findViewById(R.id.toolbar_filter)
setupObservers(binding)
setupComponents(binding)
}
override fun onStart() {
super.onStart()
if (satPassAdapter.itemCount == 0) {
viewModel.calculatePasses()
}
}
private fun setupObservers(binding: FragmentPassListBinding) {
viewModel.getPassList().observe(viewLifecycleOwner, { result ->
when (result) {
is Result.Success -> {
satPassList = result.data
satPassAdapter.setList(satPassList)
if (satPassList.isNotEmpty()) {
binding.layoutInfo.visibility = View.INVISIBLE
binding.recPassList.visibility = View.VISIBLE
} else {
binding.layoutInfo.visibility = View.VISIBLE
binding.recPassList.visibility = View.INVISIBLE
}
setTimer()
binding.refLayoutPassList.isRefreshing = false
}
is Result.InProgress -> {
binding.refLayoutPassList.isRefreshing = true
}
}
})
}
private fun setupComponents(binding: FragmentPassListBinding) {
binding.recPassList.apply {
layoutManager = LinearLayoutManager(mainActivity)
adapter = satPassAdapter
isVerticalScrollBarEnabled = false
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
satPassAdapter.setList(satPassList)
setTimer()
binding.refLayoutPassList.setProgressBackgroundColorSchemeResource(R.color.themeAccent)
binding.refLayoutPassList.setColorSchemeResources(R.color.backgroundDark)
binding.refLayoutPassList.setOnRefreshListener { viewModel.calculatePasses() }
btnPassPrefs.setOnClickListener {
showSatPassPrefsDialog(viewModel.getHoursAhead(), viewModel.getMinElevation())
}
binding.fabSatSelect.setOnClickListener {
lifecycleScope.launch {
val list = viewModel.getAllEntries() as MutableList
if (list.isEmpty()) {
getString(R.string.err_update_data).toast(mainActivity, Toast.LENGTH_SHORT)
} else {
showSelectSatDialog(list)
}
}
}
}
private fun showSatPassPrefsDialog(hoursAhead: Int, minEl: Double) {
val satPassPrefView = View.inflate(mainActivity, R.layout.dialog_pass_pref, null)
val etHoursAhead = satPassPrefView.findViewById<EditText>(R.id.pref_et_hoursAhead)
val etMinEl = satPassPrefView.findViewById<EditText>(R.id.pref_et_minEl)
etHoursAhead.setText(hoursAhead.toString())
etMinEl.setText(minEl.toString())
AlertDialog.Builder(mainActivity).apply {
setPositiveButton(getString(android.R.string.ok)) { _, _ ->
val hoursStr = etHoursAhead.text.toString()
val elevationStr = etMinEl.text.toString()
if (hoursStr.isNotEmpty() && elevationStr.isNotEmpty()) {
val hours = hoursStr.toInt()
val elevation = elevationStr.toDouble()
when {
hours < 1 || hours > 168 -> {
getString(R.string.pref_hours_ahead_input_error).toast(
mainActivity,
Toast.LENGTH_SHORT
)
}
elevation < 0 || elevation > 90 -> {
getString(R.string.pref_min_el_input_error).toast(
mainActivity,
Toast.LENGTH_SHORT
)
}
else -> {
viewModel.setPassPrefs(hours, elevation)
viewModel.calculatePasses()
}
}
} else getString(R.string.err_enter_value).toast(mainActivity, Toast.LENGTH_SHORT)
}
setNegativeButton(getString(android.R.string.cancel)) { dialog, _ ->
dialog.cancel()
}
setView(satPassPrefView)
create()
show()
}
}
private fun showSelectSatDialog(tleMainList: MutableList<SatEntry>) {
val listener = object : SatEntryDialogFragment.EntriesSubmitListener {
override fun onEntriesSubmit(catNumList: MutableList<Int>) {
viewModel.updateEntriesSelection(catNumList)
}
}
SatEntryDialogFragment(tleMainList).apply {
setEntriesListener(listener)
show(mainActivity.supportFragmentManager, "SatEntryDialogFragment")
}
}
private fun setTimer() {
if (satPassList.isNotEmpty()) {
resetTimer()
val timeNow = Date()
try {
setTimerForNext(timeNow)
} catch (e: NoSuchElementException) {
setTimerForLast(timeNow)
} finally {
aosTimer.start()
isTimerSet = true
}
} else {
resetTimer(true)
}
}
private fun setTimerForNext(timeNow: Date) {
val nextPass = satPassList.first { it.pass.startTime.after(timeNow) }
val millisBeforeStart = nextPass.pass.startTime.time.minus(timeNow.time)
aosTimer = object : CountDownTimer(millisBeforeStart, 1000) {
override fun onFinish() {
setTimer()
}
override fun onTick(millisUntilFinished: Long) {
aosTimerText.text = String.format(
mainActivity.getString(R.string.pat_timer),
TimeUnit.MILLISECONDS.toHours(millisUntilFinished) % 60,
TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished) % 60,
TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished) % 60
)
satPassAdapter.updateRecycler()
}
}
}
private fun setTimerForLast(timeNow: Date) {
val lastPass = satPassList.last()
val millisBeforeEnd = lastPass.pass.endTime.time.minus(timeNow.time)
aosTimer = object : CountDownTimer(millisBeforeEnd, 1000) {
override fun onFinish() {
viewModel.calculatePasses()
}
override fun onTick(millisUntilFinished: Long) {
aosTimerText.text = String.format(
mainActivity.getString(R.string.pat_timer),
TimeUnit.MILLISECONDS.toHours(millisUntilFinished) % 60,
TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished) % 60,
TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished) % 60
)
satPassAdapter.updateRecycler()
}
}
}
private fun resetTimer(resetToNull: Boolean = false) {
if (isTimerSet) {
aosTimer.cancel()
isTimerSet = false
}
if (resetToNull) aosTimerText.text =
String.format(getString(R.string.pat_timer), 0, 0, 0)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
@@ -1,173 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.ui.fragments
import android.content.Context
import android.hardware.*
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import com.rtbishop.look4sat.Look4SatApp
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.dagger.ViewModelFactory
import com.rtbishop.look4sat.data.Result
import com.rtbishop.look4sat.data.SatPass
import com.rtbishop.look4sat.databinding.FragmentPolarViewBinding
import com.rtbishop.look4sat.ui.MainActivity
import com.rtbishop.look4sat.ui.SharedViewModel
import com.rtbishop.look4sat.ui.adapters.TransmitterAdapter
import com.rtbishop.look4sat.ui.views.PolarView
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.*
import javax.inject.Inject
import kotlin.math.round
class PolarViewFragment : Fragment(R.layout.fragment_polar_view), SensorEventListener {
@Inject
lateinit var modelFactory: ViewModelFactory
private lateinit var mainActivity: MainActivity
private lateinit var viewModel: SharedViewModel
private lateinit var binding: FragmentPolarViewBinding
private lateinit var satPass: SatPass
private lateinit var sensorManager: SensorManager
private var polarView: PolarView? = null
private val args: PolarViewFragmentArgs by navArgs()
private val transmitterAdapter = TransmitterAdapter()
private var magneticDeclination = 0f
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mainActivity = activity as MainActivity
binding = FragmentPolarViewBinding.bind(view)
(mainActivity.application as Look4SatApp).appComponent.inject(this)
viewModel = ViewModelProvider(mainActivity, modelFactory).get(SharedViewModel::class.java)
sensorManager = mainActivity.getSystemService(Context.SENSOR_SERVICE) as SensorManager
binding.recPolar.apply {
layoutManager = LinearLayoutManager(mainActivity)
adapter = transmitterAdapter
}
observePasses()
observeStationPosition()
}
override fun onResume() {
super.onResume()
if (viewModel.getCompass()) {
sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR).also { sensor ->
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL)
}
}
}
override fun onPause() {
super.onPause()
if (viewModel.getCompass()) {
sensorManager.unregisterListener(this)
}
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
override fun onSensorChanged(event: SensorEvent?) {
event?.let { calculateAzimuth(it) }
}
private fun calculateAzimuth(event: SensorEvent) {
val rotationValues = FloatArray(9)
SensorManager.getRotationMatrixFromVector(rotationValues, event.values)
val orientationValues = FloatArray(3)
SensorManager.getOrientation(rotationValues, orientationValues)
val magneticAzimuth = ((orientationValues[0] * 57.2957795f) + 360f) % 360f
val roundedAzimuth = round(magneticAzimuth * 100) / 100
polarView?.rotation = -(roundedAzimuth + magneticDeclination)
}
private fun observePasses() {
viewModel.getPassList().observe(viewLifecycleOwner, { result ->
if (result is Result.Success) {
val refreshRate = viewModel.getRefreshRate()
satPass = result.data[args.satPassIndex]
mainActivity.supportActionBar?.title = satPass.tle.name
polarView = PolarView(mainActivity, satPass)
binding.framePolar.addView(polarView)
observeTransmitters()
refreshText(refreshRate)
}
})
}
private fun observeTransmitters() {
viewModel.getTransmittersForSat(satPass.tle.catnum).observe(viewLifecycleOwner, {
if (it.isNotEmpty()) {
transmitterAdapter.setPredictor(satPass.predictor)
transmitterAdapter.setList(it)
transmitterAdapter.notifyDataSetChanged()
binding.recPolar.visibility = View.VISIBLE
binding.tvPolarNoTrans.visibility = View.INVISIBLE
} else {
binding.recPolar.visibility = View.INVISIBLE
binding.tvPolarNoTrans.visibility = View.VISIBLE
}
})
}
private fun observeStationPosition() {
viewModel.getGSP().observe(viewLifecycleOwner, { result ->
if (result is Result.Success) {
val geoField = GeomagneticField(
result.data.latitude.toFloat(),
result.data.longitude.toFloat(),
result.data.heightAMSL.toFloat(),
System.currentTimeMillis()
)
magneticDeclination = geoField.declination
}
})
}
private fun refreshText(rate: Long) {
lifecycleScope.launch {
while (true) {
setPassText()
polarView?.invalidate()
transmitterAdapter.notifyDataSetChanged()
delay(rate)
}
}
}
private fun setPassText() {
val satPos = satPass.predictor.getSatPos(Date())
val polarAz = mainActivity.getString(R.string.pat_azimuth)
val polarEl = mainActivity.getString(R.string.pat_elevation)
val polarRng = mainActivity.getString(R.string.pat_distance)
val polarAlt = mainActivity.getString(R.string.pat_altitude)
binding.tvPolarAz.text = String.format(polarAz, Math.toDegrees(satPos.azimuth))
binding.tvPolarEl.text = String.format(polarEl, Math.toDegrees(satPos.elevation))
binding.tvPolarRng.text = String.format(polarRng, satPos.range)
binding.tvPolarAlt.text = String.format(polarAlt, satPos.altitude)
}
}
@@ -1,133 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.ui.fragments
import android.content.SharedPreferences
import android.os.Bundle
import android.text.InputType
import androidx.lifecycle.ViewModelProvider
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceFragmentCompat
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.ui.MainActivity
import com.rtbishop.look4sat.ui.SharedViewModel
import com.rtbishop.look4sat.utility.Extensions.toast
class PreferencesFragment : PreferenceFragmentCompat(),
SharedPreferences.OnSharedPreferenceChangeListener {
private lateinit var mainActivity: MainActivity
private lateinit var viewModel: SharedViewModel
private lateinit var keyLat: String
private lateinit var keyLon: String
private lateinit var keyAlt: String
private lateinit var keyDelay: String
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preference, rootKey)
val provider = EditTextPreference.SimpleSummaryProvider.getInstance()
mainActivity = activity as MainActivity
viewModel = ViewModelProvider(mainActivity).get(SharedViewModel::class.java)
keyLat = mainActivity.getString(R.string.pref_lat_key)
keyLon = mainActivity.getString(R.string.pref_lon_key)
keyAlt = mainActivity.getString(R.string.pref_alt_key)
keyDelay = mainActivity.getString(R.string.pref_refresh_rate_key)
findPreference<EditTextPreference>(keyLat)?.apply {
summaryProvider = provider
setOnBindEditTextListener {
it.inputType = InputType.TYPE_CLASS_NUMBER or
InputType.TYPE_NUMBER_FLAG_DECIMAL or
InputType.TYPE_NUMBER_FLAG_SIGNED
}
setOnPreferenceChangeListener { _, newValue ->
val valueStr = newValue.toString()
if (valueStr.isEmpty() || valueStr == "-" || valueStr.toDouble() < -90.0 || valueStr.toDouble() > 90.0) {
getString(R.string.pref_lat_input_error).toast(mainActivity)
return@setOnPreferenceChangeListener false
}
return@setOnPreferenceChangeListener true
}
}
findPreference<EditTextPreference>(keyLon)?.apply {
summaryProvider = provider
setOnBindEditTextListener {
it.inputType = InputType.TYPE_CLASS_NUMBER or
InputType.TYPE_NUMBER_FLAG_DECIMAL or
InputType.TYPE_NUMBER_FLAG_SIGNED
}
setOnPreferenceChangeListener { _, newValue ->
val valueStr = newValue.toString()
if (valueStr.isEmpty() || valueStr == "-" || valueStr.toDouble() < -180.0 || valueStr.toDouble() > 180.0) {
getString(R.string.pref_lon_input_error).toast(mainActivity)
return@setOnPreferenceChangeListener false
}
return@setOnPreferenceChangeListener true
}
}
findPreference<EditTextPreference>(keyAlt)?.apply {
summaryProvider = provider
setOnBindEditTextListener {
it.inputType = InputType.TYPE_CLASS_NUMBER or
InputType.TYPE_NUMBER_FLAG_DECIMAL or
InputType.TYPE_NUMBER_FLAG_SIGNED
}
setOnPreferenceChangeListener { _, newValue ->
val valueStr = newValue.toString()
if (valueStr.isEmpty() || valueStr == "-" || valueStr.toDouble() < -413.0 || valueStr.toDouble() > 8850.0) {
getString(R.string.pref_alt_input_error).toast(mainActivity)
return@setOnPreferenceChangeListener false
}
return@setOnPreferenceChangeListener true
}
}
findPreference<EditTextPreference>(keyDelay)?.apply {
summaryProvider = provider
setOnBindEditTextListener { it.inputType = InputType.TYPE_CLASS_NUMBER }
setOnPreferenceChangeListener { _, newValue ->
val valueStr = newValue.toString()
if (valueStr.isEmpty() || valueStr.toLong() < 250 || valueStr.toLong() > 10000) {
getString(R.string.pref_refresh_rate_input_error).toast(mainActivity)
return@setOnPreferenceChangeListener false
}
return@setOnPreferenceChangeListener true
}
}
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == keyLat || key == keyLon || key == keyAlt) viewModel.setPositionFromPref()
}
override fun onResume() {
super.onResume()
preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
}
override fun onPause() {
super.onPause()
preferenceManager.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
}
}
@@ -1,138 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.ui.fragments
import android.app.Dialog
import android.os.Bundle
import android.view.ViewGroup
import android.view.Window
import android.widget.SearchView
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.recyclerview.widget.LinearLayoutManager
import com.rtbishop.look4sat.data.SatEntry
import com.rtbishop.look4sat.databinding.DialogSatEntryBinding
import com.rtbishop.look4sat.ui.adapters.SatEntryAdapter
import java.util.*
class SatEntryDialogFragment(private var entries: MutableList<SatEntry>) :
AppCompatDialogFragment(), SearchView.OnQueryTextListener {
private lateinit var entriesListener: EntriesSubmitListener
private val entryAdapter = SatEntryAdapter(entries)
private var selectAllToggle = true
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val binding = DialogSatEntryBinding.inflate(requireActivity().layoutInflater)
val satEntryDialog = Dialog(requireActivity()).apply {
window?.requestFeature(Window.FEATURE_NO_TITLE)
setContentView(binding.root)
window?.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
binding.dialogSearch.apply {
setOnQueryTextListener(this@SatEntryDialogFragment)
onActionViewExpanded()
clearFocus()
}
binding.dialogRecycler.apply {
layoutManager = LinearLayoutManager(activity)
adapter = entryAdapter
}
binding.dialogBtnPositive.setOnClickListener { onPositiveClicked() }
binding.dialogBtnNegative.setOnClickListener { onNegativeClicked() }
binding.dialogBtnNeutral.setOnClickListener { onNeutralClicked() }
return satEntryDialog
}
fun setEntriesListener(listener: EntriesSubmitListener): SatEntryDialogFragment {
entriesListener = listener
return this
}
private fun filterEntries(list: MutableList<SatEntry>, query: String): MutableList<SatEntry> {
if (query.isEmpty()) return list
return try {
filterByCatNum(list, query.toInt())
} catch (e: NumberFormatException) {
filterByName(list, query)
}
}
private fun filterByCatNum(list: MutableList<SatEntry>, catNum: Int): MutableList<SatEntry> {
val filteredList = list.filter { it.catNum == catNum }
return filteredList as MutableList<SatEntry>
}
private fun filterByName(list: MutableList<SatEntry>, query: String): MutableList<SatEntry> {
val searchQuery = query.toLowerCase(Locale.getDefault())
val filteredList = mutableListOf<SatEntry>()
list.forEach {
val name = it.name.toLowerCase(Locale.getDefault())
if (name.contains(searchQuery)) filteredList.add(it)
}
return filteredList
}
private fun onPositiveClicked() {
val catNumList = mutableListOf<Int>().apply {
entries.forEach { if (it.isSelected) this.add(it.catNum) }
}
entriesListener.onEntriesSubmit(catNumList)
dismiss()
}
private fun onNegativeClicked() {
dismiss()
}
private fun onNeutralClicked() {
selectAllToggle = if (selectAllToggle) {
val currentList = entryAdapter.getEntries()
currentList.forEach { it.isSelected = true }
entryAdapter.setEntries(currentList)
false
} else {
val currentList = entryAdapter.getEntries()
currentList.forEach { it.isSelected = false }
entryAdapter.setEntries(currentList)
true
}
}
override fun onQueryTextChange(newText: String): Boolean {
val filteredList = filterEntries(entries, newText)
entryAdapter.setEntries(filteredList)
return false
}
override fun onQueryTextSubmit(query: String): Boolean {
return false
}
interface EntriesSubmitListener {
fun onEntriesSubmit(catNumList: MutableList<Int>)
}
}
@@ -1,81 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.ui.fragments
import android.app.Dialog
import android.os.Bundle
import android.view.ViewGroup
import android.view.Window
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.recyclerview.widget.LinearLayoutManager
import com.rtbishop.look4sat.data.TleSource
import com.rtbishop.look4sat.databinding.DialogTleSourcesBinding
import com.rtbishop.look4sat.ui.adapters.TleSourcesAdapter
class TleSourcesDialogFragment(sources: List<TleSource>) :
AppCompatDialogFragment() {
private lateinit var sourcesListener: SourcesSubmitListener
private val emptySource = TleSource("")
private val sourcesAdapter = TleSourcesAdapter(sources.toMutableList())
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val binding = DialogTleSourcesBinding.inflate(requireActivity().layoutInflater)
val tleSourcesDialog = Dialog(requireActivity()).apply {
window?.requestFeature(Window.FEATURE_NO_TITLE)
setContentView(binding.root)
window?.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
binding.tleSourcesRecycler.apply {
layoutManager = LinearLayoutManager(activity)
adapter = sourcesAdapter
}
binding.tleSourceBtnAdd.setOnClickListener {
val tempSources = sourcesAdapter.getSources()
tempSources.add(emptySource)
sourcesAdapter.setSources(tempSources)
sourcesAdapter.notifyDataSetChanged()
}
binding.tleSourcesBtnNeg.setOnClickListener { dismiss() }
binding.tleSourcesBtnPos.setOnClickListener {
val filteredSources = sourcesAdapter.getSources()
.filter { it.url != String() && it.url != " " && it.url.contains("https://") }
sourcesListener.onSourcesSubmit(filteredSources)
dismiss()
}
return tleSourcesDialog
}
fun setSourcesListener(listener: SourcesSubmitListener): TleSourcesDialogFragment {
sourcesListener = listener
return this
}
interface SourcesSubmitListener {
fun onSourcesSubmit(list: List<TleSource>)
}
}
@@ -1,171 +0,0 @@
package com.rtbishop.look4sat.ui.views
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.*
import android.view.View
import androidx.core.content.ContextCompat
import com.github.amsacode.predict4java.GroundStationPosition
import com.github.amsacode.predict4java.Position
import com.github.amsacode.predict4java.SatPos
import com.github.amsacode.predict4java.TLE
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.data.SatPass
import java.util.*
import kotlin.math.abs
class MapView(context: Context) : View(context) {
constructor(context: Context, groundStationPosition: GroundStationPosition) : this(context) {
gsp = groundStationPosition
}
private lateinit var passList: List<SatPass>
private lateinit var gsp: GroundStationPosition
private lateinit var selectedSat: TLE
private val scale = resources.displayMetrics.density
private val groundTrackPaint = Paint().apply {
isAntiAlias = true
color = ContextCompat.getColor(context, R.color.satTrack)
style = Paint.Style.STROKE
strokeWidth = scale
}
private val satPaintPrim = Paint().apply {
isAntiAlias = true
color = ContextCompat.getColor(context, R.color.satPrimary)
style = Paint.Style.STROKE
strokeWidth = scale
}
private val satPaintSec = Paint().apply {
isAntiAlias = true
color = ContextCompat.getColor(context, R.color.satSecondary)
style = Paint.Style.STROKE
strokeWidth = scale
}
private val txtPaint = Paint().apply {
isAntiAlias = true
color = ContextCompat.getColor(context, R.color.satPrimary)
style = Paint.Style.FILL
strokeWidth = scale
textSize = 16f
setShadowLayer(2f, 2f, 2f, Color.BLACK)
}
private val outlinePaint = Paint().apply {
isAntiAlias = true
color = Color.BLACK
style = Paint.Style.FILL_AND_STROKE
strokeWidth = scale
textSize = 16f
}
private val rect = Rect()
private var checked = 0
private var degLon = 0f
private var degLat = 0f
fun setList(satPassList: List<SatPass>) {
passList = satPassList
}
fun setChecked(item: Int) {
checked = item
selectedSat = passList[checked].tle
}
@SuppressLint("DrawAllocation")
override fun onDraw(canvas: Canvas) {
degLon = width / 360f
degLat = height / 180f
canvas.translate(width / 2f, height / 2f)
drawHomeLoc(canvas)
val currentTime = Date(System.currentTimeMillis())
val orbitalPeriod = (24 * 60 / selectedSat.meanmo).toInt()
val predictor = passList[checked].predictor
val positions = predictor.getPositions(currentTime, 60, 0, orbitalPeriod * 3)
drawGroundTrack(canvas, positions)
passList.forEach { satPass -> drawSat(canvas, satPass, currentTime, satPaintSec) }
drawSat(canvas, passList[checked], currentTime, satPaintPrim)
}
private fun drawHomeLoc(canvas: Canvas) {
val lon = gsp.longitude.toFloat()
val lat = gsp.latitude.toFloat() * -1
val cx = lon * degLon
val cy = lat * degLat
val gspName = context.getString(R.string.map_gsp)
txtPaint.getTextBounds(gspName, 0, gspName.length, rect)
canvas.drawCircle(cx, cy, scale * 2, txtPaint)
canvas.drawText(gspName, cx - rect.width() / 2, cy - txtPaint.textSize, outlinePaint)
canvas.drawText(gspName, cx - rect.width() / 2, cy - txtPaint.textSize, txtPaint)
}
private fun drawGroundTrack(canvas: Canvas, list: List<SatPos>) {
val path = Path()
var lon: Float
var lat: Float
var lastLon = 181f
list.withIndex().forEach { (index, satPos) ->
lon = Math.toDegrees(satPos.longitude).toFloat()
lat = Math.toDegrees(satPos.latitude).toFloat() * -1
if (lon > 180f) lon -= 360f
lon *= degLon
lat *= degLat
if (index == 0 || abs(lon - lastLon) > 180) path.moveTo(lon, lat)
else path.lineTo(lon, lat)
lastLon = lon
}
canvas.drawPath(path, groundTrackPaint)
}
private fun drawSat(canvas: Canvas, satPass: SatPass, date: Date, paint: Paint) {
val tle = satPass.tle
val predictor = satPass.predictor
val satPosNow = predictor.getSatPos(date)
val footprintPosList = satPosNow.rangeCircle
drawFootprint(canvas, footprintPosList, paint)
drawName(canvas, satPosNow, tle.name)
}
private fun drawFootprint(canvas: Canvas, list: List<Position>, paint: Paint) {
val path = Path()
var lon: Float
var lat: Float
var lastLon = 181f
list.withIndex().forEach { (index, position) ->
lon = position.lon.toFloat()
lat = position.lat.toFloat() * -1
if (lon > 180f) lon -= 360f
lon *= degLon
lat *= degLat
if (index == 0 || abs(lon - lastLon) > 180) path.moveTo(lon, lat)
else path.lineTo(lon, lat)
lastLon = lon
}
canvas.drawPath(path, paint)
}
private fun drawName(canvas: Canvas, position: SatPos, name: String) {
var lon = Math.toDegrees(position.longitude).toFloat()
val lat = Math.toDegrees(position.latitude).toFloat() * -1
if (lon > 180f) lon -= 360f
val cx = lon * degLon
val cy = lat * degLat
canvas.drawCircle(cx, cy, scale * 2, txtPaint)
txtPaint.getTextBounds(name, 0, name.length, rect)
canvas.drawText(name, cx - rect.width() / 2, cy - txtPaint.textSize, outlinePaint)
canvas.drawText(name, cx - rect.width() / 2, cy - txtPaint.textSize, txtPaint)
}
}
@@ -1,117 +0,0 @@
package com.rtbishop.look4sat.ui.views
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.view.View
import androidx.core.content.ContextCompat
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.data.SatPass
import java.util.*
import kotlin.math.cos
import kotlin.math.sin
class PolarView(context: Context) : View(context) {
constructor(context: Context, satPass: SatPass) : this(context) {
pass = satPass
}
private lateinit var pass: SatPass
private val polarWidth = resources.displayMetrics.widthPixels
private val polarCenter = polarWidth / 2f
private val scale = resources.displayMetrics.density
private val radius = polarWidth * 0.49f
private val txtSize = scale * 15
private val path: Path = Path()
private var azimuth = 0f
private val radarPaint = Paint().apply {
isAntiAlias = true
color = ContextCompat.getColor(context, R.color.themeLight)
style = Paint.Style.STROKE
strokeWidth = scale
}
private val txtPaint = Paint().apply {
isAntiAlias = true
color = ContextCompat.getColor(context, R.color.themeAccent)
textSize = txtSize
}
private val trackPaint = Paint().apply {
isAntiAlias = true
color = ContextCompat.getColor(context, R.color.satTrack)
style = Paint.Style.STROKE
strokeWidth = scale
}
private val satPaint = Paint().apply {
isAntiAlias = true
color = ContextCompat.getColor(context, R.color.themeAccent)
style = Paint.Style.FILL
}
override fun onDraw(canvas: Canvas) {
canvas.rotate(-azimuth, polarCenter, polarCenter)
canvas.translate(polarCenter, polarCenter)
drawRadarView(canvas)
drawRadarText(canvas)
if (!pass.tle.isDeepspace) {
drawPassTrajectory(canvas, pass)
}
drawSatellite(canvas, pass)
}
private fun drawRadarView(cvs: Canvas) {
cvs.drawLine(-radius, 0f, radius, 0f, radarPaint)
cvs.drawLine(0f, -radius, 0f, radius, radarPaint)
cvs.drawCircle(0f, 0f, radius, radarPaint)
cvs.drawCircle(0f, 0f, (radius / 3) * 2, radarPaint)
cvs.drawCircle(0f, 0f, radius / 3, radarPaint)
}
private fun drawRadarText(cvs: Canvas) {
cvs.drawText("N", scale, -radius + txtSize - scale * 2, txtPaint)
cvs.drawText("30°", scale, -((radius / 3) * 2) - scale * 2, txtPaint)
cvs.drawText("60°", scale, -(radius / 3) - scale * 2, txtPaint)
cvs.drawText("90°", scale, -scale * 2, txtPaint)
}
private fun drawPassTrajectory(cvs: Canvas, satPass: SatPass) {
val startTime = satPass.pass.startTime
val endTime = satPass.pass.endTime
while (startTime.before(endTime)) {
val satPos = satPass.predictor.getSatPos(startTime)
val x = sph2CartX(satPos.azimuth, satPos.elevation, radius.toDouble())
val y = sph2CartY(satPos.azimuth, satPos.elevation, radius.toDouble())
if (startTime.compareTo(satPass.pass.startTime) == 0) {
path.moveTo(x, -y)
} else {
path.lineTo(x, -y)
}
startTime.time += 15000
}
cvs.drawPath(path, trackPaint)
}
private fun drawSatellite(cvs: Canvas, satPass: SatPass) {
val date = Date(System.currentTimeMillis())
val satPos = satPass.predictor.getSatPos(date)
if (satPos.elevation > 0) {
val x = sph2CartX(satPos.azimuth, satPos.elevation, radius.toDouble())
val y = sph2CartY(satPos.azimuth, satPos.elevation, radius.toDouble())
cvs.drawCircle(x, -y, txtSize / 3, satPaint)
}
}
private fun sph2CartX(azimuth: Double, elevation: Double, r: Double): Float {
val piDiv2 = Math.PI / 2.0
val radius = r * (piDiv2 - elevation) / piDiv2
return (radius * cos(piDiv2 - azimuth)).toFloat()
}
private fun sph2CartY(azimuth: Double, elevation: Double, r: Double): Float {
val piDiv2 = Math.PI / 2.0
val radius = r * (piDiv2 - elevation) / piDiv2
return (radius * sin(piDiv2 - azimuth)).toFloat()
}
}
@@ -1,19 +0,0 @@
package com.rtbishop.look4sat.utility
import androidx.room.TypeConverter
import com.github.amsacode.predict4java.TLE
import com.google.gson.Gson
class Converters {
private val gSon = Gson()
@TypeConverter
fun tleToString(tle: TLE): String {
return gSon.toJson(tle)
}
@TypeConverter
fun tleFromString(string: String): TLE {
return gSon.fromJson(string, TLE::class.java)
}
}
@@ -1,11 +0,0 @@
package com.rtbishop.look4sat.utility
import android.content.Context
import android.widget.Toast
object Extensions {
fun String.toast(context: Context, duration: Int = Toast.LENGTH_SHORT): Toast {
return Toast.makeText(context, this, duration).apply { show() }
}
}
@@ -1,269 +0,0 @@
/**
* predict4java: An SDP4 / SGP4 library for satellite orbit predictions
*
* Copyright (C) 2004-2010 David A. B. Johnson, G4DPZ.
*
* This class is a Java port of one of the core elements of
* the Predict program, Copyright John A. Magliacane,
* KD2BD 1991-2003: http://www.qsl.net/kd2bd/predict.html
*
* Dr. T.S. Kelso is the author of the SGP4/SDP4 orbital models,
* originally written in Fortran and Pascal, and released into the
* public domain through his website (http://www.celestrak.com/).
* Neoklis Kyriazis, 5B4AZ, later re-wrote Dr. Kelso's code in C,
* and released it under the GNU GPL in 2002.
* PREDICT's core is based on 5B4AZ's code translation efforts.
*
* Author: David A. B. Johnson, G4DPZ <dave></dave>@g4dpz.me.uk>
*
* Comments, questions and bugreports should be submitted via
* http://sourceforge.net/projects/websat/
* More details can be found at the project home page:
*
* http://websat.sourceforge.net
*
* 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 2 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, visit http://www.fsf.org/
*/
package com.rtbishop.look4sat.utility
import com.github.amsacode.predict4java.*
import java.util.*
class PassPredictor(private val tle: TLE, private val qth: GroundStationPosition) {
private val sat: Satellite = SatelliteFactory.createSatellite(tle)
private val south = "south"
private val north = "north"
private val deadSpotNone = "none"
private val speedOfLight = 2.99792458E8
private val twoPi = Math.PI * 2.0
private val timeZone: TimeZone = TimeZone.getTimeZone("UTC")
private var iterationCount = 0
fun getDownlinkFreq(freq: Long, date: Date): Long {
val cal = Calendar.getInstance(timeZone).apply {
clear()
timeInMillis = date.time
}
val satPos = getSatPos(cal.time)
val rangeRate = satPos.rangeRate
return (freq.toDouble() * (speedOfLight - rangeRate * 1000.0) / speedOfLight).toLong()
}
fun getUplinkFreq(freq: Long, date: Date): Long {
val cal = Calendar.getInstance(timeZone).apply {
clear()
timeInMillis = date.time
}
val satPos = getSatPos(cal.time)
val rangeRate = satPos.rangeRate
return (freq.toDouble() * (speedOfLight + rangeRate * 1000.0) / speedOfLight).toLong()
}
fun getSatPos(time: Date): SatPos {
iterationCount++
return sat.getPosition(qth, time)
}
fun getPositions(
referenceDate: Date,
incrementSeconds: Int,
minutesBefore: Int,
minutesAfter: Int
): List<SatPos> {
val positions: MutableList<SatPos> = ArrayList()
val endDateDate = Date(referenceDate.time + minutesAfter * 60L * 1000L)
var trackDate = Date(referenceDate.time - minutesBefore * 60L * 1000L)
while (trackDate.before(endDateDate)) {
positions.add(getSatPos(trackDate))
trackDate = Date(trackDate.time + incrementSeconds * 1000)
}
return positions
}
fun getPasses(start: Date, hoursAhead: Int, windBack: Boolean): List<SatPassTime> {
iterationCount = 0
val satellite = SatelliteFactory.createSatellite(tle)
val passes: MutableList<SatPassTime> = ArrayList()
val trackEndDate = Date(start.time + hoursAhead * 60L * 60L * 1000L)
var trackStartDate = start
var windBackTime = windBack
var lastAOS: Date
var count = 0
if (satellite.willBeSeen(qth)) {
if (tle.isDeepspace) passes.add(nextGeoSatPass(start))
else {
do {
if (count > 0) windBackTime = false
val pass = nextSatPass(trackStartDate, windBackTime)
lastAOS = pass.startTime
passes.add(pass)
trackStartDate =
Date(pass.endTime.time + threeQuarterOrbitMinutes() * 60L * 1000L)
count++
} while (lastAOS < trackEndDate)
}
}
return passes
}
private fun nextGeoSatPass(date: Date): SatPassTime {
val aosAzimuth: Int
val losAzimuth: Int
val tca: Date
val polePassed: String
val cal = Calendar.getInstance(timeZone).apply {
clear()
timeInMillis = date.time
}
val satPos = getSatPos(cal.time)
aosAzimuth = (satPos.azimuth / (2.0 * Math.PI) * 360.0).toInt()
losAzimuth = (satPos.azimuth / (2.0 * Math.PI) * 360.0).toInt()
tca = satPos.time
polePassed = getPolePassed(satPos, satPos)
cal.add(Calendar.HOUR, -12)
val startDate = cal.time
cal.add(Calendar.HOUR, 24)
val endDate = cal.time
return SatPassTime(
startDate, endDate, tca, polePassed, aosAzimuth,
losAzimuth, satPos.elevation / (2.0 * Math.PI) * 360.0
)
}
private fun nextSatPass(date: Date, windBack: Boolean = false): SatPassTime {
val aosAzimuth: Int
val losAzimuth: Int
var maxElevation = 0.0
var elevation: Double
var prevPos: SatPos
var polePassed = deadSpotNone
// get the current position
val cal = Calendar.getInstance(timeZone).apply {
clear()
timeInMillis = date.time
}
// wind back time 1/4 of an orbit
if (windBack) {
val meanMotion = tle.meanmo
cal.add(Calendar.MINUTE, (-24.0 * 60.0 / meanMotion / 4.0).toInt())
}
var satPos = getSatPos(cal.time)
// test for the elevation being above the horizon
if (satPos.elevation > 0.0) {
// move time forward in 30 second intervals until the sat goes below the horizon
do {
satPos = getPosition(cal, 60)
} while (satPos.elevation > 0.0)
// move time forward 3/4 orbit
cal.add(Calendar.MINUTE, threeQuarterOrbitMinutes())
}
// now find the next time it comes above the horizon
do {
satPos = getPosition(cal, 60)
elevation = satPos.elevation
if (elevation > maxElevation) {
maxElevation = elevation
}
} while (satPos.elevation < 0.0)
// refine it to 5 seconds
cal.add(Calendar.SECOND, -60)
do {
satPos = getPosition(cal, 5)
elevation = satPos.elevation
if (elevation > maxElevation) {
maxElevation = elevation
}
prevPos = satPos
} while (satPos.elevation < 0.0)
val startDate = satPos.time
aosAzimuth = (satPos.azimuth / (2.0 * Math.PI) * 360.0).toInt()
// now find when it goes below
do {
satPos = getPosition(cal, 30)
val currPolePassed = getPolePassed(prevPos, satPos)
if (currPolePassed != deadSpotNone) {
polePassed = currPolePassed
}
elevation = satPos.elevation
if (elevation > maxElevation) {
maxElevation = elevation
}
prevPos = satPos
} while (satPos.elevation > 0.0)
// refine it to 5 seconds
cal.add(Calendar.SECOND, -30)
do {
satPos = getPosition(cal, 5)
elevation = satPos.elevation
if (elevation > maxElevation) {
maxElevation = elevation
}
} while (satPos.elevation > 0.0)
val endDate = satPos.time
losAzimuth = (satPos.azimuth / (2.0 * Math.PI) * 360.0).toInt()
val tca = Date(startDate.time + (endDate.time - startDate.time) / 2)
return SatPassTime(
startDate, endDate, tca, polePassed, aosAzimuth,
losAzimuth, maxElevation / (2.0 * Math.PI) * 360.0
)
}
private fun getPosition(cal: Calendar, offSet: Int): SatPos {
cal.add(Calendar.SECOND, offSet)
return getSatPos(cal.time)
}
private fun getPolePassed(prevPos: SatPos, satPos: SatPos): String {
var polePassed = deadSpotNone
val az1 = prevPos.azimuth / twoPi * 360.0
val az2 = satPos.azimuth / twoPi * 360.0
if (az1 > az2) {
// we may be moving from 350 or greater through north
if (az1 > 350 && az2 < 10) polePassed = north
else {
// we may be moving from 190 or greater through south
if (az1 > 180 && az2 < 180) polePassed = south
}
} else {
// we may be moving from 10 or less through north
if (az1 < 10 && az2 > 350) polePassed = north
else {
// we may be moving from 170 or more through south
if (az1 < 180 && az2 > 180) polePassed = south
}
}
return polePassed
}
private fun threeQuarterOrbitMinutes(): Int {
return (24.0 * 60.0 / tle.meanmo * 0.75).toInt()
}
}
@@ -1,124 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.utility
import android.content.SharedPreferences
import android.location.Location
import android.location.LocationManager
import androidx.core.content.edit
import com.github.amsacode.predict4java.GroundStationPosition
import javax.inject.Inject
class PrefsManager @Inject constructor(
private val preferences: SharedPreferences,
private val locationManager: LocationManager
) {
private val keyHoursAhead = "hoursAhead"
private val keyMinElevation = "minEl"
private val keyLatitude = "latitude"
private val keyLongitude = "longitude"
private val keyAltitude = "altitude"
private val keyRefreshRate = "rate"
private val keyCompass = "compass"
private val keyTimeUtc = "timeUTC"
private val defaultHoursAhead = 8
private val defaultMinEl = 16.0
private val defaultRefreshRate = "3000"
private val defaultGSP = "0.0"
fun isFirstLaunch(): Boolean {
val keyIsFirstLaunch = "keyIsFirstLaunch"
val isFirstLaunch = preferences.getBoolean(keyIsFirstLaunch, true)
return if (isFirstLaunch) {
preferences.edit {
putBoolean(keyIsFirstLaunch, false)
apply()
}
true
} else isFirstLaunch
}
fun isTimeUTC(): Boolean {
return preferences.getBoolean(keyTimeUtc, false)
}
fun getHoursAhead(): Int {
return preferences.getInt(keyHoursAhead, defaultHoursAhead)
}
fun getMinElevation(): Double {
return preferences.getDouble(keyMinElevation, defaultMinEl)
}
fun getRefreshRate(): Long {
return preferences.getString(keyRefreshRate, defaultRefreshRate)!!.toLong()
}
fun getCompass(): Boolean {
return preferences.getBoolean(keyCompass, true)
}
fun getPosition(): GroundStationPosition {
val lat = preferences.getString(keyLatitude, defaultGSP)!!.toDouble()
val lon = preferences.getString(keyLongitude, defaultGSP)!!.toDouble()
val alt = preferences.getString(keyAltitude, defaultGSP)!!.toDouble()
return GroundStationPosition(lat, lon, alt)
}
fun getLastKnownLocation(): Location? {
val provPassive = LocationManager.PASSIVE_PROVIDER
return try {
val location = locationManager.getLastKnownLocation(provPassive)
location?.let {
preferences.edit {
putString(keyLatitude, location.latitude.toString())
putString(keyLongitude, location.longitude.toString())
putString(keyAltitude, location.altitude.toString())
apply()
}
}
location
} catch (e: SecurityException) {
null
}
}
fun setHoursAhead(hours: Int) {
preferences.edit {
putInt(keyHoursAhead, hours)
apply()
}
}
fun setMinElevation(minEl: Double) {
preferences.edit {
putDouble(keyMinElevation, minEl)
apply()
}
}
private fun SharedPreferences.Editor.putDouble(key: String, double: Double) {
putLong(key, double.toRawBits())
}
private fun SharedPreferences.getDouble(key: String, default: Double): Double {
return Double.fromBits(getLong(key, default.toRawBits()))
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/backgroundMapText" />
<corners android:radius="8dp" />
</shape>
-28
View File
@@ -1,28 +0,0 @@
<!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#C0C0C0"
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="32dp"
android:height="32dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFE082"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
</vector>
-28
View File
@@ -1,28 +0,0 @@
<!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFE082"
android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z" />
</vector>
@@ -1,28 +0,0 @@
<!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#C0C0C0"
android:pathData="M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z" />
</vector>
-7
View File
@@ -1,7 +0,0 @@
<vector android:height="24dp" android:viewportHeight="100"
android:viewportWidth="100" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFE082" android:pathData="M63.5,45.5c-2.485,0 -4.5,2.015 -4.5,4.5c0,2.485 2.015,4.5 4.5,4.5S68,52.485 68,50C68,47.515 65.985,45.5 63.5,45.5L63.5,45.5z"/>
<path android:fillColor="#FFE082" android:pathData="M54.5,23c14.888,0 27,12.112 27,27s-12.112,27 -27,27s-27,-12.112 -27,-27S39.612,23 54.5,23M54.5,18.5C37.103,18.5 23,32.603 23,50s14.103,31.5 31.5,31.5S86,67.397 86,50S71.897,18.5 54.5,18.5L54.5,18.5z"/>
<path android:fillColor="#FFE082" android:pathData="M50,9.5c22.332,0 40.5,18.168 40.5,40.5S72.332,90.5 50,90.5S9.5,72.332 9.5,50S27.668,9.5 50,9.5M50,5C25.147,5 5,25.147 5,50s20.147,45 45,45s45,-20.147 45,-45S74.853,5 50,5L50,5z"/>
<path android:fillColor="#FFE082" android:pathData="M59,36.5c7.444,0 13.5,6.056 13.5,13.5c0,7.444 -6.056,13.5 -13.5,13.5c-7.444,0 -13.5,-6.056 -13.5,-13.5C45.5,42.556 51.556,36.5 59,36.5M59,32c-9.941,0 -18,8.059 -18,18s8.059,18 18,18s18,-8.059 18,-18S68.941,32 59,32L59,32z"/>
</vector>
@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/themeIcon"
android:pathData="M20,6h-8l-2,-2L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,8h16v10z" />
</vector>
-28
View File
@@ -1,28 +0,0 @@
<!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#C0C0C0"
android:pathData="M10,18h4v-2h-4v2zM3,6v2h18L21,6L3,6zM6,13h12v-2L6,11v2z" />
</vector>
@@ -1,30 +0,0 @@
<!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="28dp"
android:height="28dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/themeIcon"
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,27 +0,0 @@
<!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="270"
android:endColor="#181818"
android:startColor="@color/backgroundDark"
android:type="linear" />
</shape>
@@ -1,41 +0,0 @@
<!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<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="#FFE082"
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="#FFE082"
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>
-10
View File
@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@color/themeAccent"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/themeAccent"
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>
-10
View File
@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@color/themeAccent"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/themeAccent"
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,28 +0,0 @@
<!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFE082"
android:pathData="M12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5 5,-2.24 5,-5 -2.24,-5 -5,-5zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z" />
</vector>
@@ -1,28 +0,0 @@
<!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFE082"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z" />
</vector>
@@ -1,57 +0,0 @@
<!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:fillAlpha="1"
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="m105.138,13.879a2.864,2.864 90,0 1,1.701 0.85l19.334,19.331a2.864,2.864 90,0 1,-0 4.027l-26.584,26.625a2.864,2.864 90,0 1,-1.835 0.805l-7.922,0.626 5.773,5.772a2.864,2.864 90,0 1,-0 4.027l-19.602,19.644a2.864,2.864 90,0 1,-4.073 0l-5.773,-5.772 -0.627,7.92a2.864,2.864 90,0 1,-0.805 1.835l-26.629,26.58a2.864,2.864 90,0 1,-4.073 0l-19.289,-19.286a2.864,2.864 90,0 1,-0 -4.072l26.584,-26.625a2.864,2.864 90,0 1,1.835 -0.805l7.922,-0.626 -5.773,-5.772a2.864,2.864 90,0 1,-0 -4.072l19.603,-19.599a2.864,2.864 90,0 1,4.073 0l5.773,5.772 0.626,-7.92a2.864,2.864 90,0 1,0.806 -1.835l26.629,-26.58a2.864,2.864 90,0 1,2.059 -0.85,2.864 2.864,90 0,1 0.269,0zM104.825,20.77 L80.97,44.62 80.165,55.315 85.58,60.729 96.276,59.879 120.13,36.074zM55.326,80.15 L44.629,81 20.775,104.806 36.081,120.109 59.891,96.259 60.741,85.564z"
android:strokeColor="#00000000"
tools:ignore="VectorPath" />
<path
android:fillAlpha="1"
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="m62.653,12.194a2.864,2.864 90,1 1,-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 1,-5.728 0c-0,-12.897 4.935,-25.829 14.773,-35.664 9.85,-9.848 22.772,-14.785 35.684,-14.785z"
android:strokeColor="#00000000" />
<path
android:fillAlpha="1"
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="m62.653,1a2.864,2.864 90,1 1,-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 1,-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"
android:strokeColor="#00000000" />
<path
android:fillAlpha="1"
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="m62.653,23.302a2.864,2.864 90,1 1,-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 1,-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"
android:strokeColor="#00000000" />
<path
android:fillAlpha="1"
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="m62.653,34.525a2.864,2.864 90,1 1,-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 1,-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"
android:strokeColor="#00000000" />
</vector>
@@ -1,57 +0,0 @@
<!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:fillAlpha="1"
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="m22.862,13.879a2.864,2.864 90,0 0,-1.701 0.85l-19.334,19.331a2.864,2.864 90,0 0,0 4.027l26.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.027l19.602,19.644a2.864,2.864 90,0 0,4.073 0l5.773,-5.772 0.627,7.92a2.864,2.864 90,0 0,0.805 1.835l26.629,26.58a2.864,2.864 90,0 0,4.073 0l19.289,-19.286a2.864,2.864 90,0 0,0 -4.072l-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.072l-19.603,-19.599a2.864,2.864 90,0 0,-4.073 0l-5.773,5.772 -0.626,-7.92a2.864,2.864 90,0 0,-0.806 -1.835l-26.629,-26.58a2.864,2.864 90,0 0,-2.059 -0.85,2.864 2.864,90 0,0 -0.269,0zM23.175,20.77 L47.03,44.62 47.835,55.315 42.42,60.729 31.724,59.879 7.87,36.074zM72.674,80.15 L83.371,81 107.225,104.806 91.919,120.109 68.109,96.259 67.259,85.564z"
android:strokeColor="#00000000"
tools:ignore="VectorPath" />
<path
android:fillAlpha="1"
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="m65.347,12.194a2.864,2.864 90,1 0,0 5.727c11.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 0c0,-12.897 -4.935,-25.829 -14.773,-35.664 -9.85,-9.848 -22.772,-14.785 -35.684,-14.785z"
android:strokeColor="#00000000" />
<path
android:fillAlpha="1"
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="m65.347,1a2.864,2.864 90,1 0,0 5.727c14.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 0c0,-15.763 -6.025,-31.562 -18.055,-43.59 -12.029,-12.028 -27.818,-18.052 -43.597,-18.052z"
android:strokeColor="#00000000" />
<path
android:fillAlpha="1"
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="m65.347,23.302a2.864,2.864 90,1 0,0 5.727c8.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 0c0,-10.061 -3.861,-20.153 -11.533,-27.824 -7.672,-7.671 -17.756,-11.517 -27.829,-11.517z"
android:strokeColor="#00000000" />
<path
android:fillAlpha="1"
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="m65.347,34.525a2.864,2.864 90,1 0,0 5.727c5.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 0c0,-7.192 -2.757,-14.406 -8.236,-19.883 -5.479,-5.478 -12.693,-8.234 -19.887,-8.234z"
android:strokeColor="#00000000" />
</vector>
@@ -1,57 +0,0 @@
<!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:fillAlpha="1"
android:fillColor="#C0C0C0"
android:fillType="evenOdd"
android:pathData="m105.138,114.121a2.864,2.864 90,0 0,1.701 -0.85l19.334,-19.331a2.864,2.864 90,0 0,-0 -4.027l-26.584,-26.625a2.864,2.864 90,0 0,-1.835 -0.805l-7.922,-0.626 5.773,-5.772a2.864,2.864 90,0 0,-0 -4.027l-19.602,-19.644a2.864,2.864 90,0 0,-4.073 -0l-5.773,5.772 -0.627,-7.92a2.864,2.864 90,0 0,-0.805 -1.835l-26.629,-26.58a2.864,2.864 90,0 0,-4.073 -0l-19.289,19.286a2.864,2.864 90,0 0,-0 4.072l26.584,26.625a2.864,2.864 90,0 0,1.835 0.805l7.922,0.626 -5.773,5.772a2.864,2.864 90,0 0,-0 4.072l19.603,19.599a2.864,2.864 90,0 0,4.073 -0l5.773,-5.772 0.626,7.92a2.864,2.864 90,0 0,0.806 1.835l26.629,26.58a2.864,2.864 90,0 0,2.059 0.85,2.864 2.864,90 0,0 0.269,-0zM104.825,107.23 L80.97,83.38 80.165,72.685 85.58,67.271 96.276,68.121 120.13,91.926zM55.326,47.85 L44.629,47 20.775,23.194 36.081,7.891 59.891,31.741 60.741,42.436z"
android:strokeColor="#00000000"
tools:ignore="VectorPath" />
<path
android:fillAlpha="1"
android:fillColor="#FFE082"
android:fillType="evenOdd"
android:pathData="m62.653,115.806a2.864,2.864 90,1 0,-0 -5.727c-11.457,-0 -22.876,-4.351 -31.634,-13.107 -8.741,-8.74 -13.095,-20.174 -13.095,-31.614a2.864,2.864 90,1 0,-5.728 -0c-0,12.897 4.935,25.829 14.773,35.664 9.85,9.848 22.772,14.785 35.684,14.785z"
android:strokeColor="#00000000" />
<path
android:fillAlpha="1"
android:fillColor="#FFE082"
android:fillType="evenOdd"
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"
android:strokeColor="#00000000" />
<path
android:fillAlpha="1"
android:fillColor="#FFE082"
android:fillType="evenOdd"
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"
android:strokeColor="#00000000" />
<path
android:fillAlpha="1"
android:fillColor="#FFE082"
android:fillType="evenOdd"
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"
android:strokeColor="#00000000" />
</vector>
-28
View File
@@ -1,28 +0,0 @@
<!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
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>
-33
View File
@@ -1,33 +0,0 @@
<!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:fillColor="#FFE082"
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="#FFE082"
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>
-31
View File
@@ -1,31 +0,0 @@
<!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFE082"
android:pathData="M6.18,17.82m-2.18,0a2.18,2.18 0,1 1,4.36 0a2.18,2.18 0,1 1,-4.36 0" />
<path
android:fillColor="#FFE082"
android:pathData="M4,4.44v2.83c7.03,0 12.73,5.7 12.73,12.73h2.83c0,-8.59 -6.97,-15.56 -15.56,-15.56zM4,10.1v2.83c3.9,0 7.07,3.17 7.07,7.07h2.83c0,-5.47 -4.43,-9.9 -9.9,-9.9z" />
</vector>
@@ -1,28 +0,0 @@
<!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/themeIcon"
android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94L23,13v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z" />
</vector>
@@ -1,28 +0,0 @@
<!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/themeIcon"
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,28 +0,0 @@
<!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M20.5,3l-0.16,0.03L15,5.1 9,3 3.36,4.9c-0.21,0.07 -0.36,0.25 -0.36,0.48V20.5c0,0.28 0.22,0.5 0.5,0.5l0.16,-0.03L9,18.9l6,2.1 5.64,-1.9c0.21,-0.07 0.36,-0.25 0.36,-0.48V3.5c0,-0.28 -0.22,-0.5 -0.5,-0.5zM15,19l-6,-2.11V5l6,2.11V19z" />
</vector>
@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
android:opacity="opaque">
<item android:drawable="@drawable/ic_launcher_background" />
<item>
<bitmap
android:gravity="center"
android:src="@drawable/splash_front" />
</item>
</layer-list>
-44
View File
@@ -1,44 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<androidx.drawerlayout.widget.DrawerLayout 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/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<include
android:id="@+id/include_app_bar"
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/drawer_header"
app:menu="@menu/drawer_menu" />
</androidx.drawerlayout.widget.DrawerLayout>
-71
View File
@@ -1,71 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:titleTextColor="@color/themeAccent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/toolbar_timer"
style="@style/ToolbarText"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginEnd="24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/toolbar_filter"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/toolbar_filter"
style="@style/ToolbarButton"
android:contentDescription="@string/placeholder"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<include
android:id="@+id/include_main_content"
layout="@layout/content_main" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
-40
View File
@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:showIn="@layout/app_bar_main">
<fragment
android:id="@+id/nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph"
tools:ignore="FragmentTagUsage" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,79 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<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/pass_pref_root"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/pref_tv_title"
style="@style/DialogText"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp"
android:text="@string/dialog_filter_passes"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/pref_tv_hoursAhead"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
android:text="@string/pref_hours_ahead_summary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/pref_tv_title" />
<EditText
android:id="@+id/pref_et_hoursAhead"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/pref_hours_ahead_hint"
android:importantForAutofill="no"
android:inputType="number"
app:layout_constraintEnd_toEndOf="@+id/pref_tv_hoursAhead"
app:layout_constraintStart_toStartOf="@+id/pref_tv_hoursAhead"
app:layout_constraintTop_toBottomOf="@+id/pref_tv_hoursAhead" />
<TextView
android:id="@+id/pref_tv_minEl"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/pref_min_el_summary"
app:layout_constraintEnd_toEndOf="@+id/pref_et_hoursAhead"
app:layout_constraintStart_toStartOf="@+id/pref_et_hoursAhead"
app:layout_constraintTop_toBottomOf="@+id/pref_et_hoursAhead" />
<EditText
android:id="@+id/pref_et_minEl"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/pref_min_el_hint"
android:importantForAutofill="no"
android:inputType="number|numberDecimal"
app:layout_constraintEnd_toEndOf="@+id/pref_tv_minEl"
app:layout_constraintStart_toStartOf="@+id/pref_tv_minEl"
app:layout_constraintTop_toBottomOf="@+id/pref_tv_minEl" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,88 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
~ Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
~ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-->
<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/dialog_sat_entry_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="4dp">
<TextView
android:id="@+id/dialog_title"
style="@style/DialogText"
android:layout_width="0dp"
android:layout_marginTop="8dp"
android:text="@string/dialog_select_sat"
app:layout_constraintEnd_toEndOf="@+id/dialog_btn_positive"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/dialog_btn_neutral"
app:layout_constraintTop_toTopOf="parent" />
<SearchView
android:id="@+id/dialog_search"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp"
android:queryHint="@string/dialog_entries_hint"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/dialog_title" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/dialog_recycler"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@+id/dialog_btn_negative"
app:layout_constraintEnd_toEndOf="@+id/dialog_search"
app:layout_constraintStart_toStartOf="@+id/dialog_search"
app:layout_constraintTop_toBottomOf="@+id/dialog_search" />
<TextView
android:id="@+id/dialog_btn_neutral"
style="@style/DialogText"
android:layout_marginStart="8dp"
android:text="@string/btn_all_none"
app:layout_constraintBaseline_toBaselineOf="@+id/dialog_btn_negative"
app:layout_constraintStart_toStartOf="@+id/dialog_recycler" />
<TextView
android:id="@+id/dialog_btn_negative"
style="@style/DialogText"
android:layout_marginEnd="24dp"
android:text="@string/btn_cancel"
app:layout_constraintBaseline_toBaselineOf="@+id/dialog_btn_positive"
app:layout_constraintEnd_toStartOf="@+id/dialog_btn_positive" />
<TextView
android:id="@+id/dialog_btn_positive"
style="@style/DialogText"
android:layout_width="36dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:gravity="end"
android:text="@string/btn_ok"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/dialog_recycler" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,78 +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/tleSourcesDialog"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="4dp">
<TextView
android:id="@+id/tleSourcesTitle"
style="@style/DialogText"
android:layout_marginStart="12dp"
android:layout_marginTop="10dp"
android:text="@string/dialog_tle_sources"
app:layout_constraintEnd_toStartOf="@+id/tleSourceBtnAdd"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/tleSourceBtnAdd"
android:layout_width="36dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="10dp"
android:contentDescription="@string/placeholder"
app:layout_constraintBottom_toBottomOf="@+id/tleSourcesWarning"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/tleSourcesTitle"
app:layout_constraintTop_toTopOf="@+id/tleSourcesTitle"
app:srcCompat="@drawable/ic_add" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/tleSourcesRecycler"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@+id/tleSourcesBtnPos"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tleSourceBtnAdd"
app:layout_constraintVertical_chainStyle="packed">
</androidx.recyclerview.widget.RecyclerView>
<TextView
android:id="@+id/tleSourcesWarning"
style="@style/DialogText"
android:layout_marginTop="4dp"
android:gravity="center"
android:text="@string/https_only"
app:layout_constraintStart_toStartOf="@+id/tleSourcesTitle"
app:layout_constraintTop_toBottomOf="@+id/tleSourcesTitle" />
<TextView
android:id="@+id/tleSourcesBtnNeg"
style="@style/DialogText"
android:layout_marginEnd="24dp"
android:text="@string/btn_cancel"
app:layout_constraintBaseline_toBaselineOf="@+id/tleSourcesBtnPos"
app:layout_constraintEnd_toStartOf="@+id/tleSourcesBtnPos"
app:layout_constraintHorizontal_chainStyle="spread_inside" />
<TextView
android:id="@+id/tleSourcesBtnPos"
style="@style/DialogText"
android:layout_marginBottom="8dp"
android:text="@string/btn_ok"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/tleSourceBtnAdd" />
</androidx.constraintlayout.widget.ConstraintLayout>
Loaded 100 of 5904 files, more files were not shown because too many files have changed in this diff. Show more