From 96a9816e42661995a00de5d66a633d93da7fdd19 Mon Sep 17 00:00:00 2001 From: Aran Roig Date: Wed, 22 Apr 2026 00:39:01 +0200 Subject: [PATCH] CI/CD Works --- .gitea/workflows/build-apk.yml | 52 ++++++++++++++++ apk/app.json | 23 ++++++- apk/build.sh | 59 ++++++++++++++++++ apk/components/birthdate-list.tsx | 41 ++++++++---- apk/package-lock.json | 100 +++++++++++++++++++----------- apk/package.json | 5 +- 6 files changed, 229 insertions(+), 51 deletions(-) create mode 100644 .gitea/workflows/build-apk.yml create mode 100755 apk/build.sh diff --git a/.gitea/workflows/build-apk.yml b/.gitea/workflows/build-apk.yml new file mode 100644 index 0000000..47cbf49 --- /dev/null +++ b/.gitea/workflows/build-apk.yml @@ -0,0 +1,52 @@ +name: Build APK + +on: + push: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + shell: bash + working-directory: ./apk + + steps: + - name: 📥 Checkout + uses: actions/checkout@v4 + + - name: 🟢 Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: 📦 Install dependencies + run: npm install + + - name: ☕ Setup Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + + - name: 📱 Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: ⚙️ Expo prebuild + run: npx expo prebuild --clean --non-interactive + + - name: 🏗️ Build APK (Release) + working-directory: ./apk/android + run: ./gradlew assembleRelease --stacktrace --no-daemon + + - name: Create Release + uses: https://gitea.com/actions/gitea-release-action@v1 + with: + tag_name: latest + name: Latest Build + overwrite_files: true + files: | + - ./apk/android/app/build/outputs/apk/release/app-release.apk + env: + GITEA_TOKEN: ${{ secrets.GITEA }} \ No newline at end of file diff --git a/apk/app.json b/apk/app.json index 1a38be9..9d4353a 100644 --- a/apk/app.json +++ b/apk/app.json @@ -9,7 +9,8 @@ "userInterfaceStyle": "automatic", "newArchEnabled": true, "ios": { - "supportsTablet": true + "supportsTablet": true, + "bundleIdentifier": "com.arandano69.Birthday" }, "android": { "permissions": [ @@ -22,7 +23,8 @@ "monochromeImage": "./assets/images/android-icon-monochrome.png" }, "edgeToEdgeEnabled": true, - "predictiveBackGestureEnabled": false + "predictiveBackGestureEnabled": false, + "package": "com.arandano69.Birthday" }, "web": { "output": "static", @@ -42,11 +44,28 @@ "backgroundColor": "#000000" } } + ], + [ + "expo-build-properties", + { + "android": { + "gradleProperties": { + "org.gradle.jvmargs": "-Xmx2g -XX:MaxMetaspaceSize=512m", + "org.gradle.parallel": "false" + } + } + } ] ], "experiments": { "typedRoutes": true, "reactCompiler": true + }, + "extra": { + "router": {}, + "eas": { + "projectId": "f761fcbd-46f2-4387-8282-005e44223075" + } } } } diff --git a/apk/build.sh b/apk/build.sh new file mode 100755 index 0000000..dd2bdc5 --- /dev/null +++ b/apk/build.sh @@ -0,0 +1,59 @@ +#!/bin/bash +export ANDROID_HOME=$HOME/android +export ANDROID_SDK_ROOT=$ANDROID_HOME +export PATH=$ANDROID_HOME/emulator:$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools:$ANDROID_HOME/tools/bin:$PATH + +set -e # stop on error + +echo "🚧 Starting APK build..." + +# Step 1: Ensure native android folder exists +if [ ! -d "android" ]; then + echo "📦 Running Expo prebuild..." + npx expo prebuild +fi + +# Step 2: Go to android folder +cd android + +# Step 3: Check if keystore exists +KEYSTORE_FILE="app/my-release-key.keystore" + +if [ ! -f "$KEYSTORE_FILE" ]; then + echo "🔑 Generating keystore..." + + keytool -genkeypair -v \ + -storetype PKCS12 \ + -keystore $KEYSTORE_FILE \ + -alias my-key-alias \ + -keyalg RSA \ + -keysize 2048 \ + -validity 10000 \ + -storepass password \ + -keypass password \ + -dname "CN=Your Name, OU=Dev, O=MyApp, L=City, S=State, C=US" +fi + +# Step 4: Ensure gradle.properties has signing config +GRADLE_PROPS="gradle.properties" + +if ! grep -q "MYAPP_UPLOAD_STORE_FILE" $GRADLE_PROPS; then + echo "⚙️ Adding signing config..." + + cat <> $GRADLE_PROPS +MYAPP_UPLOAD_STORE_FILE=my-release-key.keystore +MYAPP_UPLOAD_KEY_ALIAS=my-key-alias +MYAPP_UPLOAD_STORE_PASSWORD=password +MYAPP_UPLOAD_KEY_PASSWORD=password +EOF +fi + +# Step 5: Build release APK +echo "🏗️ Building APK..." +./gradlew assembleRelease + +# Step 6: Output path +APK_PATH="app/build/outputs/apk/release/app-release.apk" + +echo "✅ Build complete!" +echo "📦 APK located at: android/$APK_PATH" \ No newline at end of file diff --git a/apk/components/birthdate-list.tsx b/apk/components/birthdate-list.tsx index 6e31158..20311cc 100644 --- a/apk/components/birthdate-list.tsx +++ b/apk/components/birthdate-list.tsx @@ -52,34 +52,44 @@ export function BirthdayList() { // Helper function to group and sort function groupBirthdaysByDate(data: BirthdayEntry[]): SectionData[] { - // Sort by date const today = new Date(); const currentYear = today.getFullYear(); + const todayAtMidnight = new Date(today); + todayAtMidnight.setHours(0, 0, 0, 0); const daysUntilNext = (month: number, day: number) => { let target = new Date(currentYear, month, day); - if(target < today.setHours(0, 0, 0, 0)){ + if (target < todayAtMidnight) { target = new Date(currentYear + 1, month, day); } const msPerDay = 1000 * 60 * 60 * 24; - const diff = target - new Date(today.setHours(0,0,0,0)); + const diff = target.getTime() - todayAtMidnight.getTime(); return Math.ceil(diff / msPerDay); }; const sorted = [...data].sort((a, b) => { - const dateA = parseBirthdayDate(a); - const dateB = parseBirthdayDate(b); - return daysUntilNext(dateA.getMonth(), dateA.getDate()) - daysUntilNext(dateB.getMonth(), dateB.getDate()); + const dateA = parseBirthdayDate(a.date); + const dateB = parseBirthdayDate(b.date); + + const nextBirthdayDiff = + daysUntilNext(dateA.getMonth(), dateA.getDate()) - + daysUntilNext(dateB.getMonth(), dateB.getDate()); + + if (nextBirthdayDiff !== 0) { + return nextBirthdayDiff; + } + + return dateA.getFullYear() - dateB.getFullYear(); }); - // Group by date const grouped = sorted.reduce((acc, item) => { - const existing = acc.find((section) => section.title === item.date); + const sectionKey = getMonthDayKey(item.date); + const existing = acc.find((section) => section.title === sectionKey); if (existing) { existing.data.push(item); } else { - acc.push({ title: item.date, data: [item] }); + acc.push({ title: sectionKey, data: [item] }); } return acc; @@ -92,11 +102,20 @@ function parseBirthdayDate(date: string) { return new Date(`${date}T00:00:00`); } +function getMonthDayKey(date: string) { + const parsedDate = parseBirthdayDate(date); + const month = `${parsedDate.getMonth() + 1}`.padStart(2, "0"); + const day = `${parsedDate.getDate()}`.padStart(2, "0"); + + return `${month}-${day}`; +} + function formatDisplayDate(date: string) { - return parseBirthdayDate(date).toLocaleDateString(undefined, { + const [month, day] = date.split("-").map(Number); + + return new Date(2000, month - 1, day).toLocaleDateString(undefined, { month: "long", day: "numeric", - year: "numeric", }); } diff --git a/apk/package-lock.json b/apk/package-lock.json index 920f4b4..f78e2de 100644 --- a/apk/package-lock.json +++ b/apk/package-lock.json @@ -15,6 +15,7 @@ "@react-navigation/elements": "^2.6.3", "@react-navigation/native": "^7.1.8", "expo": "~54.0.33", + "expo-build-properties": "~1.0.10", "expo-constants": "~18.0.13", "expo-font": "~14.0.11", "expo-haptics": "~15.0.8", @@ -3765,9 +3766,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3782,9 +3780,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3799,9 +3794,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3816,9 +3808,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3833,9 +3822,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3850,9 +3836,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3867,9 +3850,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3884,9 +3864,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -6166,6 +6143,53 @@ "react-native": "*" } }, + "node_modules/expo-build-properties": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/expo-build-properties/-/expo-build-properties-1.0.10.tgz", + "integrity": "sha512-mFCZbrbrv0AP5RB151tAoRzwRJelqM7bCJzCkxpu+owOyH+p/rFC/q7H5q8B9EpVWj8etaIuszR+gKwohpmu1Q==", + "license": "MIT", + "dependencies": { + "ajv": "^8.11.0", + "semver": "^7.6.0" + }, + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-build-properties/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/expo-build-properties/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/expo-build-properties/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/expo-constants": { "version": "18.0.13", "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.13.tgz", @@ -6850,6 +6874,22 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -8680,9 +8720,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -8703,9 +8740,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -8726,9 +8760,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -8749,9 +8780,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ diff --git a/apk/package.json b/apk/package.json index 1281795..33d1c9c 100644 --- a/apk/package.json +++ b/apk/package.json @@ -5,8 +5,8 @@ "scripts": { "start": "expo start", "reset-project": "node ./scripts/reset-project.js", - "android": "expo start --android", - "ios": "expo start --ios", + "android": "expo run:android", + "ios": "expo run:ios", "web": "expo start --web", "lint": "expo lint" }, @@ -18,6 +18,7 @@ "@react-navigation/elements": "^2.6.3", "@react-navigation/native": "^7.1.8", "expo": "~54.0.33", + "expo-build-properties": "~1.0.10", "expo-constants": "~18.0.13", "expo-font": "~14.0.11", "expo-haptics": "~15.0.8",