Resolve manual update links from selected source
Some checks failed
Gitea Smoke / smoke (push) Successful in 0s
Gitea Android APK / build (push) Has been cancelled

This commit is contained in:
Codex 2026-07-06 12:56:08 +00:00
parent 79fd148511
commit 0609c73d35
3 changed files with 56 additions and 20 deletions

View File

@ -177,8 +177,8 @@ In the app:
- If Android sends you to the unknown-app install permission screen, return to - If Android sends you to the unknown-app install permission screen, return to
the app after allowing it; the app continues installing the already downloaded the app after allowing it; the app continues installing the already downloaded
APK without another update check or download. APK without another update check or download.
- Open the `Update` page and tap `APK` to download the current public APK in a - Open the `Update` page and tap `APK` to resolve the APK from the selected
browser. update source and open it in a browser.
- Open the `Update` page and tap `Details` to see the installed version, - Open the `Update` page and tap `Details` to see the installed version,
selected update source, APK URL, install permission state, and a `Check selected update source, APK URL, install permission state, and a `Check
update` button. update` button.

View File

@ -3,7 +3,6 @@ package com.neatstudio.tmuxandroid;
import android.Manifest; import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.ClipData; import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
@ -296,7 +295,7 @@ public final class MainActivity extends Activity {
content.addView(actionPanel( content.addView(actionPanel(
actionButton("Check now", view -> updateManager.check(true)), actionButton("Check now", view -> updateManager.check(true)),
actionButton("Source", view -> showUpdateSourcePicker()), actionButton("Source", view -> showUpdateSourcePicker()),
actionButton("APK", view -> openExternalUrl(BuildConfig.DEFAULT_APK_URL)) actionButton("APK", view -> updateManager.openApkDownload())
)); ));
content.addView(sectionTitle("Permissions")); content.addView(sectionTitle("Permissions"));
content.addView(infoBlock("Android", permissionSummary())); content.addView(infoBlock("Android", permissionSummary()));
@ -351,7 +350,7 @@ public final class MainActivity extends Activity {
"The app checks only the selected update source. APK downloads are cached by version and reused after Android install permission is granted." "The app checks only the selected update source. APK downloads are cached by version and reused after Android install permission is granted."
)); ));
content.addView(actionPanel( content.addView(actionPanel(
actionButton("Release page", view -> openExternalUrl(BuildConfig.DEFAULT_RELEASE_PAGE_URL)), actionButton("Release page", view -> updateManager.openReleasePage()),
actionButton("Update source", view -> showUpdateSourcePicker()), actionButton("Update source", view -> showUpdateSourcePicker()),
actionButton("Permissions", view -> showPermissionsAndUpdateStatus()) actionButton("Permissions", view -> showPermissionsAndUpdateStatus())
)); ));
@ -429,7 +428,7 @@ public final class MainActivity extends Activity {
return; return;
} }
if (PAGE_ABOUT.equals(activeMainPage)) { if (PAGE_ABOUT.equals(activeMainPage)) {
actionRow.addView(toolbarButton("Release", view -> openExternalUrl(BuildConfig.DEFAULT_RELEASE_PAGE_URL))); actionRow.addView(toolbarButton("Release", view -> updateManager.openReleasePage()));
actionRow.addView(toolbarButton("Permissions", view -> showPermissionsAndUpdateStatus())); actionRow.addView(toolbarButton("Permissions", view -> showPermissionsAndUpdateStatus()));
} }
} }
@ -1181,12 +1180,10 @@ public final class MainActivity extends Activity {
.append(")\n"); .append(")\n");
text.append("Server: ").append(getServerUrl()).append('\n'); text.append("Server: ").append(getServerUrl()).append('\n');
text.append('\n'); text.append('\n');
text.append("Manual APK download:\n") text.append("Manual APK download:\n");
.append(BuildConfig.DEFAULT_APK_URL) text.append("Tap APK on the Update page. The app resolves the APK from the selected update source.\n");
.append('\n'); text.append("Release page:\n");
text.append("Release page:\n") text.append("Tap Release on the About page. The app resolves the page from the selected update source.\n");
.append(BuildConfig.DEFAULT_RELEASE_PAGE_URL)
.append('\n');
text.append('\n'); text.append('\n');
text.append("In-app update:\n"); text.append("In-app update:\n");
text.append("Tap Update. The app checks only the selected source, downloads one APK per version, verifies SHA-256, then opens Android's installer.\n"); text.append("Tap Update. The app checks only the selected source, downloads one APK per version, verifies SHA-256, then opens Android's installer.\n");
@ -1229,14 +1226,6 @@ public final class MainActivity extends Activity {
return text.toString(); return text.toString();
} }
private void openExternalUrl(String url) {
try {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
} catch (ActivityNotFoundException error) {
showMessage("No app can open URL");
}
}
private void showUpdateSourcePicker() { private void showUpdateSourcePicker() {
String[] items = { String[] items = {
"GitHub: " + BuildConfig.DEFAULT_UPDATE_URL, "GitHub: " + BuildConfig.DEFAULT_UPDATE_URL,

View File

@ -32,6 +32,10 @@ final class UpdateManager {
void onMessage(String message); void onMessage(String message);
} }
private interface ReleaseUrlPicker {
String pick(ReleaseInfo info);
}
private final Activity activity; private final Activity activity;
private final SharedPreferences prefs; private final SharedPreferences prefs;
private final Callback callback; private final Callback callback;
@ -73,6 +77,40 @@ final class UpdateManager {
}); });
} }
void openApkDownload() {
openSelectedReleaseUrl("APK", info -> info.apkUrl);
}
void openReleasePage() {
openSelectedReleaseUrl("Release page", info -> info.releasePageUrl);
}
private void openSelectedReleaseUrl(String label, ReleaseUrlPicker picker) {
if (checkInProgress) {
postMessage("Update check already running");
return;
}
String manifestUrl = getUpdateManifestUrl();
checkInProgress = true;
callback.onChecking(true);
postMessage("Resolving " + label + " from " + hostLabel(manifestUrl) + "...");
executor.execute(() -> {
try {
ReleaseInfo info = fetchReleaseInfo(manifestUrl);
String url = picker.pick(info);
if (url == null || url.trim().isEmpty()) {
throw new IllegalStateException(label + " URL is missing");
}
activity.runOnUiThread(() -> openExternalUrl(url.trim(), label));
} catch (Exception error) {
postMessage(label + " failed: " + error.getMessage());
} finally {
checkInProgress = false;
activity.runOnUiThread(() -> callback.onChecking(false));
}
});
}
private String getUpdateManifestUrl() { private String getUpdateManifestUrl() {
String url = prefs.getString("update_url", BuildConfig.DEFAULT_UPDATE_URL); String url = prefs.getString("update_url", BuildConfig.DEFAULT_UPDATE_URL);
if (url == null || url.trim().isEmpty()) { if (url == null || url.trim().isEmpty()) {
@ -260,6 +298,15 @@ final class UpdateManager {
} }
} }
private void openExternalUrl(String url, String label) {
try {
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
postMessage("Opened " + label);
} catch (ActivityNotFoundException error) {
postMessage("No app can open " + label);
}
}
void resumePendingInstall() { void resumePendingInstall() {
File apk = pendingInstallApk; File apk = pendingInstallApk;
if (apk == null) { if (apk == null) {