mirror of
https://github.com/jahruz67/Bible-Daily.git
synced 2026-08-08 18:14:05 +00:00
Enhance Android build process and add settings for update management
- Implement caching for Android debug keystore - Modify APK build command to include CI versioning - Add logic to prepare release assets and upload to GitHub - Update .gitignore to exclude build directories - Introduce SettingsActivity for managing auto-update preferences - Add UpdateManager for handling update checks and dialogs - Integrate update check on app startup in MainActivity
This commit is contained in:
@@ -26,18 +26,51 @@ jobs:
|
|||||||
- name: Set up Gradle
|
- name: Set up Gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
|
||||||
|
- name: Cache Android debug keystore
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.android/debug.keystore
|
||||||
|
key: android-debug-keystore-${{ runner.os }}
|
||||||
|
|
||||||
- name: Make Gradle wrapper executable
|
- name: Make Gradle wrapper executable
|
||||||
run: chmod +x gradlew
|
run: chmod +x gradlew
|
||||||
|
|
||||||
- name: Build debug APK
|
- name: Build debug APK
|
||||||
run: ./gradlew assembleDebug
|
run: ./gradlew assembleDebug -PciVersionCode=${{ github.run_number }} -PciVersionName=1.0.${{ github.run_number }}
|
||||||
|
|
||||||
|
- name: Prepare release assets
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
VERSION_CODE="${{ github.run_number }}"
|
||||||
|
VERSION_NAME="1.0.${{ github.run_number }}"
|
||||||
|
APK_URL="https://github.com/jahruz67/Bible-Daily/releases/download/latest/app-debug.apk"
|
||||||
|
APK_PATH="$(find app/build/outputs/apk/debug -maxdepth 1 -name '*.apk' -print -quit)"
|
||||||
|
|
||||||
|
if [ -z "$APK_PATH" ]; then
|
||||||
|
echo "No APK found in app/build/outputs/apk/debug"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp "$APK_PATH" app-debug.apk
|
||||||
|
cat > latest.json <<EOF
|
||||||
|
{
|
||||||
|
"versionCode": $VERSION_CODE,
|
||||||
|
"versionName": "$VERSION_NAME",
|
||||||
|
"apkUrl": "$APK_URL",
|
||||||
|
"commit": "${GITHUB_SHA}",
|
||||||
|
"builtAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
- name: Upload APK to latest release
|
- name: Upload APK to latest release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
tag_name: latest
|
tag_name: latest
|
||||||
name: Latest APK
|
name: Latest APK
|
||||||
files: app/build/outputs/apk/debug/app-debug.apk
|
files: |
|
||||||
|
app-debug.apk
|
||||||
|
latest.json
|
||||||
make_latest: true
|
make_latest: true
|
||||||
|
overwrite_files: true
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
+3
-1
@@ -1,2 +1,4 @@
|
|||||||
/.gradle
|
/.gradle
|
||||||
/.idea
|
/.idea
|
||||||
|
/build
|
||||||
|
/app/build
|
||||||
|
|||||||
+5
-2
@@ -2,6 +2,9 @@ plugins {
|
|||||||
id "com.android.application"
|
id "com.android.application"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def ciVersionCode = providers.gradleProperty("ciVersionCode").getOrNull()
|
||||||
|
def ciVersionName = providers.gradleProperty("ciVersionName").getOrNull()
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace "com.bibliadiaria.app"
|
namespace "com.bibliadiaria.app"
|
||||||
compileSdk 35
|
compileSdk 35
|
||||||
@@ -10,8 +13,8 @@ android {
|
|||||||
applicationId "com.bibliadiaria.app"
|
applicationId "com.bibliadiaria.app"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 35
|
targetSdk 35
|
||||||
versionCode 1
|
versionCode ciVersionCode != null ? ciVersionCode.toInteger() : 1
|
||||||
versionName "1.0"
|
versionName ciVersionName != null ? ciVersionName : "1.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
android:usesCleartextTraffic="false">
|
android:usesCleartextTraffic="false">
|
||||||
|
<activity
|
||||||
|
android:name=".SettingsActivity"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ExtrasActivity"
|
android:name=".ExtrasActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.bibliadiaria.app;
|
package com.bibliadiaria.app;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.graphics.drawable.GradientDrawable;
|
import android.graphics.drawable.GradientDrawable;
|
||||||
@@ -153,6 +154,18 @@ public class ExtrasActivity extends Activity {
|
|||||||
1f
|
1f
|
||||||
));
|
));
|
||||||
|
|
||||||
|
TextView settings = textView("Ajustes", 15, COLOR_ACCENT, Typeface.BOLD);
|
||||||
|
settings.setGravity(Gravity.CENTER);
|
||||||
|
settings.setPadding(dp(14), 0, dp(14), 0);
|
||||||
|
settings.setBackground(roundedRect(Color.WHITE, dp(8), Color.rgb(219, 226, 222), dp(1)));
|
||||||
|
settings.setOnClickListener(view -> startActivity(new Intent(this, SettingsActivity.class)));
|
||||||
|
LinearLayout.LayoutParams settingsParams = new LinearLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
dp(40)
|
||||||
|
);
|
||||||
|
settingsParams.setMargins(0, 0, dp(8), 0);
|
||||||
|
topBar.addView(settings, settingsParams);
|
||||||
|
|
||||||
TextView home = textView("Inicio", 15, COLOR_ACCENT, Typeface.BOLD);
|
TextView home = textView("Inicio", 15, COLOR_ACCENT, Typeface.BOLD);
|
||||||
home.setGravity(Gravity.CENTER);
|
home.setGravity(Gravity.CENTER);
|
||||||
home.setPadding(dp(14), 0, dp(14), 0);
|
home.setPadding(dp(14), 0, dp(14), 0);
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ public class MainActivity extends Activity {
|
|||||||
|
|
||||||
setContentView(createScreen());
|
setContentView(createScreen());
|
||||||
loadToday();
|
loadToday();
|
||||||
|
checkForUpdateOnStartup();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -600,6 +601,23 @@ public class MainActivity extends Activity {
|
|||||||
loadSelectedDate();
|
loadSelectedDate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkForUpdateOnStartup() {
|
||||||
|
if (!UpdateManager.isAutoUpdatesEnabled(this) || executor == null || executor.isShutdown()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
executor.submit(() -> {
|
||||||
|
try {
|
||||||
|
UpdateManager.UpdateInfo info = UpdateManager.fetchLatestUpdate(this);
|
||||||
|
if (info.updateAvailable) {
|
||||||
|
runOnUiThread(() -> UpdateManager.showUpdateDialog(this, info));
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
// Startup update checks stay quiet so daily readings remain the main experience.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void loadSelectedDate() {
|
private void loadSelectedDate() {
|
||||||
loadDate(selectedDateCalendar.getTime());
|
loadDate(selectedDateCalendar.getTime());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,331 @@
|
|||||||
|
package com.bibliadiaria.app;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.graphics.drawable.GradientDrawable;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ScrollView;
|
||||||
|
import android.widget.Switch;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
public class SettingsActivity extends Activity {
|
||||||
|
private static final int COLOR_BACKGROUND = Color.rgb(247, 248, 246);
|
||||||
|
private static final int COLOR_CARD = Color.WHITE;
|
||||||
|
private static final int COLOR_INK = Color.rgb(25, 31, 31);
|
||||||
|
private static final int COLOR_MUTED = Color.rgb(91, 99, 98);
|
||||||
|
private static final int COLOR_ACCENT = Color.rgb(0, 107, 90);
|
||||||
|
private static final int COLOR_WARM = Color.rgb(217, 75, 61);
|
||||||
|
|
||||||
|
private ExecutorService executor;
|
||||||
|
private TextView statusText;
|
||||||
|
private TextView installedVersionText;
|
||||||
|
private TextView checkButton;
|
||||||
|
private TextView downloadButton;
|
||||||
|
private UpdateManager.UpdateInfo latestInfo;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
getWindow().setStatusBarColor(COLOR_BACKGROUND);
|
||||||
|
getWindow().setNavigationBarColor(COLOR_BACKGROUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
executor = Executors.newSingleThreadExecutor();
|
||||||
|
setContentView(createScreen());
|
||||||
|
updateInstalledVersionText();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
if (executor != null) {
|
||||||
|
executor.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private View createScreen() {
|
||||||
|
FrameLayout root = new FrameLayout(this);
|
||||||
|
root.setBackgroundColor(COLOR_BACKGROUND);
|
||||||
|
|
||||||
|
ScrollView scrollView = new ScrollView(this);
|
||||||
|
scrollView.setFillViewport(true);
|
||||||
|
scrollView.setClipToPadding(false);
|
||||||
|
|
||||||
|
LinearLayout content = new LinearLayout(this);
|
||||||
|
content.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
content.setPadding(dp(20), dp(28), dp(20), dp(28));
|
||||||
|
scrollView.addView(content, new ScrollView.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
));
|
||||||
|
|
||||||
|
content.addView(createTopBar());
|
||||||
|
|
||||||
|
TextView title = textView("Settings", 34, COLOR_INK, Typeface.BOLD);
|
||||||
|
title.setIncludeFontPadding(false);
|
||||||
|
LinearLayout.LayoutParams titleParams = new LinearLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
);
|
||||||
|
titleParams.setMargins(0, dp(18), 0, dp(18));
|
||||||
|
content.addView(title, titleParams);
|
||||||
|
|
||||||
|
content.addView(createAutoUpdatesCard());
|
||||||
|
content.addView(createVersionCard());
|
||||||
|
|
||||||
|
checkButton = actionButton("Check now", COLOR_ACCENT, Color.WHITE);
|
||||||
|
checkButton.setOnClickListener(view -> checkForUpdate());
|
||||||
|
LinearLayout.LayoutParams checkParams = new LinearLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
dp(46)
|
||||||
|
);
|
||||||
|
checkParams.setMargins(0, dp(16), 0, 0);
|
||||||
|
content.addView(checkButton, checkParams);
|
||||||
|
|
||||||
|
downloadButton = actionButton("Download latest APK", COLOR_WARM, Color.WHITE);
|
||||||
|
downloadButton.setVisibility(View.GONE);
|
||||||
|
downloadButton.setOnClickListener(view -> {
|
||||||
|
String url = latestInfo == null ? UpdateManager.APK_URL : latestInfo.apkUrl;
|
||||||
|
UpdateManager.openDownload(this, url);
|
||||||
|
});
|
||||||
|
LinearLayout.LayoutParams downloadParams = new LinearLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
dp(46)
|
||||||
|
);
|
||||||
|
downloadParams.setMargins(0, dp(10), 0, 0);
|
||||||
|
content.addView(downloadButton, downloadParams);
|
||||||
|
|
||||||
|
root.addView(scrollView, new FrameLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
));
|
||||||
|
|
||||||
|
root.setOnApplyWindowInsetsListener((view, insets) -> {
|
||||||
|
content.setPadding(
|
||||||
|
dp(20),
|
||||||
|
dp(28) + insets.getSystemWindowInsetTop(),
|
||||||
|
dp(20),
|
||||||
|
dp(28) + insets.getSystemWindowInsetBottom()
|
||||||
|
);
|
||||||
|
return insets;
|
||||||
|
});
|
||||||
|
root.requestApplyInsets();
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View createTopBar() {
|
||||||
|
LinearLayout topBar = new LinearLayout(this);
|
||||||
|
topBar.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
topBar.setGravity(Gravity.CENTER_VERTICAL);
|
||||||
|
|
||||||
|
TextView label = textView("APP", 12, COLOR_ACCENT, Typeface.BOLD);
|
||||||
|
topBar.addView(label, new LinearLayout.LayoutParams(
|
||||||
|
0,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
1f
|
||||||
|
));
|
||||||
|
|
||||||
|
TextView back = textView("Extras", 15, COLOR_ACCENT, Typeface.BOLD);
|
||||||
|
back.setGravity(Gravity.CENTER);
|
||||||
|
back.setPadding(dp(14), 0, dp(14), 0);
|
||||||
|
back.setBackground(roundedRect(Color.WHITE, dp(8), Color.rgb(219, 226, 222), dp(1)));
|
||||||
|
back.setOnClickListener(view -> finish());
|
||||||
|
topBar.addView(back, new LinearLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
dp(40)
|
||||||
|
));
|
||||||
|
|
||||||
|
return topBar;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View createAutoUpdatesCard() {
|
||||||
|
LinearLayout card = createCard();
|
||||||
|
card.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
card.setGravity(Gravity.CENTER_VERTICAL);
|
||||||
|
|
||||||
|
LinearLayout copy = new LinearLayout(this);
|
||||||
|
copy.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
|
||||||
|
TextView title = textView("Auto updates", 20, COLOR_INK, Typeface.BOLD);
|
||||||
|
title.setIncludeFontPadding(false);
|
||||||
|
copy.addView(title);
|
||||||
|
|
||||||
|
TextView subtitle = textView("Check for new builds on startup", 14, COLOR_MUTED, Typeface.NORMAL);
|
||||||
|
LinearLayout.LayoutParams subtitleParams = new LinearLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
);
|
||||||
|
subtitleParams.setMargins(0, dp(6), 0, 0);
|
||||||
|
copy.addView(subtitle, subtitleParams);
|
||||||
|
|
||||||
|
card.addView(copy, new LinearLayout.LayoutParams(
|
||||||
|
0,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
1f
|
||||||
|
));
|
||||||
|
|
||||||
|
Switch autoSwitch = new Switch(this);
|
||||||
|
autoSwitch.setChecked(UpdateManager.isAutoUpdatesEnabled(this));
|
||||||
|
autoSwitch.setContentDescription("Auto updates");
|
||||||
|
autoSwitch.setOnCheckedChangeListener((button, checked) -> {
|
||||||
|
UpdateManager.setAutoUpdatesEnabled(this, checked);
|
||||||
|
statusText.setText(checked ? "Startup checks are on." : "Startup checks are off.");
|
||||||
|
});
|
||||||
|
card.addView(autoSwitch);
|
||||||
|
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View createVersionCard() {
|
||||||
|
LinearLayout card = createCard();
|
||||||
|
card.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
|
||||||
|
TextView title = textView("Version", 20, COLOR_INK, Typeface.BOLD);
|
||||||
|
title.setIncludeFontPadding(false);
|
||||||
|
card.addView(title);
|
||||||
|
|
||||||
|
installedVersionText = textView("", 15, COLOR_MUTED, Typeface.BOLD);
|
||||||
|
LinearLayout.LayoutParams installedParams = new LinearLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
);
|
||||||
|
installedParams.setMargins(0, dp(10), 0, 0);
|
||||||
|
card.addView(installedVersionText, installedParams);
|
||||||
|
|
||||||
|
statusText = textView("Ready to check for updates.", 15, COLOR_MUTED, Typeface.NORMAL);
|
||||||
|
statusText.setLineSpacing(dp(3), 1.1f);
|
||||||
|
LinearLayout.LayoutParams statusParams = new LinearLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
);
|
||||||
|
statusParams.setMargins(0, dp(8), 0, 0);
|
||||||
|
card.addView(statusText, statusParams);
|
||||||
|
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkForUpdate() {
|
||||||
|
if (executor == null || executor.isShutdown()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
latestInfo = null;
|
||||||
|
downloadButton.setVisibility(View.GONE);
|
||||||
|
checkButton.setEnabled(false);
|
||||||
|
checkButton.setAlpha(0.62f);
|
||||||
|
statusText.setText("Checking for updates...");
|
||||||
|
|
||||||
|
executor.submit(() -> {
|
||||||
|
try {
|
||||||
|
UpdateManager.UpdateInfo info = UpdateManager.fetchLatestUpdate(this);
|
||||||
|
runOnUiThread(() -> showUpdateResult(info));
|
||||||
|
} catch (Exception error) {
|
||||||
|
runOnUiThread(() -> showUpdateError(error));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showUpdateResult(UpdateManager.UpdateInfo info) {
|
||||||
|
latestInfo = info;
|
||||||
|
checkButton.setEnabled(true);
|
||||||
|
checkButton.setAlpha(1f);
|
||||||
|
|
||||||
|
if (info.updateAvailable) {
|
||||||
|
String version = info.latestVersionName.isEmpty()
|
||||||
|
? String.valueOf(info.latestVersionCode)
|
||||||
|
: info.latestVersionName + " (" + info.latestVersionCode + ")";
|
||||||
|
statusText.setText("Update available: " + version);
|
||||||
|
downloadButton.setVisibility(View.VISIBLE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
statusText.setText("You are on the latest build: "
|
||||||
|
+ info.currentVersionName + " (" + info.currentVersionCode + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showUpdateError(Exception error) {
|
||||||
|
checkButton.setEnabled(true);
|
||||||
|
checkButton.setAlpha(1f);
|
||||||
|
statusText.setText("Could not check for updates. " + cleanMessage(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateInstalledVersionText() {
|
||||||
|
try {
|
||||||
|
installedVersionText.setText("Installed: "
|
||||||
|
+ UpdateManager.getInstalledVersionName(this)
|
||||||
|
+ " (" + UpdateManager.getInstalledVersionCode(this) + ")");
|
||||||
|
} catch (Exception error) {
|
||||||
|
installedVersionText.setText("Installed version unavailable.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String cleanMessage(Exception error) {
|
||||||
|
String message = error.getMessage();
|
||||||
|
if (message == null || message.trim().isEmpty()) {
|
||||||
|
return "Unknown error.";
|
||||||
|
}
|
||||||
|
return message.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private LinearLayout createCard() {
|
||||||
|
LinearLayout card = new LinearLayout(this);
|
||||||
|
card.setPadding(dp(18), dp(16), dp(18), dp(16));
|
||||||
|
card.setBackground(roundedRect(COLOR_CARD, dp(8), Color.rgb(229, 232, 230), dp(1)));
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
card.setElevation(dp(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
);
|
||||||
|
params.setMargins(0, 0, 0, dp(14));
|
||||||
|
card.setLayoutParams(params);
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextView actionButton(String text, int backgroundColor, int textColor) {
|
||||||
|
TextView button = textView(text, 15, textColor, Typeface.BOLD);
|
||||||
|
button.setGravity(Gravity.CENTER);
|
||||||
|
button.setPadding(dp(14), 0, dp(14), 0);
|
||||||
|
button.setBackground(roundedRect(backgroundColor, dp(8), Color.TRANSPARENT, 0));
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextView textView(String text, int sizeSp, int color, int style) {
|
||||||
|
TextView textView = new TextView(this);
|
||||||
|
textView.setText(text);
|
||||||
|
textView.setTextSize(sizeSp);
|
||||||
|
textView.setTextColor(color);
|
||||||
|
textView.setTypeface(Typeface.DEFAULT, style);
|
||||||
|
return textView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GradientDrawable roundedRect(int color, int radius, int strokeColor, int strokeWidth) {
|
||||||
|
GradientDrawable drawable = new GradientDrawable();
|
||||||
|
drawable.setShape(GradientDrawable.RECTANGLE);
|
||||||
|
drawable.setColor(color);
|
||||||
|
drawable.setCornerRadius(radius);
|
||||||
|
if (strokeWidth > 0) {
|
||||||
|
drawable.setStroke(strokeWidth, strokeColor);
|
||||||
|
}
|
||||||
|
return drawable;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int dp(int value) {
|
||||||
|
return Math.round(value * getResources().getDisplayMetrics().density);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
package com.bibliadiaria.app;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
final class UpdateManager {
|
||||||
|
static final String PREFS_NAME = "preferencias_lectura";
|
||||||
|
static final String KEY_AUTO_UPDATES = "auto_updates_enabled";
|
||||||
|
static final String LATEST_JSON_URL =
|
||||||
|
"https://github.com/jahruz67/Bible-Daily/releases/download/latest/latest.json";
|
||||||
|
static final String APK_URL =
|
||||||
|
"https://github.com/jahruz67/Bible-Daily/releases/download/latest/app-debug.apk";
|
||||||
|
|
||||||
|
private UpdateManager() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isAutoUpdatesEnabled(Context context) {
|
||||||
|
return preferences(context).getBoolean(KEY_AUTO_UPDATES, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setAutoUpdatesEnabled(Context context, boolean enabled) {
|
||||||
|
preferences(context).edit().putBoolean(KEY_AUTO_UPDATES, enabled).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
static UpdateInfo fetchLatestUpdate(Context context) throws Exception {
|
||||||
|
String jsonText = download(LATEST_JSON_URL, "application/json");
|
||||||
|
JSONObject json = new JSONObject(jsonText);
|
||||||
|
|
||||||
|
int latestVersionCode = json.optInt("versionCode", 0);
|
||||||
|
if (latestVersionCode <= 0) {
|
||||||
|
throw new IOException("Update metadata did not include a valid versionCode.");
|
||||||
|
}
|
||||||
|
|
||||||
|
long currentVersionCode = getInstalledVersionCode(context);
|
||||||
|
String latestVersionName = json.optString("versionName", "");
|
||||||
|
String apkUrl = json.optString("apkUrl", APK_URL);
|
||||||
|
String builtAt = json.optString("builtAt", "");
|
||||||
|
String commit = json.optString("commit", "");
|
||||||
|
|
||||||
|
return new UpdateInfo(
|
||||||
|
currentVersionCode,
|
||||||
|
getInstalledVersionName(context),
|
||||||
|
latestVersionCode,
|
||||||
|
latestVersionName,
|
||||||
|
apkUrl.isEmpty() ? APK_URL : apkUrl,
|
||||||
|
builtAt,
|
||||||
|
commit,
|
||||||
|
latestVersionCode > currentVersionCode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static long getInstalledVersionCode(Context context) throws PackageManager.NameNotFoundException {
|
||||||
|
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
return info.getLongVersionCode();
|
||||||
|
}
|
||||||
|
return info.versionCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getInstalledVersionName(Context context) {
|
||||||
|
try {
|
||||||
|
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
||||||
|
if (info.versionName == null || info.versionName.trim().isEmpty()) {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
return info.versionName;
|
||||||
|
} catch (PackageManager.NameNotFoundException error) {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void showUpdateDialog(Activity activity, UpdateInfo info) {
|
||||||
|
if (activity.isFinishing()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String version = info.latestVersionName.isEmpty()
|
||||||
|
? String.valueOf(info.latestVersionCode)
|
||||||
|
: info.latestVersionName + " (" + info.latestVersionCode + ")";
|
||||||
|
|
||||||
|
new AlertDialog.Builder(activity)
|
||||||
|
.setTitle("Update available")
|
||||||
|
.setMessage("Version " + version + " is ready to download.")
|
||||||
|
.setPositiveButton("Download", (dialog, which) -> openDownload(activity, info.apkUrl))
|
||||||
|
.setNegativeButton("Later", null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void openDownload(Context context, String apkUrl) {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkUrl));
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SharedPreferences preferences(Context context) {
|
||||||
|
return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String download(String urlText, String acceptHeader) throws IOException {
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) new URL(urlText).openConnection();
|
||||||
|
connection.setConnectTimeout(12000);
|
||||||
|
connection.setReadTimeout(12000);
|
||||||
|
connection.setInstanceFollowRedirects(true);
|
||||||
|
connection.setRequestProperty("User-Agent", "BibliaDiaria/1.0 Android");
|
||||||
|
connection.setRequestProperty("Accept", acceptHeader);
|
||||||
|
|
||||||
|
int responseCode = connection.getResponseCode();
|
||||||
|
if (responseCode < 200 || responseCode >= 300) {
|
||||||
|
throw new IOException("Update server responded with code " + responseCode + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
try (InputStream stream = connection.getInputStream()) {
|
||||||
|
return readStream(stream);
|
||||||
|
} finally {
|
||||||
|
connection.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readStream(InputStream stream) throws IOException {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
try (BufferedReader reader = new BufferedReader(
|
||||||
|
new InputStreamReader(stream, StandardCharsets.UTF_8))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
builder.append(line).append('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class UpdateInfo {
|
||||||
|
final long currentVersionCode;
|
||||||
|
final String currentVersionName;
|
||||||
|
final int latestVersionCode;
|
||||||
|
final String latestVersionName;
|
||||||
|
final String apkUrl;
|
||||||
|
final String builtAt;
|
||||||
|
final String commit;
|
||||||
|
final boolean updateAvailable;
|
||||||
|
|
||||||
|
UpdateInfo(
|
||||||
|
long currentVersionCode,
|
||||||
|
String currentVersionName,
|
||||||
|
int latestVersionCode,
|
||||||
|
String latestVersionName,
|
||||||
|
String apkUrl,
|
||||||
|
String builtAt,
|
||||||
|
String commit,
|
||||||
|
boolean updateAvailable
|
||||||
|
) {
|
||||||
|
this.currentVersionCode = currentVersionCode;
|
||||||
|
this.currentVersionName = currentVersionName;
|
||||||
|
this.latestVersionCode = latestVersionCode;
|
||||||
|
this.latestVersionName = latestVersionName;
|
||||||
|
this.apkUrl = apkUrl;
|
||||||
|
this.builtAt = builtAt;
|
||||||
|
this.commit = commit;
|
||||||
|
this.updateAvailable = updateAvailable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user