Clarify app download and update paths
All checks were successful
Gitea Smoke / smoke (push) Successful in 0s
Gitea Android APK / build (push) Successful in 11m42s

This commit is contained in:
Codex 2026-07-05 19:06:31 +00:00
parent 4c5cd385b7
commit d655fd7e09
3 changed files with 62 additions and 11 deletions

View File

@ -77,10 +77,13 @@ Publish a test build by pushing a `v*` tag. That creates a GitHub Release with:
```text ```text
https://github.com/neatstudio/tmux-browser-android/releases/latest/download/tmux-android.apk https://github.com/neatstudio/tmux-browser-android/releases/latest/download/tmux-android.apk
https://github.com/neatstudio/tmux-browser-android/releases/latest/download/latest.json https://github.com/neatstudio/tmux-browser-android/releases/latest/download/latest.json
https://gitea.neatcn.com/tmux/tmux-browser-android/releases/latest/download/tmux-android.apk
https://gitea.neatcn.com/tmux/tmux-browser-android/releases/latest/download/latest.json
``` ```
Those GitHub links are the stable public install/update channel. Gitea releases
are mirrored for internal tracking, but the current Gitea org/repo visibility
keeps anonymous release downloads behind login, so phones should use GitHub for
no-login install and in-app updates.
Plain branch builds only create Actions artifacts; they are useful for CI Plain branch builds only create Actions artifacts; they are useful for CI
verification, but releases are the stable download/update channel. verification, but releases are the stable download/update channel.
@ -126,13 +129,26 @@ The default update manifest is:
https://github.com/neatstudio/tmux-browser-android/releases/latest/download/latest.json https://github.com/neatstudio/tmux-browser-android/releases/latest/download/latest.json
``` ```
The Gitea mirror is: The public manual APK download is:
```text ```text
https://gitea.neatcn.com/tmux/tmux-browser-android/releases/latest/download/latest.json https://github.com/neatstudio/tmux-browser-android/releases/latest/download/tmux-android.apk
``` ```
The app tries the selected update source first, then falls back to the GitHub The app tries the selected update source first, then falls back to the GitHub
and Gitea mirrors. manifest and the Gitea release API. GitHub is the reliable no-login source today.
The Gitea API endpoint is:
The workflow uploads both the APK and `latest.json` to each release. ```text
https://gitea.neatcn.com/api/v1/repos/tmux/tmux-browser-android/releases/latest
```
In the app:
- Tap `Update` on the main screen to check `latest.json`, download the APK,
verify SHA-256, and open Android's installer.
- Open `More` -> `Open APK download` to download the current public APK in a
browser.
- Open `More` -> `Permissions / update status` to see the installed version,
update manifest, APK URL, install permission state, and a `Check update`
button.

View File

