#!/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 ── cleanup() { tput cnorm; exit; } trap cleanup EXIT INT TERM cursor_off() { tput civis; } cursor_on() { tput cnorm; } # ── arrow selector ── # usage: select_arrow "opt1" "opt2" ... → prints chosen, returns 0 select_arrow() { local opts=("$@") local sel=0 local row row=$(tput lines) row=$(( row - ${#opts[@]} - 1 )) local col=4 local key cursor_off while true; do for i in "${!opts[@]}"; do tput cup "$(( row + i ))" "$col" if [ "$i" -eq "$sel" ]; then printf "${C}❯ ${opts[$i]}${N} \033[K" else printf " ${opts[$i]} \033[K" fi done IFS= read -rsn1 key if [[ $key == $'\x1b' ]]; then read -rsn2 key case "$key" in '[A') (( sel > 0 )) && (( sel-- )) ;; '[B') (( sel < ${#opts[@]} - 1 )) && (( sel++ )) ;; esac elif [[ $key == "" ]]; then # clear selection lines for i in "${!opts[@]}"; do tput cup "$(( row + i ))" "$col" printf " \033[K" done cursor_on echo "${opts[$sel]}" return 0 fi done } # ── progress bar (fixed position) ── PROGRESS_ROW=0 set_progress_row() { PROGRESS_ROW=$(tput lines); } progress() { local pct=$1 msg=$2 local filled=$(( pct * blen / 100 )) local empty=$(( blen - filled )) local offset=$(( PROGRESS_ROW - 2 )) tput cup "$offset" 0 printf "${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 set_progress_row 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 tput cup "$(( PROGRESS_ROW - 1 ))" 0 echo -e "${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 tput cup "$(( PROGRESS_ROW - 1 ))" 0 echo -e "${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}"