FROM ubuntu:22.04 ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y \ openjdk-21-jdk \ wget \ unzip \ curl \ python3 \ git \ && rm -rf /var/lib/apt/lists/* # Install Node.js 22 RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ apt-get install -y nodejs && \ rm -rf /var/lib/apt/lists/* # Setup Android SDK ENV ANDROID_SDK_ROOT=/opt/android-sdk ENV ANDROID_HOME=/opt/android-sdk ENV PATH=$PATH:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/platform-tools RUN mkdir -p $ANDROID_SDK_ROOT/cmdline-tools RUN cd /tmp && \ wget -q "https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip" -O cmdline-tools.zip && \ unzip -q cmdline-tools.zip && \ mv cmdline-tools $ANDROID_SDK_ROOT/cmdline-tools/latest && \ rm cmdline-tools.zip RUN yes | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager --licenses 2>/dev/null || true RUN $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager \ "platform-tools" "platforms;android-34" "build-tools;34.0.0" WORKDIR /workspace COPY package*.json ./ RUN npm install COPY . . RUN npm run build && npx cap add android && npx cap copy android && npx cap sync android # Generate and copy custom icons to Android res/ AFTER cap sync RUN node -e " const sharp = require('sharp'); const fs = require('fs'); const path = require('path'); async function gen() { const src = 'public/icons/icon.png'; // sizes for each mipmap density const densities = [ { suffix: 'mdpi', size: 48 }, { suffix: 'hdpi', size: 72 }, { suffix: 'xhdpi', size: 96 }, { suffix: 'xxhdpi', size: 144 }, { suffix: 'xxxhdpi', size: 192 } ]; const base = '/workspace/android/app/src/main/res'; // Generate regular icons (with white background) - these are used for API < 26 for (const d of densities) { const dir = base + '/mipmap-' + d.suffix + '-v4'; fs.mkdirSync(dir, { recursive: true }); // ic_launcher and ic_launcher_round: full logo with white bg const icon = await sharp(src) .resize(d.size, d.size, { fit: 'contain', background: '#ffffff' }) .png() .toBuffer(); fs.writeFileSync(dir + '/ic_launcher.png', icon); fs.writeFileSync(dir + '/ic_launcher_round.png', icon); // foreground for adaptive icon (transparent bg, scaled 4x) const fgSize = d.size * 4; const logo = await sharp(src) .resize(fgSize, fgSize, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } }) .png() .toBuffer(); fs.writeFileSync(dir + '/ic_launcher_foreground.png', logo); } // Create mipmap-anydpi-v26 (adaptive icon config) const v26Dir = base + '/mipmap-anydpi-v26'; fs.mkdirSync(v26Dir, { recursive: true }); const adXml = \` \`; fs.writeFileSync(v26Dir + '/ic_launcher.xml', adXml); fs.writeFileSync(v26Dir + '/ic_launcher_round.xml', adXml); // Color for background const valuesDir = base + '/values'; fs.mkdirSync(valuesDir, { recursive: true }); fs.writeFileSync(valuesDir + '/colors.xml', '#FFFFFF'); // Create drawable for foreground on API < 26 (use same as mipmap) const drawDir = base + '/drawable'; fs.mkdirSync(drawDir, { recursive: true }); const fgDraw = await sharp(src) .resize(48, 48, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } }) .png() .toBuffer(); fs.writeFileSync(drawDir + '/ic_launcher_foreground.png', fgDraw); console.log('Icons copied successfully!'); } gen().catch(e => { console.error(e); process.exit(1); }); " # Patch version to 1.0.1 RUN sed -i 's/versionName "1.0"/versionName "1.0.1"/' /workspace/android/app/build.gradle WORKDIR /workspace/android RUN chmod +x gradlew RUN ./gradlew assembleDebug --no-daemon