@ -12,6 +12,10 @@ val defaultUpdateUrl = providers.gradleProperty("defaultUpdateUrl")
.orElse("https://github.com/${repoSlug.get()}/releases/latest/download/latest.json") .orElse("https://github.com/${repoSlug.get()}/releases/latest/download/latest.json")
val defaultGiteaUpdateUrl = providers.gradleProperty("defaultGiteaUpdateUrl") val defaultGiteaUpdateUrl = providers.gradleProperty("defaultGiteaUpdateUrl")
.orElse("https://gitea.neatcn.com/api/v1/repos/tmux/tmux-browser-android/releases/latest") .orElse("https://gitea.neatcn.com/api/v1/repos/tmux/tmux-browser-android/releases/latest")
val defaultApkUrl = providers.gradleProperty("defaultApkUrl")
.orElse("https://github.com/${repoSlug.get()}/releases/latest/download/tmux-android.apk")
val defaultReleasePageUrl = providers.gradleProperty("defaultReleasePageUrl")
.orElse("https://github.com/${repoSlug.get()}/releases/latest")
val signingProps = Properties() val signingProps = Properties()
val signingFile = rootProject.file("signing.properties") val signingFile = rootProject.file("signing.properties")
@ -33,6 +37,8 @@ android {
buildConfigField("String", "DEFAULT_SERVER_URL", "\"${defaultServerUrl.get()}\"") buildConfigField("String", "DEFAULT_SERVER_URL", "\"${defaultServerUrl.get()}\"")
buildConfigField("String", "DEFAULT_UPDATE_URL", "\"${defaultUpdateUrl.get()}\"") buildConfigField("String", "DEFAULT_UPDATE_URL", "\"${defaultUpdateUrl.get()}\"")
buildConfigField("String", "DEFAULT_GITEA_UPDATE_URL", "\"${defaultGiteaUpdateUrl.get()}\"") buildConfigField("String", "DEFAULT_GITEA_UPDATE_URL", "\"${defaultGiteaUpdateUrl.get()}\"")
buildConfigField("String", "DEFAULT_APK_URL", "\"${defaultApkUrl.get()}\"")
buildConfigField("String", "DEFAULT_RELEASE_PAGE_URL", "\"${defaultReleasePageUrl.get()}\"")
} }
buildFeatures { buildFeatures {

View File

@ -3,6 +3,7 @@ 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;
@ -463,6 +464,7 @@ public final class MainActivity extends Activity {
"Upload image URL", "Upload image URL",
"Image preview info", "Image preview info",
"Open image preview", "Open image preview",
"Open APK download",
"Update source", "Update source",
"Permissions / update status" "Permissions / update status"
}; };
@ -525,9 +527,12 @@ public final class MainActivity extends Activity {
promptOpenImagePreview(); promptOpenImagePreview();
break; break;
case 18: case 18:
showUpdateSourcePicker(); openExternalUrl(BuildConfig.DEFAULT_APK_URL);
break; break;
case 19: case 19:
showUpdateSourcePicker();
break;
case 20:
showPermissionsAndUpdateStatus(); showPermissionsAndUpdateStatus();
break; break;
default: default:
@ -914,12 +919,28 @@ public final class MainActivity extends Activity {
private void showPermissionsAndUpdateStatus() { private void showPermissionsAndUpdateStatus() {
StringBuilder text = new StringBuilder(); StringBuilder text = new StringBuilder();
text.append("Installed app: ")
.append(BuildConfig.VERSION_NAME)
.append(" (")
.append(BuildConfig.VERSION_CODE)
.append(")\n");
text.append("Server: ").append(getServerUrl()).append('\n'); text.append("Server: ").append(getServerUrl()).append('\n');
text.append("Selected update manifest: ") text.append('\n');
text.append("Manual APK download:\n")
.append(BuildConfig.DEFAULT_APK_URL)
.append('\n');
text.append("Release page:\n")
.append(BuildConfig.DEFAULT_RELEASE_PAGE_URL)
.append('\n');
text.append('\n');
text.append("In-app update:\n");
text.append("Tap Update. The app reads latest.json, downloads the APK, verifies SHA-256, then opens Android's installer.\n");
text.append("Selected manifest:\n")
.append(prefs.getString("update_url", BuildConfig.DEFAULT_UPDATE_URL)) .append(prefs.getString("update_url", BuildConfig.DEFAULT_UPDATE_URL))
.append('\n'); .append('\n');
text.append("GitHub mirror: ").append(BuildConfig.DEFAULT_UPDATE_URL).append('\n'); text.append("GitHub manifest:\n").append(BuildConfig.DEFAULT_UPDATE_URL).append('\n');
text.append("Gitea mirror: ").append(BuildConfig.DEFAULT_GITEA_UPDATE_URL).append('\n'); text.append("Gitea release API:\n").append(BuildConfig.DEFAULT_GITEA_UPDATE_URL).append('\n');
text.append('\n');
text.append("Network: manifest permission, no runtime grant required\n"); text.append("Network: manifest permission, no runtime grant required\n");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
text.append("Install unknown apps: ") text.append("Install unknown apps: ")
@ -942,10 +963,18 @@ public final class MainActivity extends Activity {
.setMessage(text.toString()) .setMessage(text.toString())
.setNegativeButton("Close", null) .setNegativeButton("Close", null)
.setNeutralButton("Install permission", (dialog, which) -> openInstallPermissionSettings()) .setNeutralButton("Install permission", (dialog, which) -> openInstallPermissionSettings())
.setPositiveButton("Notify permission", (dialog, which) -> requestNotificationPermission()) .setPositiveButton("Check update", (dialog, which) -> updateManager.check(true))
.show(); .show();
} }
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,