Fix mobile insets and update mirrors
All checks were successful
Gitea Smoke / smoke (push) Successful in 1s
All checks were successful
Gitea Smoke / smoke (push) Successful in 1s
This commit is contained in:
parent
8709c7bae5
commit
08637eafa6
12
README.md
12
README.md
@ -14,6 +14,7 @@ APIs directly:
|
||||
and kill session through HTTP API
|
||||
- open one live terminal viewer through `/ws/terminal`
|
||||
- native `/ws/events` listener for session invalidation and hook notifications
|
||||
- GitHub and Gitea update manifest mirrors with fallback
|
||||
- native API action center for health, server status, timeline, preferences,
|
||||
kanban projects, group messages, hook events, image file/URL upload, image
|
||||
preview info, and native image preview display
|
||||
@ -76,6 +77,8 @@ Publish a test build by pushing a `v*` tag. That creates a GitHub Release with:
|
||||
```text
|
||||
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://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
|
||||
```
|
||||
|
||||
Plain branch builds only create Actions artifacts; they are useful for CI
|
||||
@ -123,4 +126,13 @@ The default update manifest is:
|
||||
https://github.com/neatstudio/tmux-browser-android/releases/latest/download/latest.json
|
||||
```
|
||||
|
||||
The Gitea mirror is:
|
||||
|
||||
```text
|
||||
https://gitea.neatcn.com/tmux/tmux-browser-android/releases/latest/download/latest.json
|
||||
```
|
||||
|
||||
The app tries the selected update source first, then falls back to the GitHub
|
||||
and Gitea mirrors.
|
||||
|
||||
The workflow uploads both the APK and `latest.json` to each release.
|
||||
|
||||
@ -10,6 +10,8 @@ val defaultServerUrl = providers.gradleProperty("defaultServerUrl")
|
||||
.orElse("http://100.89.0.116:3000")
|
||||
val defaultUpdateUrl = providers.gradleProperty("defaultUpdateUrl")
|
||||
.orElse("https://github.com/${repoSlug.get()}/releases/latest/download/latest.json")
|
||||
val defaultGiteaUpdateUrl = providers.gradleProperty("defaultGiteaUpdateUrl")
|
||||
.orElse("https://gitea.neatcn.com/tmux/tmux-browser-android/releases/latest/download/latest.json")
|
||||
|
||||
val signingProps = Properties()
|
||||
val signingFile = rootProject.file("signing.properties")
|
||||
@ -30,6 +32,7 @@ android {
|
||||
|
||||
buildConfigField("String", "DEFAULT_SERVER_URL", "\"${defaultServerUrl.get()}\"")
|
||||
buildConfigField("String", "DEFAULT_UPDATE_URL", "\"${defaultUpdateUrl.get()}\"")
|
||||
buildConfigField("String", "DEFAULT_GITEA_UPDATE_URL", "\"${defaultGiteaUpdateUrl.get()}\"")
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
|
||||
@ -21,6 +21,7 @@ import android.text.InputType;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
@ -133,9 +134,24 @@ public final class MainActivity extends Activity {
|
||||
statusText.setBackgroundColor(Color.rgb(29, 34, 41));
|
||||
statusText.setSingleLine(true);
|
||||
setStatus("Ready");
|
||||
applySystemBarInsets(root);
|
||||
return root;
|
||||
}
|
||||
|
||||
private void applySystemBarInsets(View view) {
|
||||
view.setOnApplyWindowInsetsListener((target, insets) -> {
|
||||
target.setPadding(
|
||||
0,
|
||||
insets.getSystemWindowInsetTop(),
|
||||
0,
|
||||
insets.getSystemWindowInsetBottom()
|
||||
);
|
||||
return insets;
|
||||
});
|
||||
view.requestApplyInsets();
|
||||
view.post(view::requestApplyInsets);
|
||||
}
|
||||
|
||||
private void renderSessionScreen() {
|
||||
closeTerminalSocket();
|
||||
activeSessionName = null;
|
||||
@ -169,11 +185,14 @@ public final class MainActivity extends Activity {
|
||||
|
||||
private LinearLayout createServerBar() {
|
||||
LinearLayout toolbar = new LinearLayout(this);
|
||||
toolbar.setOrientation(LinearLayout.HORIZONTAL);
|
||||
toolbar.setGravity(Gravity.CENTER_VERTICAL);
|
||||
toolbar.setOrientation(LinearLayout.VERTICAL);
|
||||
toolbar.setPadding(dp(8), dp(6), dp(8), dp(6));
|
||||
toolbar.setBackgroundColor(Color.rgb(17, 20, 24));
|
||||
|
||||
LinearLayout urlRow = new LinearLayout(this);
|
||||
urlRow.setOrientation(LinearLayout.HORIZONTAL);
|
||||
urlRow.setGravity(Gravity.CENTER_VERTICAL);
|
||||
|
||||
urlField = new EditText(this);
|
||||
urlField.setSingleLine(true);
|
||||
urlField.setTextColor(Color.WHITE);
|
||||
@ -190,11 +209,20 @@ public final class MainActivity extends Activity {
|
||||
}
|
||||
return false;
|
||||
});
|
||||
toolbar.addView(urlField, new LinearLayout.LayoutParams(0, dp(42), 1));
|
||||
toolbar.addView(toolbarButton("Go", view -> saveServerAndRefresh()));
|
||||
toolbar.addView(toolbarButton("New", view -> promptCreateSession()));
|
||||
toolbar.addView(toolbarButton("Update", view -> updateManager.check(true)));
|
||||
toolbar.addView(toolbarButton("More", view -> showMainActions()));
|
||||
urlRow.addView(urlField, new LinearLayout.LayoutParams(0, dp(44), 1));
|
||||
urlRow.addView(toolbarButton("Go", view -> saveServerAndRefresh()));
|
||||
|
||||
LinearLayout actionRow = new LinearLayout(this);
|
||||
actionRow.setOrientation(LinearLayout.HORIZONTAL);
|
||||
actionRow.setGravity(Gravity.CENTER_VERTICAL);
|
||||
actionRow.setPadding(0, dp(6), 0, 0);
|
||||
actionRow.addView(toolbarButton("New", view -> promptCreateSession()));
|
||||
actionRow.addView(toolbarButton("Refresh", view -> refreshSessions()));
|
||||
actionRow.addView(toolbarButton("Update", view -> updateManager.check(true)));
|
||||
actionRow.addView(toolbarButton("More", view -> showMainActions()));
|
||||
|
||||
toolbar.addView(urlRow, matchWrap());
|
||||
toolbar.addView(actionRow, matchWrap());
|
||||
return toolbar;
|
||||
}
|
||||
|
||||
@ -435,6 +463,7 @@ public final class MainActivity extends Activity {
|
||||
"Upload image URL",
|
||||
"Image preview info",
|
||||
"Open image preview",
|
||||
"Update source",
|
||||
"Permissions / update status"
|
||||
};
|
||||
new AlertDialog.Builder(this)
|
||||
@ -496,6 +525,9 @@ public final class MainActivity extends Activity {
|
||||
promptOpenImagePreview();
|
||||
break;
|
||||
case 18:
|
||||
showUpdateSourcePicker();
|
||||
break;
|
||||
case 19:
|
||||
showPermissionsAndUpdateStatus();
|
||||
break;
|
||||
default:
|
||||
@ -883,9 +915,11 @@ public final class MainActivity extends Activity {
|
||||
private void showPermissionsAndUpdateStatus() {
|
||||
StringBuilder text = new StringBuilder();
|
||||
text.append("Server: ").append(getServerUrl()).append('\n');
|
||||
text.append("Update manifest: ")
|
||||
text.append("Selected update manifest: ")
|
||||
.append(prefs.getString("update_url", BuildConfig.DEFAULT_UPDATE_URL))
|
||||
.append('\n');
|
||||
text.append("GitHub mirror: ").append(BuildConfig.DEFAULT_UPDATE_URL).append('\n');
|
||||
text.append("Gitea mirror: ").append(BuildConfig.DEFAULT_GITEA_UPDATE_URL).append('\n');
|
||||
text.append("Network: manifest permission, no runtime grant required\n");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
text.append("Install unknown apps: ")
|
||||
@ -912,6 +946,31 @@ public final class MainActivity extends Activity {
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showUpdateSourcePicker() {
|
||||
String[] items = {
|
||||
"GitHub: " + BuildConfig.DEFAULT_UPDATE_URL,
|
||||
"Gitea: " + BuildConfig.DEFAULT_GITEA_UPDATE_URL,
|
||||
"Custom URL"
|
||||
};
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("Update source")
|
||||
.setItems(items, (dialog, which) -> {
|
||||
if (which == 0) {
|
||||
setUpdateUrl(BuildConfig.DEFAULT_UPDATE_URL);
|
||||
} else if (which == 1) {
|
||||
setUpdateUrl(BuildConfig.DEFAULT_GITEA_UPDATE_URL);
|
||||
} else {
|
||||
promptText("Custom update manifest", "https://.../latest.json", prefs.getString("update_url", BuildConfig.DEFAULT_UPDATE_URL), this::setUpdateUrl);
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private void setUpdateUrl(String url) {
|
||||
prefs.edit().putString("update_url", url).apply();
|
||||
showMessage("Update source: " + url);
|
||||
}
|
||||
|
||||
private void openInstallPermissionSettings() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
showMessage("Install permission is allowed on this Android version");
|
||||
|
||||
@ -19,6 +19,9 @@ import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
@ -41,12 +44,12 @@ final class UpdateManager {
|
||||
}
|
||||
|
||||
void check(boolean userInitiated) {
|
||||
String manifestUrl = prefs.getString("update_url", BuildConfig.DEFAULT_UPDATE_URL);
|
||||
List<String> manifestUrls = getUpdateManifestUrls();
|
||||
callback.onChecking(true);
|
||||
postMessage("Checking update...");
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
ReleaseInfo info = fetchReleaseInfo(manifestUrl);
|
||||
ReleaseInfo info = fetchFirstReleaseInfo(manifestUrls);
|
||||
if (info.versionCode <= BuildConfig.VERSION_CODE) {
|
||||
postMessage("Already up to date: " + BuildConfig.VERSION_NAME);
|
||||
return;
|
||||
@ -61,6 +64,42 @@ final class UpdateManager {
|
||||
});
|
||||
}
|
||||
|
||||
private List<String> getUpdateManifestUrls() {
|
||||
LinkedHashSet<String> urls = new LinkedHashSet<>();
|
||||
addUrl(urls, prefs.getString("update_url", BuildConfig.DEFAULT_UPDATE_URL));
|
||||
addUrl(urls, BuildConfig.DEFAULT_UPDATE_URL);
|
||||
addUrl(urls, BuildConfig.DEFAULT_GITEA_UPDATE_URL);
|
||||
return new ArrayList<>(urls);
|
||||
}
|
||||
|
||||
private void addUrl(LinkedHashSet<String> urls, String url) {
|
||||
if (url != null && !url.trim().isEmpty()) {
|
||||
urls.add(url.trim());
|
||||
}
|
||||
}
|
||||
|
||||
private ReleaseInfo fetchFirstReleaseInfo(List<String> manifestUrls) throws Exception {
|
||||
Exception lastError = null;
|
||||
for (String manifestUrl : manifestUrls) {
|
||||
try {
|
||||
postMessage("Checking " + hostLabel(manifestUrl) + "...");
|
||||
return fetchReleaseInfo(manifestUrl);
|
||||
} catch (Exception error) {
|
||||
lastError = error;
|
||||
postMessage(hostLabel(manifestUrl) + " failed");
|
||||
}
|
||||
}
|
||||
throw lastError == null ? new IllegalStateException("No update source configured") : lastError;
|
||||
}
|
||||
|
||||
private String hostLabel(String url) {
|
||||
try {
|
||||
return new URL(url).getHost();
|
||||
} catch (Exception ignored) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
private ReleaseInfo fetchReleaseInfo(String manifestUrl) throws Exception {
|
||||
String json = readText(manifestUrl);
|
||||
JSONObject root = new JSONObject(json);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user