feat(android): add update checks using shared python release checker
This commit is contained in:
parent
54b86cd9e2
commit
934eb345a2
|
|
@ -3,6 +3,7 @@ package org.flowseal.tgwsproxy
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
|
@ -14,13 +15,16 @@ import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.flowseal.tgwsproxy.databinding.ActivityMainBinding
|
import org.flowseal.tgwsproxy.databinding.ActivityMainBinding
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
private lateinit var settingsStore: ProxySettingsStore
|
private lateinit var settingsStore: ProxySettingsStore
|
||||||
|
private var currentUpdateStatus: ProxyUpdateStatus? = null
|
||||||
|
|
||||||
private val notificationPermissionLauncher = registerForActivityResult(
|
private val notificationPermissionLauncher = registerForActivityResult(
|
||||||
ActivityResultContracts.RequestPermission(),
|
ActivityResultContracts.RequestPermission(),
|
||||||
|
|
@ -46,6 +50,10 @@ class MainActivity : AppCompatActivity() {
|
||||||
binding.saveButton.setOnClickListener { onSaveClicked(showMessage = true) }
|
binding.saveButton.setOnClickListener { onSaveClicked(showMessage = true) }
|
||||||
binding.openLogsButton.setOnClickListener { onOpenLogsClicked() }
|
binding.openLogsButton.setOnClickListener { onOpenLogsClicked() }
|
||||||
binding.openTelegramButton.setOnClickListener { onOpenTelegramClicked() }
|
binding.openTelegramButton.setOnClickListener { onOpenTelegramClicked() }
|
||||||
|
binding.openReleasePageButton.setOnClickListener { onOpenReleasePageClicked() }
|
||||||
|
binding.checkUpdatesSwitch.setOnCheckedChangeListener { _, _ ->
|
||||||
|
renderUpdateStatus(currentUpdateStatus, binding.checkUpdatesSwitch.isChecked)
|
||||||
|
}
|
||||||
binding.disableBatteryOptimizationButton.setOnClickListener {
|
binding.disableBatteryOptimizationButton.setOnClickListener {
|
||||||
AndroidSystemStatus.openBatteryOptimizationSettings(this)
|
AndroidSystemStatus.openBatteryOptimizationSettings(this)
|
||||||
}
|
}
|
||||||
|
|
@ -53,7 +61,9 @@ class MainActivity : AppCompatActivity() {
|
||||||
AndroidSystemStatus.openAppSettings(this)
|
AndroidSystemStatus.openAppSettings(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderConfig(settingsStore.load())
|
val config = settingsStore.load()
|
||||||
|
renderConfig(config)
|
||||||
|
refreshUpdateStatus(checkNow = config.checkUpdates)
|
||||||
requestNotificationPermissionIfNeeded()
|
requestNotificationPermissionIfNeeded()
|
||||||
observeServiceState()
|
observeServiceState()
|
||||||
renderSystemStatus()
|
renderSystemStatus()
|
||||||
|
|
@ -78,6 +88,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
if (showMessage) {
|
if (showMessage) {
|
||||||
Snackbar.make(binding.root, R.string.settings_saved, Snackbar.LENGTH_SHORT).show()
|
Snackbar.make(binding.root, R.string.settings_saved, Snackbar.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
refreshUpdateStatus(checkNow = config.checkUpdates)
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,7 +122,9 @@ class MainActivity : AppCompatActivity() {
|
||||||
binding.logMaxMbInput.setText(config.logMaxMbText)
|
binding.logMaxMbInput.setText(config.logMaxMbText)
|
||||||
binding.bufferKbInput.setText(config.bufferKbText)
|
binding.bufferKbInput.setText(config.bufferKbText)
|
||||||
binding.poolSizeInput.setText(config.poolSizeText)
|
binding.poolSizeInput.setText(config.poolSizeText)
|
||||||
|
binding.checkUpdatesSwitch.isChecked = config.checkUpdates
|
||||||
binding.verboseSwitch.isChecked = config.verbose
|
binding.verboseSwitch.isChecked = config.verbose
|
||||||
|
renderUpdateStatus(currentUpdateStatus, config.checkUpdates)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun collectConfigFromForm(): ProxyConfig {
|
private fun collectConfigFromForm(): ProxyConfig {
|
||||||
|
|
@ -122,10 +135,63 @@ class MainActivity : AppCompatActivity() {
|
||||||
logMaxMbText = binding.logMaxMbInput.text?.toString().orEmpty(),
|
logMaxMbText = binding.logMaxMbInput.text?.toString().orEmpty(),
|
||||||
bufferKbText = binding.bufferKbInput.text?.toString().orEmpty(),
|
bufferKbText = binding.bufferKbInput.text?.toString().orEmpty(),
|
||||||
poolSizeText = binding.poolSizeInput.text?.toString().orEmpty(),
|
poolSizeText = binding.poolSizeInput.text?.toString().orEmpty(),
|
||||||
|
checkUpdates = binding.checkUpdatesSwitch.isChecked,
|
||||||
verbose = binding.verboseSwitch.isChecked,
|
verbose = binding.verboseSwitch.isChecked,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onOpenReleasePageClicked() {
|
||||||
|
val url = currentUpdateStatus?.htmlUrl ?: "https://github.com/Dark-Avery/tg-ws-proxy/releases/latest"
|
||||||
|
val opened = runCatching {
|
||||||
|
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||||
|
}.isSuccess
|
||||||
|
if (!opened) {
|
||||||
|
Snackbar.make(binding.root, R.string.release_page_open_failed, Snackbar.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshUpdateStatus(checkNow: Boolean) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val status = withContext(Dispatchers.IO) {
|
||||||
|
PythonProxyBridge.getUpdateStatus(this@MainActivity, checkNow)
|
||||||
|
}
|
||||||
|
currentUpdateStatus = status
|
||||||
|
renderUpdateStatus(status, binding.checkUpdatesSwitch.isChecked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderUpdateStatus(status: ProxyUpdateStatus?, checkUpdatesEnabled: Boolean) {
|
||||||
|
val currentVersion = status?.currentVersion ?: "unknown"
|
||||||
|
binding.currentVersionValue.text = getString(
|
||||||
|
R.string.updates_current_version_format,
|
||||||
|
currentVersion,
|
||||||
|
)
|
||||||
|
binding.updateStatusValue.text = when {
|
||||||
|
!checkUpdatesEnabled -> {
|
||||||
|
getString(R.string.updates_status_disabled)
|
||||||
|
}
|
||||||
|
status == null -> {
|
||||||
|
getString(R.string.updates_status_initial)
|
||||||
|
}
|
||||||
|
!status.error.isNullOrBlank() -> {
|
||||||
|
getString(R.string.updates_status_error, status.error)
|
||||||
|
}
|
||||||
|
status.hasUpdate && !status.latestVersion.isNullOrBlank() -> {
|
||||||
|
getString(
|
||||||
|
R.string.updates_status_available,
|
||||||
|
status.latestVersion,
|
||||||
|
status.currentVersion,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
status.aheadOfRelease -> {
|
||||||
|
getString(R.string.updates_status_newer, status.currentVersion)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
getString(R.string.updates_status_latest, status.currentVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun observeServiceState() {
|
private fun observeServiceState() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ data class ProxyConfig(
|
||||||
val logMaxMbText: String = formatDecimal(DEFAULT_LOG_MAX_MB),
|
val logMaxMbText: String = formatDecimal(DEFAULT_LOG_MAX_MB),
|
||||||
val bufferKbText: String = DEFAULT_BUFFER_KB.toString(),
|
val bufferKbText: String = DEFAULT_BUFFER_KB.toString(),
|
||||||
val poolSizeText: String = DEFAULT_POOL_SIZE.toString(),
|
val poolSizeText: String = DEFAULT_POOL_SIZE.toString(),
|
||||||
|
val checkUpdates: Boolean = true,
|
||||||
val verbose: Boolean = false,
|
val verbose: Boolean = false,
|
||||||
) {
|
) {
|
||||||
fun validate(): ValidationResult {
|
fun validate(): ValidationResult {
|
||||||
|
|
@ -78,6 +79,7 @@ data class ProxyConfig(
|
||||||
logMaxMb = logMaxMbValue,
|
logMaxMb = logMaxMbValue,
|
||||||
bufferKb = bufferKbValue,
|
bufferKb = bufferKbValue,
|
||||||
poolSize = poolSizeValue,
|
poolSize = poolSizeValue,
|
||||||
|
checkUpdates = checkUpdates,
|
||||||
verbose = verbose,
|
verbose = verbose,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -130,5 +132,6 @@ data class NormalizedProxyConfig(
|
||||||
val logMaxMb: Double,
|
val logMaxMb: Double,
|
||||||
val bufferKb: Int,
|
val bufferKb: Int,
|
||||||
val poolSize: Int,
|
val poolSize: Int,
|
||||||
|
val checkUpdates: Boolean,
|
||||||
val verbose: Boolean,
|
val verbose: Boolean,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ class ProxySettingsStore(context: Context) {
|
||||||
KEY_POOL_SIZE,
|
KEY_POOL_SIZE,
|
||||||
ProxyConfig.DEFAULT_POOL_SIZE,
|
ProxyConfig.DEFAULT_POOL_SIZE,
|
||||||
).toString(),
|
).toString(),
|
||||||
|
checkUpdates = preferences.getBoolean(KEY_CHECK_UPDATES, true),
|
||||||
verbose = preferences.getBoolean(KEY_VERBOSE, false),
|
verbose = preferences.getBoolean(KEY_VERBOSE, false),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -39,6 +40,7 @@ class ProxySettingsStore(context: Context) {
|
||||||
.putFloat(KEY_LOG_MAX_MB, config.logMaxMb.toFloat())
|
.putFloat(KEY_LOG_MAX_MB, config.logMaxMb.toFloat())
|
||||||
.putInt(KEY_BUFFER_KB, config.bufferKb)
|
.putInt(KEY_BUFFER_KB, config.bufferKb)
|
||||||
.putInt(KEY_POOL_SIZE, config.poolSize)
|
.putInt(KEY_POOL_SIZE, config.poolSize)
|
||||||
|
.putBoolean(KEY_CHECK_UPDATES, config.checkUpdates)
|
||||||
.putBoolean(KEY_VERBOSE, config.verbose)
|
.putBoolean(KEY_VERBOSE, config.verbose)
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
|
@ -51,6 +53,7 @@ class ProxySettingsStore(context: Context) {
|
||||||
private const val KEY_LOG_MAX_MB = "log_max_mb"
|
private const val KEY_LOG_MAX_MB = "log_max_mb"
|
||||||
private const val KEY_BUFFER_KB = "buf_kb"
|
private const val KEY_BUFFER_KB = "buf_kb"
|
||||||
private const val KEY_POOL_SIZE = "pool_size"
|
private const val KEY_POOL_SIZE = "pool_size"
|
||||||
|
private const val KEY_CHECK_UPDATES = "check_updates"
|
||||||
private const val KEY_VERBOSE = "verbose"
|
private const val KEY_VERBOSE = "verbose"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,19 @@ object PythonProxyBridge {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getUpdateStatus(context: Context, checkNow: Boolean = false): ProxyUpdateStatus {
|
||||||
|
val payload = getModule(context).callAttr("get_update_status_json", checkNow).toString()
|
||||||
|
val json = JSONObject(payload)
|
||||||
|
return ProxyUpdateStatus(
|
||||||
|
currentVersion = json.optString("current_version").ifBlank { "unknown" },
|
||||||
|
latestVersion = json.optString("latest").ifBlank { null },
|
||||||
|
hasUpdate = json.optBoolean("has_update", false),
|
||||||
|
aheadOfRelease = json.optBoolean("ahead_of_release", false),
|
||||||
|
htmlUrl = json.optString("html_url").ifBlank { null },
|
||||||
|
error = json.optString("error").ifBlank { null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun getModule(context: Context) =
|
private fun getModule(context: Context) =
|
||||||
getPython(context.applicationContext).getModule(MODULE_NAME)
|
getPython(context.applicationContext).getModule(MODULE_NAME)
|
||||||
|
|
||||||
|
|
@ -63,3 +76,12 @@ data class ProxyTrafficStats(
|
||||||
val running: Boolean = false,
|
val running: Boolean = false,
|
||||||
val lastError: String? = null,
|
val lastError: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class ProxyUpdateStatus(
|
||||||
|
val currentVersion: String = "unknown",
|
||||||
|
val latestVersion: String? = null,
|
||||||
|
val hasUpdate: Boolean = false,
|
||||||
|
val aheadOfRelease: Boolean = false,
|
||||||
|
val htmlUrl: String? = null,
|
||||||
|
val error: String? = null,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ from pathlib import Path
|
||||||
from typing import Iterable, Optional
|
from typing import Iterable, Optional
|
||||||
|
|
||||||
from proxy.app_runtime import ProxyAppRuntime
|
from proxy.app_runtime import ProxyAppRuntime
|
||||||
|
from proxy import __version__
|
||||||
import proxy.tg_ws_proxy as tg_ws_proxy
|
import proxy.tg_ws_proxy as tg_ws_proxy
|
||||||
|
from utils.update_check import RELEASES_PAGE_URL, get_status, run_check
|
||||||
|
|
||||||
|
|
||||||
_RUNTIME_LOCK = threading.RLock()
|
_RUNTIME_LOCK = threading.RLock()
|
||||||
|
|
@ -125,3 +127,23 @@ def get_runtime_stats_json() -> str:
|
||||||
payload["running"] = running
|
payload["running"] = running
|
||||||
payload["last_error"] = _LAST_ERROR
|
payload["last_error"] = _LAST_ERROR
|
||||||
return json.dumps(payload)
|
return json.dumps(payload)
|
||||||
|
|
||||||
|
|
||||||
|
def get_update_status_json(check_now: bool = False) -> str:
|
||||||
|
payload = {
|
||||||
|
"current_version": __version__,
|
||||||
|
"latest": "",
|
||||||
|
"has_update": False,
|
||||||
|
"ahead_of_release": False,
|
||||||
|
"html_url": RELEASES_PAGE_URL,
|
||||||
|
"error": "",
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
if check_now:
|
||||||
|
run_check(__version__)
|
||||||
|
payload.update(get_status())
|
||||||
|
payload["current_version"] = __version__
|
||||||
|
payload["html_url"] = payload.get("html_url") or RELEASES_PAGE_URL
|
||||||
|
except Exception as exc:
|
||||||
|
payload["error"] = str(exc)
|
||||||
|
return json.dumps(payload)
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,56 @@
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
app:cardCornerRadius="20dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="18dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/updates_label"
|
||||||
|
android:textAppearance="@style/TextAppearance.Material3.LabelLarge" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/currentVersionValue"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:textAppearance="@style/TextAppearance.Material3.BodySmall" />
|
||||||
|
|
||||||
|
<com.google.android.material.materialswitch.MaterialSwitch
|
||||||
|
android:id="@+id/checkUpdatesSwitch"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="14dp"
|
||||||
|
android:text="@string/updates_check_label" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/updateStatusValue"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/updates_status_initial"
|
||||||
|
android:textAppearance="@style/TextAppearance.Material3.BodyMedium" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/openReleasePageButton"
|
||||||
|
style="@style/Widget.Material3.Button.OutlinedButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="14dp"
|
||||||
|
android:text="@string/updates_open_release_button" />
|
||||||
|
</LinearLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|
@ -147,10 +197,28 @@
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="20dp"
|
android:layout_marginTop="20dp"
|
||||||
|
app:cardCornerRadius="20dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="18dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/endpoint_section_label"
|
||||||
|
android:textAppearance="@style/TextAppearance.Material3.LabelLarge" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="14dp"
|
||||||
android:hint="@string/host_hint">
|
android:hint="@string/host_hint">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
|
@ -189,12 +257,32 @@
|
||||||
android:inputType="textMultiLine"
|
android:inputType="textMultiLine"
|
||||||
android:minLines="5" />
|
android:minLines="5" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
app:cardCornerRadius="20dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="18dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/advanced_section_label"
|
||||||
|
android:textAppearance="@style/TextAppearance.Material3.LabelLarge" />
|
||||||
|
|
||||||
<com.google.android.material.materialswitch.MaterialSwitch
|
<com.google.android.material.materialswitch.MaterialSwitch
|
||||||
android:id="@+id/verboseSwitch"
|
android:id="@+id/verboseSwitch"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="14dp"
|
||||||
android:text="@string/verbose_label" />
|
android:text="@string/verbose_label" />
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
|
@ -238,6 +326,8 @@
|
||||||
android:inputType="number"
|
android:inputType="number"
|
||||||
android:maxLines="1" />
|
android:maxLines="1" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/errorText"
|
android:id="@+id/errorText"
|
||||||
|
|
@ -248,12 +338,30 @@
|
||||||
android:textColor="?attr/colorError"
|
android:textColor="?attr/colorError"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
app:cardCornerRadius="20dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="18dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/actions_section_label"
|
||||||
|
android:textAppearance="@style/TextAppearance.Material3.LabelLarge" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/saveButton"
|
android:id="@+id/saveButton"
|
||||||
style="@style/Widget.Material3.Button.OutlinedButton"
|
style="@style/Widget.Material3.Button.OutlinedButton"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="18dp"
|
android:layout_marginTop="14dp"
|
||||||
android:text="@string/save_button" />
|
android:text="@string/save_button" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
|
|
@ -295,6 +403,8 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="12dp"
|
android:layout_marginTop="12dp"
|
||||||
android:text="@string/open_logs_button" />
|
android:text="@string/open_logs_button" />
|
||||||
|
</LinearLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,20 @@
|
||||||
<string name="system_check_background_ok">Background restriction: not detected.</string>
|
<string name="system_check_background_ok">Background restriction: not detected.</string>
|
||||||
<string name="system_check_background_restricted">Background restriction: enabled, Android may block long-running work.</string>
|
<string name="system_check_background_restricted">Background restriction: enabled, Android may block long-running work.</string>
|
||||||
<string name="system_check_oem_note">Some phones also require manual vendor settings such as Autostart, Lock in recents, or Unrestricted battery mode.</string>
|
<string name="system_check_oem_note">Some phones also require manual vendor settings such as Autostart, Lock in recents, or Unrestricted battery mode.</string>
|
||||||
|
<string name="endpoint_section_label">Proxy endpoint</string>
|
||||||
|
<string name="routing_section_label">Routing</string>
|
||||||
|
<string name="advanced_section_label">Advanced</string>
|
||||||
|
<string name="actions_section_label">Actions</string>
|
||||||
|
<string name="updates_label">Updates</string>
|
||||||
|
<string name="updates_current_version_format">v%1$s</string>
|
||||||
|
<string name="updates_check_label">Check for updates on launch</string>
|
||||||
|
<string name="updates_status_initial">Status will appear after background check.</string>
|
||||||
|
<string name="updates_status_disabled">Automatic update checks are disabled.</string>
|
||||||
|
<string name="updates_status_latest">Installed version %1$s matches the latest GitHub release.</string>
|
||||||
|
<string name="updates_status_newer">Installed version %1$s is newer than the latest GitHub release.</string>
|
||||||
|
<string name="updates_status_available">Version %1$s is available on GitHub (installed: %2$s).</string>
|
||||||
|
<string name="updates_status_error">Update check failed: %1$s</string>
|
||||||
|
<string name="updates_open_release_button">Open Release Page</string>
|
||||||
<string name="host_hint">Proxy host</string>
|
<string name="host_hint">Proxy host</string>
|
||||||
<string name="port_hint">Proxy port</string>
|
<string name="port_hint">Proxy port</string>
|
||||||
<string name="dc_ip_hint">DC to IP mappings (one DC:IP per line)</string>
|
<string name="dc_ip_hint">DC to IP mappings (one DC:IP per line)</string>
|
||||||
|
|
@ -33,6 +47,7 @@
|
||||||
<string name="disable_battery_optimization_button">Disable Battery Optimization</string>
|
<string name="disable_battery_optimization_button">Disable Battery Optimization</string>
|
||||||
<string name="open_app_settings_button">Open App Settings</string>
|
<string name="open_app_settings_button">Open App Settings</string>
|
||||||
<string name="last_error_label">Last service error</string>
|
<string name="last_error_label">Last service error</string>
|
||||||
|
<string name="release_page_open_failed">Failed to open release page.</string>
|
||||||
<string name="settings_saved">Settings saved</string>
|
<string name="settings_saved">Settings saved</string>
|
||||||
<string name="service_start_requested">Foreground service start requested</string>
|
<string name="service_start_requested">Foreground service start requested</string>
|
||||||
<string name="service_restart_requested">Foreground service restart requested</string>
|
<string name="service_restart_requested">Foreground service restart requested</string>
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,38 @@ class AndroidProxyBridgeTests(unittest.TestCase):
|
||||||
self.assertEqual(captured["log_max_mb"], 7.0)
|
self.assertEqual(captured["log_max_mb"], 7.0)
|
||||||
self.assertTrue(captured["verbose"])
|
self.assertTrue(captured["verbose"])
|
||||||
|
|
||||||
|
def test_get_update_status_json_merges_python_update_state(self):
|
||||||
|
original_run_check = android_proxy_bridge.run_check
|
||||||
|
original_get_status = android_proxy_bridge.get_status
|
||||||
|
try:
|
||||||
|
captured = {}
|
||||||
|
|
||||||
|
def fake_run_check(version):
|
||||||
|
captured["run_check_version"] = version
|
||||||
|
|
||||||
|
def fake_get_status():
|
||||||
|
return {
|
||||||
|
"latest": "1.3.1",
|
||||||
|
"has_update": True,
|
||||||
|
"ahead_of_release": False,
|
||||||
|
"html_url": "https://example.com/release",
|
||||||
|
"error": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
android_proxy_bridge.run_check = fake_run_check
|
||||||
|
android_proxy_bridge.get_status = fake_get_status
|
||||||
|
|
||||||
|
result = json.loads(android_proxy_bridge.get_update_status_json(True))
|
||||||
|
finally:
|
||||||
|
android_proxy_bridge.run_check = original_run_check
|
||||||
|
android_proxy_bridge.get_status = original_get_status
|
||||||
|
|
||||||
|
self.assertEqual(captured["run_check_version"], android_proxy_bridge.__version__)
|
||||||
|
self.assertEqual(result["current_version"], android_proxy_bridge.__version__)
|
||||||
|
self.assertEqual(result["latest"], "1.3.1")
|
||||||
|
self.assertTrue(result["has_update"])
|
||||||
|
self.assertEqual(result["html_url"], "https://example.com/release")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ from typing import Any, Dict, Optional, Tuple
|
||||||
from urllib.error import HTTPError, URLError
|
from urllib.error import HTTPError, URLError
|
||||||
from urllib.request import Request, urlopen
|
from urllib.request import Request, urlopen
|
||||||
|
|
||||||
REPO = "Flowseal/tg-ws-proxy"
|
REPO = "Dark-Avery/tg-ws-proxy"
|
||||||
RELEASES_LATEST_API = f"https://api.github.com/repos/{REPO}/releases/latest"
|
RELEASES_LATEST_API = f"https://api.github.com/repos/{REPO}/releases/latest"
|
||||||
RELEASES_PAGE_URL = f"https://github.com/{REPO}/releases/latest"
|
RELEASES_PAGE_URL = f"https://github.com/{REPO}/releases/latest"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue