Compare commits

...
Author SHA1 Message Date
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
6 changed files with 64 additions and 19 deletions

No files matched your search

+15 -3
View File
@@ -1,7 +1,19 @@
import java.io.FileInputStream
import java.util.Properties
plugins { plugins {
alias(libs.plugins.convention.applicationPlugin) 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 { android {
namespace = libs.versions.packageName.get() namespace = libs.versions.packageName.get()
defaultConfig { defaultConfig {
@@ -11,9 +23,9 @@ android {
signingConfigs { signingConfigs {
create("release") { create("release") {
storeFile = file(System.getProperty("user.home") + "/my-release-key.jks") storeFile = file(System.getProperty("user.home") + "/my-release-key.jks")
storePassword = "look4sat123" storePassword = releaseCred("RELEASE_STORE_PASSWORD")
keyAlias = "look4sat" keyAlias = releaseCred("RELEASE_KEY_ALIAS").ifEmpty { "look4sat" }
keyPassword = "look4sat123" keyPassword = releaseCred("RELEASE_KEY_PASSWORD")
} }
} }
buildTypes { buildTypes {
@@ -199,11 +199,10 @@ class AmSatRepository(
/** Build one SatStatus (3 days x 12 slots) per catalog satellite, slotting reports by age. */ /** Build one SatStatus (3 days x 12 slots) per catalog satellite, slotting reports by age. */
private fun buildStatuses(names: List<String>, reports: List<ApiReport>, nowSec: Long): List<SatStatus> { private fun buildStatuses(names: List<String>, reports: List<ApiReport>, nowSec: Long): List<SatStatus> {
val byName = reports.groupBy { it.name } val byName = reports.groupBy { it.name }
val monthAbbr = arrayOf("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
val utc = Calendar.getInstance(TimeZone.getTimeZone("UTC")) val utc = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
val labels = (0 until 3).map { d -> val labels = (0 until 3).map { d ->
utc.timeInMillis = (nowSec - d * 86400L) * 1000 utc.timeInMillis = (nowSec - d * 86400L) * 1000
"${monthAbbr[utc.get(Calendar.MONTH)]} ${utc.get(Calendar.DAY_OF_MONTH)}" formatDayLabel(utc)
} }
return names.map { name -> return names.map { name ->
val slots = (0 until 36).map { slotIdx -> val slots = (0 until 36).map { slotIdx ->
@@ -228,6 +227,15 @@ class AmSatRepository(
} }
} }
private fun formatDayLabel(calendar: Calendar): String {
return if (Locale.getDefault().language == Locale.CHINESE.language) {
"${calendar.get(Calendar.MONTH) + 1}月${calendar.get(Calendar.DAY_OF_MONTH)}日"
} else {
val monthAbbr = arrayOf("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
"${monthAbbr[calendar.get(Calendar.MONTH)]} ${calendar.get(Calendar.DAY_OF_MONTH)}"
}
}
private fun toSatReport(r: ApiReport): SatReport { private fun toSatReport(r: ApiReport): SatReport {
val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")) val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
cal.timeInMillis = r.reportedTimeUtcSec * 1000 cal.timeInMillis = r.reportedTimeUtcSec * 1000
@@ -19,6 +19,8 @@ package com.rtbishop.look4sat.core.domain.source
object Sources { object Sources {
val satelliteDataUrls = mapOf( val satelliteDataUrls = mapOf(
"LAPAN-A2" to "https://www.kaggle.com/api/v1/datasets/download/muazamnugroho/lapan-a2-satellite-two-line-element-tle-dataset/LAPAN-A2_TLE_latest.txt",
"R4UAB" to "https://r4uab.ru/satonline.txt",
"All" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=active&FORMAT=csv", "All" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=active&FORMAT=csv",
"SatNOGS" to "https://db.satnogs.org/api/tle/?format=3le", "SatNOGS" to "https://db.satnogs.org/api/tle/?format=3le",
"Amateur" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=amateur&FORMAT=csv", "Amateur" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=amateur&FORMAT=csv",
@@ -46,7 +48,6 @@ object Sources {
"Amsat" to "https://amsat.org/tle/current/nasabare.txt", "Amsat" to "https://amsat.org/tle/current/nasabare.txt",
"Classified" to "https://www.mmccants.org/tles/classfd.zip", "Classified" to "https://www.mmccants.org/tles/classfd.zip",
"McCants" to "https://www.mmccants.org/tles/inttles.zip", "McCants" to "https://www.mmccants.org/tles/inttles.zip",
"R4UAB" to "https://r4uab.ru/satonline.txt",
"ARISS" to "https://live.ariss.org/iss.txt", "ARISS" to "https://live.ariss.org/iss.txt",
"Other" to "" // key for sats filter "Other" to "" // key for sats filter
) )
@@ -19,7 +19,9 @@ package com.rtbishop.look4sat.feature.settings
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@@ -29,6 +31,9 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -37,6 +42,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
@@ -126,7 +132,8 @@ fun UpdateCheckerScreen(
!state.hasUpdate -> StatusRow(showSpinner = false, message = stringResource(R.string.update_check_up_to_date)) !state.hasUpdate -> StatusRow(showSpinner = false, message = stringResource(R.string.update_check_up_to_date))
else -> ReleaseCard( else -> ReleaseCard(
state = state, state = state,
onDownload = onDownload onDownload = onDownload,
modifier = Modifier.weight(1f)
) )
} }
} }
@@ -155,12 +162,12 @@ private fun StatusRow(showSpinner: Boolean, message: String? = null) {
} }
@Composable @Composable
private fun ReleaseCard(state: UpdateCheckerState, onDownload: () -> Unit) { private fun ReleaseCard(state: UpdateCheckerState, onDownload: () -> Unit, modifier: Modifier = Modifier) {
val release = state.release ?: return val release = state.release ?: return
ElevatedCard(modifier = Modifier.fillMaxWidth()) { ElevatedCard(modifier = modifier.fillMaxWidth()) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxSize()
.padding(horizontal = 12.dp, vertical = 10.dp) .padding(horizontal = 12.dp, vertical = 10.dp)
) { ) {
Text( Text(
@@ -183,11 +190,24 @@ private fun ReleaseCard(state: UpdateCheckerState, onDownload: () -> Unit) {
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,
fontSize = 13.sp fontSize = 13.sp
) )
Text( Box(
text = release.body, modifier = Modifier
fontSize = 12.sp, .fillMaxWidth()
color = MaterialTheme.colorScheme.onSurfaceVariant .weight(1f)
) .padding(top = 4.dp)
.clip(RoundedCornerShape(12.dp))
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.45f))
.verticalScroll(rememberScrollState())
.padding(10.dp)
) {
Text(
text = release.body,
fontSize = 12.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
} else {
Spacer(modifier = Modifier.weight(1f))
} }
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
when { when {
@@ -608,12 +608,16 @@ private fun formatFetchedAt(utcMs: Long): String {
val cal = Calendar.getInstance() val cal = Calendar.getInstance()
cal.timeInMillis = utcMs cal.timeInMillis = utcMs
val day = cal.get(Calendar.DAY_OF_MONTH) val day = cal.get(Calendar.DAY_OF_MONTH)
val month = MONTH_ABBR[cal.get(Calendar.MONTH)] val monthIndex = cal.get(Calendar.MONTH)
val year = cal.get(Calendar.YEAR) val year = cal.get(Calendar.YEAR)
val hh = cal.get(Calendar.HOUR_OF_DAY).toString().padStart(2, '0') val hh = cal.get(Calendar.HOUR_OF_DAY).toString().padStart(2, '0')
val mm = cal.get(Calendar.MINUTE).toString().padStart(2, '0') val mm = cal.get(Calendar.MINUTE).toString().padStart(2, '0')
val ss = cal.get(Calendar.SECOND).toString().padStart(2, '0') val ss = cal.get(Calendar.SECOND).toString().padStart(2, '0')
return "$day$month $year - $hh:$mm:$ss" return if (Locale.getDefault().language == Locale.CHINESE.language) {
"${year}年${monthIndex + 1}月${day}日 - $hh:$mm:$ss"
} else {
"$day${MONTH_ABBR[monthIndex]} $year - $hh:$mm:$ss"
}
} }
private fun formatUtcNowForUpload(utcMs: Long): String { private fun formatUtcNowForUpload(utcMs: Long): String {
+2 -2
View File
@@ -1,8 +1,8 @@
[versions] [versions]
#noinspection UnusedVersionCatalogEntry #noinspection UnusedVersionCatalogEntry
appVersionCode = "450" appVersionCode = "451"
#noinspection UnusedVersionCatalogEntry #noinspection UnusedVersionCatalogEntry
appVersionName = "4.4.6-ba7opf.6" appVersionName = "4.4.6-ba7opf.7"
#noinspection UnusedVersionCatalogEntry #noinspection UnusedVersionCatalogEntry
compileSdk = "37" compileSdk = "37"
#noinspection UnusedVersionCatalogEntry #noinspection UnusedVersionCatalogEntry