Skip to content

ui: improve page interaction experience #5

ui: improve page interaction experience

ui: improve page interaction experience #5

Workflow file for this run

name: Build Release APKs
on:
workflow_dispatch:
inputs:
version_name:
description: "Override output file version name, for example 1.0.1"
required: false
type: string
send_to_telegram:
description: "Upload release APKs to Telegram"
required: true
default: true
type: boolean
push:
branches:
- "**"
tags:
- "v*"
permissions:
contents: write
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
release:
name: Build, Publish, And Notify
runs-on: ubuntu-latest
timeout-minutes: 60
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
TELEGRAM_MESSAGE_THREAD_ID: ${{ secrets.TELEGRAM_MESSAGE_THREAD_ID }}
TELEGRAM_MAX_DOCUMENT_BYTES: ${{ vars.TELEGRAM_MAX_DOCUMENT_BYTES }}
RELEASE_KEYSTORE_BASE64: ${{ secrets.RELEASE_KEYSTORE_BASE64 }}
RELEASE_KEYSTORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_PASSWORD }}
RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }}
RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }}
SEND_TO_TELEGRAM: ${{ github.event_name != 'workflow_dispatch' || github.event.inputs.send_to_telegram == 'true' }}
REQUESTED_VERSION_NAME: ${{ github.event.inputs.version_name }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Read ProjectConfig version
id: version
shell: bash
run: |
set -euo pipefail
version_name="$(sed -n 's/^[[:space:]]*const val VERSION_NAME = "\([^"]*\)".*/\1/p' buildSrc/src/main/kotlin/BuildConfig.kt | head -n 1)"
if [ -z "$version_name" ]; then
echo "::error::Unable to read ProjectConfig.VERSION_NAME from buildSrc/src/main/kotlin/BuildConfig.kt."
exit 1
fi
is_release=false
if [[ "$version_name" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
is_release=true
echo "Detected formal release version: $version_name"
else
echo "::notice::ProjectConfig.VERSION_NAME is '$version_name', not strict x.y.z. GitHub Release creation will be skipped."
fi
create_github_release=false
if [ "$is_release" = "true" ] && { [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ] || [ "$GITHUB_REF" = "refs/heads/main" ]; }; then
create_github_release=true
fi
echo "version_name=$version_name" >> "$GITHUB_OUTPUT"
echo "tag_name=v$version_name" >> "$GITHUB_OUTPUT"
echo "is_release=$is_release" >> "$GITHUB_OUTPUT"
echo "create_github_release=$create_github_release" >> "$GITHUB_OUTPUT"
- name: Make Gradle wrapper executable
run: chmod +x ./gradlew
- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "25"
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Set up Android SDK
uses: android-actions/setup-android@v3
- name: Install Android SDK packages
shell: bash
run: |
set -euo pipefail
set +o pipefail
yes | sdkmanager --licenses >/dev/null
licenses_status="${PIPESTATUS[1]}"
set -o pipefail
if [ "$licenses_status" -ne 0 ]; then
echo "::error::Failed to accept Android SDK licenses."
exit "$licenses_status"
fi
target_sdk="$(sed -n 's/^[[:space:]]*const val TARGET_SDK = \([0-9]\+\).*/\1/p' buildSrc/src/main/kotlin/BuildConfig.kt | head -n 1)"
if [ -z "$target_sdk" ]; then
echo "::error::Unable to read ProjectConfig.TARGET_SDK from buildSrc/src/main/kotlin/BuildConfig.kt."
exit 1
fi
available_packages="$(sdkmanager --list | awk '{ print $1 }')"
platform_package="platforms;android-${target_sdk}"
if ! grep -Fxq "$platform_package" <<< "$available_packages"; then
if grep -Fxq "platforms;android-${target_sdk}.0" <<< "$available_packages"; then
platform_package="platforms;android-${target_sdk}.0"
else
echo "::error::No Android SDK platform package is available for TARGET_SDK=$target_sdk."
exit 1
fi
fi
build_tools_package="$(printf '%s\n' "$available_packages" | awk '$1 ~ /^build-tools;[0-9]/ { print $1 }' | sort -V | tail -n 1)"
ndk_package="$(printf '%s\n' "$available_packages" | awk '$1 ~ /^ndk;[0-9]/ { print $1 }' | sort -V | tail -n 1)"
if [ -z "$build_tools_package" ]; then
echo "::error::No Android build-tools package is available from sdkmanager."
exit 1
fi
if [ -z "$ndk_package" ]; then
echo "::error::No Android NDK package is available from sdkmanager."
exit 1
fi
sdkmanager --install "$platform_package" "$build_tools_package" "$ndk_package"
ndk_version="${ndk_package#ndk;}"
echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/$ndk_version" >> "$GITHUB_ENV"
echo "ANDROID_NDK_ROOT=$ANDROID_HOME/ndk/$ndk_version" >> "$GITHUB_ENV"
- name: Validate signing secrets
shell: bash
run: |
set -euo pipefail
if [ -z "${RELEASE_KEYSTORE_BASE64:-}" ]; then
echo "::error::Missing repository secret RELEASE_KEYSTORE_BASE64."
exit 1
fi
if [ -z "${RELEASE_KEYSTORE_PASSWORD:-}" ]; then
echo "::error::Missing repository secret RELEASE_KEYSTORE_PASSWORD."
exit 1
fi
if [ -z "${RELEASE_KEY_ALIAS:-}" ]; then
echo "::error::Missing repository secret RELEASE_KEY_ALIAS."
exit 1
fi
- name: Build release APKs
run: ./gradlew --no-daemon :app:assembleRelease
- name: Sign release APKs
shell: bash
run: |
set -euo pipefail
keystore_path="$RUNNER_TEMP/release.keystore"
printf '%s' "$RELEASE_KEYSTORE_BASE64" | base64 --decode > "$keystore_path"
key_password="${RELEASE_KEY_PASSWORD:-$RELEASE_KEYSTORE_PASSWORD}"
export RELEASE_KEYSTORE_PASSWORD
export RELEASE_KEY_PASSWORD="$key_password"
zipalign="$(find "$ANDROID_HOME/build-tools" -type f -name zipalign | sort -V | tail -n 1)"
apksigner="$(find "$ANDROID_HOME/build-tools" -type f -name apksigner | sort -V | tail -n 1)"
if [ -z "$zipalign" ]; then
echo "::error::zipalign was not found in Android build-tools."
exit 1
fi
if [ -z "$apksigner" ]; then
echo "::error::apksigner was not found in Android build-tools."
exit 1
fi
mkdir -p signed-apks
shopt -s nullglob
for apk in app/build/outputs/apk/release/*.apk; do
base_name="$(basename "$apk" .apk)"
aligned_apk="$RUNNER_TEMP/${base_name}-aligned.apk"
signed_apk="signed-apks/${base_name}-signed.apk"
"$zipalign" -p -f 4 "$apk" "$aligned_apk"
"$apksigner" sign \
--ks "$keystore_path" \
--ks-pass env:RELEASE_KEYSTORE_PASSWORD \
--ks-key-alias "$RELEASE_KEY_ALIAS" \
--key-pass env:RELEASE_KEY_PASSWORD \
--out "$signed_apk" \
"$aligned_apk"
"$apksigner" verify --verbose "$signed_apk"
done
if ! find signed-apks -type f -name "*.apk" | grep -q .; then
echo "::error::No signed release APKs were produced."
exit 1
fi
- name: Collect release APKs
id: collect
shell: bash
env:
CONFIG_VERSION_NAME: ${{ steps.version.outputs.version_name }}
CREATE_GITHUB_RELEASE: ${{ steps.version.outputs.create_github_release }}
run: |
set -euo pipefail
ref_version_name="${GITHUB_REF_NAME#v}"
version_name="${REQUESTED_VERSION_NAME:-}"
if [ -z "$version_name" ] && [ "${GITHUB_REF_TYPE:-}" = "tag" ] && [ "$ref_version_name" != "$GITHUB_REF_NAME" ]; then
version_name="$ref_version_name"
fi
if [ -z "$version_name" ]; then
version_name="$CONFIG_VERSION_NAME"
fi
version_code="$(git rev-list --count HEAD)"
mkdir -p release-artifacts
shopt -s nullglob
for apk in signed-apks/*.apk; do
file_name="$(basename "$apk")"
abi="universal"
case "$file_name" in
*arm64-v8a*) abi="arm64-v8a" ;;
*armeabi-v7a*) abi="armeabi-v7a" ;;
*x86_64*) abi="x86_64" ;;
*x86*) abi="x86" ;;
*universal*) abi="universal" ;;
esac
cp "$apk" "release-artifacts/AsteriskNG_${version_name}-${version_code}-${abi}.apk"
done
if ! find release-artifacts -type f -name "*.apk" | grep -q .; then
echo "::error::No release APKs were found under signed-apks."
exit 1
fi
echo "version_name=$version_name" >> "$GITHUB_OUTPUT"
echo "version_code=$version_code" >> "$GITHUB_OUTPUT"
ls -lh release-artifacts
- name: Upload GitHub artifact
uses: actions/upload-artifact@v4
with:
name: AsteriskNG_${{ steps.collect.outputs.version_name }}-${{ steps.collect.outputs.version_code }}
path: release-artifacts/*.apk
if-no-files-found: error
- name: Create tag and release notes
if: steps.version.outputs.create_github_release == 'true'
id: release_info
shell: bash
env:
TAG_NAME: ${{ steps.version.outputs.tag_name }}
VERSION_NAME: ${{ steps.version.outputs.version_name }}
VERSION_CODE: ${{ steps.collect.outputs.version_code }}
run: |
set -euo pipefail
current_sha="$(git rev-parse HEAD)"
git fetch --tags --force
if git rev-parse -q --verify "refs/tags/$TAG_NAME" >/dev/null; then
tag_sha="$(git rev-list -n 1 "$TAG_NAME")"
if [ "$tag_sha" != "$current_sha" ]; then
echo "::error::Tag $TAG_NAME already exists at $tag_sha, but this workflow is running at $current_sha."
exit 1
fi
echo "Tag $TAG_NAME already exists on the current commit."
else
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "$TAG_NAME" -m "Release $TAG_NAME" "$current_sha"
git push origin "refs/tags/$TAG_NAME"
fi
mapfile -t release_tags < <(
git for-each-ref --format='%(refname:short)' refs/tags |
grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' || true
)
previous_candidates="$(mktemp)"
for candidate in "${release_tags[@]}"; do
if [ "$candidate" = "$TAG_NAME" ]; then
continue
fi
if git merge-base --is-ancestor "$candidate" "$current_sha"; then
count="$(git rev-list --count "${candidate}..${current_sha}")"
printf '%s\t%s\n' "$count" "$candidate" >> "$previous_candidates"
fi
done
previous_tag=""
if [ -s "$previous_candidates" ]; then
previous_tag="$(sort -n "$previous_candidates" | head -n 1 | cut -f2-)"
fi
range="$current_sha"
if [ -n "$previous_tag" ]; then
range="${previous_tag}..${current_sha}"
fi
tmp_changes="$(mktemp)"
git log --reverse --no-merges --pretty=format:"- %s ([%h](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/%H))" "$range" > "$tmp_changes"
if [ ! -s "$tmp_changes" ]; then
git log --reverse --pretty=format:"- %s ([%h](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/%H))" "$range" > "$tmp_changes"
fi
{
echo "## AsteriskNG $TAG_NAME"
echo
echo "- Version name: $VERSION_NAME"
echo "- Version code: $VERSION_CODE"
echo
if [ -n "$previous_tag" ]; then
echo "### Changes since $previous_tag"
echo
echo "[Compare $previous_tag...$TAG_NAME](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/compare/${previous_tag}...${TAG_NAME})"
else
echo "### Changes"
fi
echo
if [ -s "$tmp_changes" ]; then
cat "$tmp_changes"
echo
else
echo "- No commit changes found."
fi
echo
echo "### APK artifacts"
echo
find release-artifacts -maxdepth 1 -type f -name "*.apk" -printf "- %f\n" | sort
} > release-notes.md
echo "previous_tag=$previous_tag" >> "$GITHUB_OUTPUT"
cat release-notes.md
- name: Create GitHub Release
if: steps.version.outputs.create_github_release == 'true'
shell: bash
env:
GH_TOKEN: ${{ github.token }}
TAG_NAME: ${{ steps.version.outputs.tag_name }}
run: |
set -euo pipefail
if gh release view "$TAG_NAME" >/dev/null 2>&1; then
gh release upload "$TAG_NAME" release-artifacts/*.apk --clobber
gh release edit "$TAG_NAME" \
--title "AsteriskNG $TAG_NAME" \
--notes-file release-notes.md \
--latest
else
gh release create "$TAG_NAME" release-artifacts/*.apk \
--title "AsteriskNG $TAG_NAME" \
--notes-file release-notes.md \
--latest
fi
- name: Validate Telegram secrets
if: env.SEND_TO_TELEGRAM == 'true'
shell: bash
run: |
set -euo pipefail
if [ -z "${TELEGRAM_BOT_TOKEN:-}" ]; then
echo "::error::Missing repository secret TELEGRAM_BOT_TOKEN."
exit 1
fi
if [ -z "${TELEGRAM_CHAT_ID:-}" ]; then
echo "::error::Missing repository secret TELEGRAM_CHAT_ID."
exit 1
fi
- name: Upload release APKs to Telegram
if: env.SEND_TO_TELEGRAM == 'true'
shell: bash
run: |
set -euo pipefail
mapfile -d '' apks < <(find release-artifacts -maxdepth 1 -type f -name '*.apk' -print0 | sort -z)
if [ "${#apks[@]}" -eq 0 ]; then
echo "::error::No APK files were found in release-artifacts before Telegram upload."
find release-artifacts -maxdepth 1 -type f -print || true
exit 1
fi
ls -lh release-artifacts
max_document_bytes="${TELEGRAM_MAX_DOCUMENT_BYTES:-52428800}"
commit_id="$(git rev-parse HEAD)"
commit_short_id="$(git rev-parse --short=12 HEAD)"
commit_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/commit/$commit_id"
workflow_run_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
commit_message="$(git log -1 --pretty=%B)"
commit_message="${commit_message//$'\r'/}"
if [ -z "${commit_message//[[:space:]]/}" ]; then
commit_message="(empty commit message)"
fi
if [ "${#commit_message}" -gt 650 ]; then
commit_message="${commit_message:0:650}"$'\n...'
fi
commit_message_html="$(printf '%s' "$commit_message" | python3 -c 'import html, sys; print(html.escape(sys.stdin.read(), quote=False), end="")')"
for apk in "${apks[@]}"; do
apk_path="$(realpath "$apk")"
apk_name="$(basename "$apk")"
if [ ! -r "$apk_path" ]; then
echo "::error::APK is not readable: $apk_path"
exit 1
fi
apk_size="$(stat -c '%s' "$apk_path")"
if [ "$apk_size" -gt "$max_document_bytes" ]; then
echo "::warning::Skipping Telegram upload for $apk_name because it is $apk_size bytes, above the $max_document_bytes byte limit."
continue
fi
echo "Uploading $apk_name to Telegram"
caption="$(printf 'AsteriskNG\n\n%s\n\n%s\n<a href="%s">Commit</a> | <a href="%s">Workflow run</a>' \
"$commit_message_html" \
"$commit_short_id" \
"$commit_url" \
"$workflow_run_url")"
args=(
--fail-with-body
-sS
-X POST
"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument"
--form-string "chat_id=${TELEGRAM_CHAT_ID}"
--form "document=@${apk_path};filename=${apk_name}"
--form-string "caption=${caption}"
--form-string "parse_mode=HTML"
)
if [ -n "${TELEGRAM_MESSAGE_THREAD_ID:-}" ]; then
args+=(--form-string "message_thread_id=${TELEGRAM_MESSAGE_THREAD_ID}")
fi
curl "${args[@]}"
done