#!/usr/bin/env bash set -euo pipefail ROOT="$(cd "$(dirname "$0")" && pwd)" AAB_DIR="$ROOT/build/app/outputs/bundle/release" FLUTTER="/opt/flutter/bin/flutter" JAVA_HOME="/root/.sdkman/candidates/java/21.0.11-amzn" # ── colors ── R=$'\033[0;31m' G=$'\033[0;32m' Y=$'\033[1;33m' C=$'\033[0;36m' B=$'\033[1m' N=$'\033[0m' bar="━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" blen=${#bar} # ── helpers ── CURSOR_ON='\033[?25h' CURSOR_OFF='\033[?25l' cleanup() { printf "$CURSOR_ON" >/dev/tty; exit; } trap cleanup EXIT INT TERM # ── arrow selector ── select_arrow() { local opts=("$@") local sel=0 key i n=${#opts[@]} local tty=/dev/tty for i in "${!opts[@]}"; do if [ "$i" -eq "$sel" ]; then printf " ${C}❯ ${opts[$i]}${N}\n" >"$tty" else printf " ${opts[$i]}\n" >"$tty" fi done printf '\033[%dA' "$n" >"$tty" printf "$CURSOR_OFF" >"$tty" while true; do IFS= read -rsn1 key <"$tty" if [[ $key == $'\x1b' ]]; then read -rsn2 -t 0.01 key <"$tty" || key='' case "$key" in '[A') (( sel > 0 )) && (( sel-- )) ;; '[B') (( sel < n - 1 )) && (( sel++ )) ;; esac elif [[ -z "$key" || "$key" == $'\n' || "$key" == $'\r' ]]; then printf '\033[%dA\033[J' "$n" >"$tty" printf "$CURSOR_ON" >"$tty" printf '%s\n' "${opts[$sel]}" return 0 fi for i in "${!opts[@]}"; do if [ "$i" -eq "$sel" ]; then printf "\033[2K\r ${C}❯ ${opts[$i]}${N}\n" >"$tty" else printf "\033[2K\r ${opts[$i]}\n" >"$tty" fi done printf '\033[%dA' "$n" >"$tty" done } # ── progress bar ── progress() { local pct=$1 msg=$2 local filled=$(( pct * blen / 100 )) local empty=$(( blen - filled )) printf "\r${C}[${bar:0:filled}${N}${bar:0:empty}${C}]${N} %3d%% %s\033[K" "$pct" "$msg" } # ── ── ── ── ── ── ── ── ── clear cat </dev/null; then echo -e "${R}✗ Not a Flutter project root.${N}" exit 1 fi NAME=$(grep "^name:" "$ROOT/pubspec.yaml" | head -1 | awk '{print $2}' | tr -d '"') VERSION=$(grep "^version:" "$ROOT/pubspec.yaml" | head -1 | awk '{print $2}') printf " Project ${C}%s${N}\n" "$NAME" printf " Version ${C}%s${N}\n" "$VERSION" printf " Output ${C}%s${N}\n" "$AAB_DIR" echo # ── confirm with arrows ── echo -e " ${Y}Build release for Play Store?${N}" CHOICE=$(select_arrow "Yes" "No") if [ "$CHOICE" != "Yes" ]; then echo -e "${Y}Canceled.${N}" exit 0 fi export JAVA_HOME="$JAVA_HOME" # ── clean ── progress 3 "Cleaning..." "$FLUTTER" clean > /dev/null 2>&1 || true progress 6 "Cleaning... done" # ── pub get ── progress 8 "Dependencies..." "$FLUTTER" pub get > /dev/null 2>&1 progress 12 "Dependencies... done" # ── build AAB ── progress 12 "Building release AAB..." BUILD_OUTPUT=$("$FLUTTER" build appbundle --release 2>&1) || { echo -e "\n${R}✗ Build failed:${N}" echo "$BUILD_OUTPUT" exit 1 } progress 75 "AAB built successfully" # ── native debug symbols ── SYMBOLS_DIR="$ROOT/build/app/intermediates/merged_native_libs/release/mergeReleaseNativeLibs/out/lib" progress 78 "Packaging symbols..." if [ -d "$SYMBOLS_DIR" ]; then cd "$SYMBOLS_DIR" rm -f "$AAB_DIR/native-debug-symbols.zip" zip -r "$AAB_DIR/native-debug-symbols.zip" . > /dev/null 2>&1 cd "$ROOT" progress 95 "Symbols packaged" else echo -e "\n${Y}⚠ No native libs found${N}" fi # ── verify ── progress 98 "Verifying..." sleep 0.2 progress 100 "Complete!" echo # ── results ── AAB_FILE="$AAB_DIR/app-release.aab" SYM_FILE="$AAB_DIR/native-debug-symbols.zip" size_aab=$(du -sh "$AAB_FILE" 2>/dev/null | cut -f1) size_sym=$(du -sh "$SYM_FILE" 2>/dev/null | cut -f1) cat </dev/null \ && echo -e " ${G}✓ AAB copied${N}" \ || echo -e " ${R}✗ Failed to copy AAB${N}" if [ -f "$SYM_FILE" ]; then cp "$SYM_FILE" "$DEST/" 2>/dev/null \ && echo -e " ${G}✓ Symbols copied${N}" \ || echo -e " ${R}✗ Failed to copy symbols${N}" fi else echo -e " ${R}✗ Directory not found: $DEST${N}" fi fi echo echo -e "${B}═══════════════════════════════════════${N}" echo -e "${G}Ready to upload to Play Console!${N}" echo -e "${B}═══════════════════════════════════════${N}"