Add v9.0: Auto-update system with Gitea integration

- Implement UpdateManager to check Gitea releases for new versions
- Add update dialogs with mandatory/optional update support
- Integrate DownloadManager for APK downloads
- Add FileProvider configuration for app installation
- Support update-manifest.json for version control enforcement
- Add comprehensive update strings and error handling
- Include gradlew executable permissions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
renato97
2025-11-23 21:14:14 +01:00
parent d2d66a7906
commit cf11aa04bc
9 changed files with 746 additions and 5 deletions

View File

@@ -1,10 +1,13 @@
package com.streamplayer;
import android.app.AlertDialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
@@ -35,6 +38,8 @@ public class MainActivity extends AppCompatActivity {
private final List<EventItem> cachedEvents = new ArrayList<>();
private List<SectionEntry> sections;
private SectionEntry currentSection;
private UpdateManager updateManager;
private AlertDialog updateDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -60,6 +65,45 @@ public class MainActivity extends AppCompatActivity {
sectionList.setAdapter(sectionAdapter);
selectSection(0);
updateManager = new UpdateManager(this);
updateManager.checkForUpdates(new UpdateManager.UpdateCallback() {
@Override
public void onUpdateAvailable(UpdateManager.UpdateInfo info) {
handleUpdateInfo(info);
}
@Override
public void onUpToDate() {
// Nothing to do.
}
@Override
public void onError(String message) {
Toast.makeText(MainActivity.this,
getString(R.string.update_error_checking, message),
Toast.LENGTH_SHORT).show();
}
});
}
@Override
protected void onResume() {
super.onResume();
if (updateManager != null) {
updateManager.resumePendingInstall(this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (updateDialog != null && updateDialog.isShowing()) {
updateDialog.dismiss();
}
if (updateManager != null) {
updateManager.release();
}
}
private void selectSection(int index) {
@@ -151,6 +195,87 @@ public class MainActivity extends AppCompatActivity {
startActivity(intent);
}
private void handleUpdateInfo(UpdateManager.UpdateInfo info) {
if (info == null) {
return;
}
boolean forceUpdate = info.isMandatory(BuildConfig.VERSION_CODE);
showUpdateDialog(info, forceUpdate);
}
private void showUpdateDialog(UpdateManager.UpdateInfo info, boolean mandatory) {
if (isFinishing()) {
return;
}
if (updateDialog != null && updateDialog.isShowing()) {
updateDialog.dismiss();
}
AlertDialog.Builder builder = new AlertDialog.Builder(this)
.setTitle(mandatory ? R.string.update_required_title : R.string.update_available_title)
.setMessage(buildUpdateMessage(info))
.setPositiveButton(R.string.update_action_download,
(dialog, which) -> updateManager.downloadUpdate(MainActivity.this, info))
.setNeutralButton(R.string.update_action_view_release,
(dialog, which) -> openReleasePage(info));
if (mandatory) {
builder.setCancelable(false);
builder.setNegativeButton(R.string.update_action_close_app,
(dialog, which) -> finish());
} else {
builder.setNegativeButton(R.string.update_action_later, null);
}
updateDialog = builder.show();
}
private CharSequence buildUpdateMessage(UpdateManager.UpdateInfo info) {
StringBuilder builder = new StringBuilder();
builder.append(getString(R.string.update_current_version,
BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
builder.append('\n');
builder.append(getString(R.string.update_latest_version,
info.versionName, info.versionCode));
if (info.minSupportedVersionCode > 0) {
builder.append('\n').append(getString(R.string.update_min_supported,
info.minSupportedVersionCode));
}
String size = info.formatSize(this);
if (!size.isEmpty()) {
builder.append('\n').append(getString(R.string.update_download_size, size));
}
if (info.downloadCount > 0) {
builder.append('\n').append(getString(R.string.update_downloads,
info.downloadCount));
}
if (!info.releaseNotes.isEmpty()) {
builder.append("\n\n");
builder.append(getString(R.string.update_release_notes_title));
builder.append('\n');
builder.append(info.getReleaseNotesPreview());
}
if (!info.isMandatory(BuildConfig.VERSION_CODE)) {
builder.append("\n\n");
builder.append(getString(R.string.update_optional_hint));
}
return builder.toString();
}
private void openReleasePage(UpdateManager.UpdateInfo info) {
String url = info.releasePageUrl;
if (url == null || url.isEmpty()) {
url = info.downloadUrl;
}
if (url == null || url.isEmpty()) {
Toast.makeText(this, R.string.update_error_missing_url, Toast.LENGTH_SHORT).show();
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
try {
startActivity(intent);
} catch (Exception e) {
Toast.makeText(this, R.string.update_error_open_release, Toast.LENGTH_SHORT).show();
}
}
private int getSpanCount() {
return getResources().getInteger(R.integer.channel_grid_span);
}