Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
3920728
Integrate SKIE
mdrlzy Jan 24, 2026
b1b32ca
Implement iOS native bindings and fix build configuration
Jan 26, 2026
1ba0789
Adressed some bugs
Feb 2, 2026
2b98626
Removed unncecessary md file
Feb 2, 2026
f72b9be
Add Fastlane CI/CD for iOS builds
Feb 17, 2026
b209e6d
Fix CI: remove macOS-specific org.gradle.java.home from gradle.proper…
Feb 17, 2026
81d157d
Added fastlane build log
Feb 17, 2026
efbc14e
Fix iOS CI build errors
Feb 18, 2026
d655254
Fix copyFrameworkForXcode for configuration-cache compatibility
Feb 18, 2026
2ddc6c1
Use dev.ark-builders.drop bundle ID, add XCFramework Info.plist fallback
Feb 18, 2026
b3bc180
Use cp -R for XCFramework copy to preserve full directory structure
Feb 18, 2026
466d63f
Set iOS marketing version to 1.1 for App Store upload
Feb 20, 2026
b26d388
Add iOS app icons to fix TestFlight upload validation
Feb 20, 2026
74d83d0
Fix app icon: replace with non-transparent icon for App Store validat…
Feb 20, 2026
d4bda20
Use macos-26 runner and Xcode 26.2 for iOS release
Feb 20, 2026
be95e65
Fix OpenSSL 3: add -legacy flag for Apple p12 (RC2-40-CBC) on macos-26
Feb 20, 2026
d02d27d
fix: use latest ArkDrop Swift bindings
May 20, 2026
8db0f33
feat: support debug on x86_64
May 20, 2026
edec9ad
feat: update bindings
May 21, 2026
ef81c65
ci: add `fix/kmp-ios-impl` to Release iOS App runner
oluiscabral May 21, 2026
1bccf74
feat: iOS Send and Receive with Firebase logs
oluiscabral May 23, 2026
e0c9d26
ci: set current branch as TestFlight deploy trigger
oluiscabral May 23, 2026
3391ed1
fix: unversioned Crashlytics
oluiscabral May 23, 2026
6ab1305
fix: crashlytics KTX module no longer supported
oluiscabral May 23, 2026
35c3d76
style: maintain type style consistency
oluiscabral May 23, 2026
5cea8e9
fix: missing `google-services.json` on development lint
oluiscabral May 23, 2026
f621acc
style: ./gradlew ktlintFormat
oluiscabral May 23, 2026
c9535d3
style: ktlint impositions
oluiscabral May 23, 2026
f5b02d5
style: ktlint format and check
May 24, 2026
8ea6f29
Firebase config validation for iOS builds
kirillt May 24, 2026
8fdc240
Indentation fix
kirillt May 24, 2026
d77cdc8
Environments refactoring
kirillt Jun 4, 2026
4530391
CI fix
kirillt Jun 4, 2026
2a09e34
CI fix
kirillt Jun 4, 2026
bcbabcb
Firebase integration
kirillt Jun 4, 2026
d8dbc2b
Fix CI
kirillt Jun 4, 2026
6e9a823
Fix CI
kirillt Jun 4, 2026
9692ac0
Fix CI
kirillt Jun 4, 2026
4f82be2
Basic dSYM support
kirillt Jun 4, 2026
a1efdd0
CI improvement
kirillt Jun 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions .github/workflows/_android-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
name: Android Build

on:
workflow_call:
inputs:
environment_name:
required: true
type: string
build_command:
required: true
type: string
expected_firebase_project_id:
required: true
type: string
run_ktlint:
required: false
type: boolean
default: false
run_lint:
required: false
type: boolean
default: false
use_keystore:
required: false
type: boolean
default: false
upload_artifact:
required: false
type: boolean
default: false
artifact_name:
required: false
type: string
default: android-build
artifact_path:
required: false
type: string
default: ./composeApp/build/outputs/apk/release/composeApp-release.apk
lint_artifact_name:
required: false
type: string
default: android-lint-results

env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

jobs:
build:
environment: ${{ inputs.environment_name }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'adopt'
cache: gradle

- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@v3

- name: Run KtLint
if: ${{ inputs.run_ktlint }}
run: ./gradlew ktlintCheck

- name: Decrypt Android keystore
if: ${{ inputs.use_keystore }}
run: |
set -euo pipefail
missing=()
for name in \
ANDROID_KEYSTORE_ENCRYPTED \
ANDROID_KEYSTORE_PASSWORD \
ANDROID_KEYSTORE_STORE_PASSWORD \
ANDROID_KEY_ALIAS \
ANDROID_KEY_PASSWORD
do
if [ -z "${!name:-}" ]; then
missing+=("$name")
fi
done

if [ ${#missing[@]} -gt 0 ]; then
printf 'Missing required Android signing secrets:\n'
printf ' - %s\n' "${missing[@]}"
exit 1
fi

printf '%s' "$ANDROID_KEYSTORE_ENCRYPTED" > keystore.asc
gpg -d --passphrase "$ANDROID_KEYSTORE_PASSWORD" --batch keystore.asc > keystore.jks
if [ ! -s keystore.jks ]; then
echo "keystore.jks is missing or empty after decrypting ANDROID_KEYSTORE_ENCRYPTED"
exit 1
fi
KEYSTORE_INFO=$(keytool -list -v -keystore keystore.jks -storepass "$ANDROID_KEYSTORE_STORE_PASSWORD" -alias "$ANDROID_KEY_ALIAS")
echo "$KEYSTORE_INFO" | grep -q "PrivateKeyEntry"
KEYSTORE_TYPE=$(echo "$KEYSTORE_INFO" | sed -n 's/^Keystore type: //p' | head -n 1)
if [ "$KEYSTORE_TYPE" = "JKS" ]; then
keytool -certreq -keystore keystore.jks -storepass "$ANDROID_KEYSTORE_STORE_PASSWORD" -alias "$ANDROID_KEY_ALIAS" -keypass "$ANDROID_KEY_PASSWORD" -file android-release-signing.csr >/dev/null
rm -f android-release-signing.csr
fi
env:
ANDROID_KEYSTORE_ENCRYPTED: ${{ secrets.ANDROID_KEYSTORE_ENCRYPTED }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEYSTORE_STORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_STORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}

- name: Decode Android Firebase config
run: |
set -euo pipefail
if [ -z "${ANDROID_GOOGLE_SERVICES_JSON_BASE64:-}" ]; then
echo "ANDROID_GOOGLE_SERVICES_JSON_BASE64 secret is missing or empty"
exit 1
fi
echo "$ANDROID_GOOGLE_SERVICES_JSON_BASE64" | base64 --decode > composeApp/google-services.json
node - "${{ inputs.expected_firebase_project_id }}" <<'NODE'
const fs = require("fs");
const expectedProjectId = process.argv[2];
const expectedPackage = "dev.arkbuilders.drop";
const cfg = JSON.parse(fs.readFileSync("composeApp/google-services.json", "utf8"));
const clients = cfg.client || [];
if (!cfg.project_info || !cfg.project_info.project_id) {
throw new Error("Missing project_info.project_id");
}
if (cfg.project_info.project_id !== expectedProjectId) {
throw new Error(`Firebase project mismatch. Expected ${expectedProjectId}, got ${cfg.project_info.project_id}`);
}
const matchingClient = clients.find((client) =>
client.client_info &&
client.client_info.android_client_info &&
client.client_info.android_client_info.package_name === expectedPackage
);
if (!matchingClient) {
throw new Error(`Missing Firebase client for ${expectedPackage}`);
}
const appId = matchingClient.client_info.mobilesdk_app_id;
if (!appId || !appId.includes(":android:")) {
throw new Error("Firebase Android client is missing a valid mobilesdk_app_id");
}
const apiKeys = matchingClient.api_key || [];
if (!apiKeys.some((apiKey) => apiKey.current_key)) {
throw new Error("Firebase Android client is missing api_key.current_key");
}
NODE
env:
ANDROID_GOOGLE_SERVICES_JSON_BASE64: ${{ secrets.ANDROID_GOOGLE_SERVICES_JSON_BASE64 }}

- name: Build Android
run: ${{ inputs.build_command }}
env:
ANDROID_KEYSTORE_STORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_STORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}

- name: Run Android lint
if: ${{ inputs.run_lint }}
run: ./gradlew lint

- name: Upload Android lint results
if: ${{ always() && inputs.run_lint }}
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.lint_artifact_name }}
path: ./composeApp/build/reports/*.html

- name: Upload Android artifact
if: ${{ inputs.upload_artifact }}
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact_name }}
path: ${{ inputs.artifact_path }}
219 changes: 219 additions & 0 deletions .github/workflows/_ios-testflight.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
name: iOS TestFlight

on:
workflow_call:
inputs:
environment_name:
required: true
type: string
artifact_name:
required: true
type: string
log_artifact_name:
required: true
type: string
expected_firebase_project_id:
required: true
type: string

jobs:
build:
runs-on: macos-26
environment: ${{ inputs.environment_name }}
env:
APP_BUNDLE_ID: dev.ark-builders.drop
APP_TEAM_ID: SQNXHTL7FT
IOS_P12_PASSWORD: ${{ secrets.IOS_P12_PASSWORD }}
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Validate secrets
run: |
set -euo pipefail
missing=()
for name in \
IOS_P12_BASE64 \
IOS_P12_PASSWORD \
IOS_PROFILE_BASE64 \
ASC_API_KEY_BASE64 \
ASC_KEY_ID \
ASC_ISSUER_ID \
IOS_GOOGLE_SERVICE_INFO_PLIST_BASE64
do
if [ -z "${!name:-}" ]; then
missing+=("$name")
fi
done

if [ ${#missing[@]} -gt 0 ]; then
printf 'Missing required %s secrets:\n' "${{ inputs.environment_name }}"
printf ' - %s\n' "${missing[@]}"
exit 1
fi
env:
IOS_P12_BASE64: ${{ secrets.IOS_P12_BASE64 }}
IOS_PROFILE_BASE64: ${{ secrets.IOS_PROFILE_BASE64 }}
ASC_API_KEY_BASE64: ${{ secrets.ASC_API_KEY_BASE64 }}
IOS_GOOGLE_SERVICE_INFO_PLIST_BASE64: ${{ secrets.IOS_GOOGLE_SERVICE_INFO_PLIST_BASE64 }}

- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3"
bundler-cache: true

- name: Install gems
run: bundle install

- name: Build Kotlin framework for Release
run: |
export JAVA_HOME=$(/usr/libexec/java_home -v 17)
./gradlew :shared:assembleSharedReleaseXCFramework
mkdir -p shared/build/XCFrameworks/debug
cp -R shared/build/XCFrameworks/release/shared.xcframework shared/build/XCFrameworks/debug/

- name: Ensure XCFramework Info.plist
run: |
set -euo pipefail
XCF_DIR="shared/build/XCFrameworks/debug/shared.xcframework"
if [ ! -f "$XCF_DIR/Info.plist" ]; then
cp shared/build/XCFrameworks/release/shared.xcframework/Info.plist "$XCF_DIR/" 2>/dev/null || \
cp shared/XCFramework-Info.plist "$XCF_DIR/Info.plist"
fi

- name: Select Xcode version
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '26.2'

- name: Decode iOS certificate
run: |
set -euo pipefail
echo "$IOS_P12_BASE64" | base64 --decode > cert.p12
if [ "$(stat -f%z cert.p12)" -lt 100 ]; then
echo "cert.p12 is too small, likely corrupted"
exit 1
fi
env:
IOS_P12_BASE64: ${{ secrets.IOS_P12_BASE64 }}

- name: Validate iOS certificate
run: |
set -euo pipefail
openssl pkcs12 -in cert.p12 -legacy -nodes -passin env:IOS_P12_PASSWORD 2>&1 | grep -q "PRIVATE KEY"

- name: Decode provisioning profile
run: |
set -euo pipefail
echo "$IOS_PROFILE_BASE64" | base64 --decode > profile.mobileprovision
if [ "$(stat -f%z profile.mobileprovision)" -lt 100 ]; then
echo "profile.mobileprovision is too small, likely corrupted"
exit 1
fi
security cms -D -i profile.mobileprovision > profile.plist
plutil -lint profile.plist
PROFILE_TEAM_ID=$(/usr/libexec/PlistBuddy -c "Print :TeamIdentifier:0" profile.plist)
if [ "$PROFILE_TEAM_ID" != "$APP_TEAM_ID" ]; then
echo "Provisioning profile TeamIdentifier mismatch. Expected $APP_TEAM_ID, got $PROFILE_TEAM_ID"
exit 1
fi
PROFILE_APP_ID=$(/usr/libexec/PlistBuddy -c "Print :Entitlements:application-identifier" profile.plist)
EXPECTED_APP_ID="$APP_TEAM_ID.$APP_BUNDLE_ID"
if [ "$PROFILE_APP_ID" != "$EXPECTED_APP_ID" ]; then
echo "Provisioning profile application-identifier mismatch. Expected $EXPECTED_APP_ID, got $PROFILE_APP_ID"
exit 1
fi
env:
IOS_PROFILE_BASE64: ${{ secrets.IOS_PROFILE_BASE64 }}

- name: Decode and validate Firebase config
run: |
set -euo pipefail
PLIST_PATH="iosApp/iosApp/GoogleService-Info.plist"
echo "$IOS_GOOGLE_SERVICE_INFO_PLIST_BASE64" | base64 -D > "$PLIST_PATH"
plutil -lint "$PLIST_PATH"
for key in GOOGLE_APP_ID BUNDLE_ID API_KEY GCM_SENDER_ID PROJECT_ID; do
/usr/libexec/PlistBuddy -c "Print :$key" "$PLIST_PATH" >/dev/null
done
PLIST_PROJECT_ID=$(/usr/libexec/PlistBuddy -c "Print :PROJECT_ID" "$PLIST_PATH")
if [ "$PLIST_PROJECT_ID" != "${{ inputs.expected_firebase_project_id }}" ]; then
echo "Firebase PROJECT_ID mismatch. Expected ${{ inputs.expected_firebase_project_id }}, got $PLIST_PROJECT_ID"
exit 1
fi
PLIST_BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :BUNDLE_ID" "$PLIST_PATH")
if [ "$PLIST_BUNDLE_ID" != "$APP_BUNDLE_ID" ]; then
echo "Firebase BUNDLE_ID mismatch. Expected $APP_BUNDLE_ID, got $PLIST_BUNDLE_ID"
exit 1
fi
GOOGLE_APP_ID=$(/usr/libexec/PlistBuddy -c "Print :GOOGLE_APP_ID" "$PLIST_PATH")
if [[ "$GOOGLE_APP_ID" != *":ios:"* ]]; then
echo "Firebase GOOGLE_APP_ID does not look like an iOS app id: $GOOGLE_APP_ID"
exit 1
fi
API_KEY=$(/usr/libexec/PlistBuddy -c "Print :API_KEY" "$PLIST_PATH")
if [ -z "$API_KEY" ]; then
echo "Firebase API_KEY is empty"
exit 1
fi
env:
IOS_GOOGLE_SERVICE_INFO_PLIST_BASE64: ${{ secrets.IOS_GOOGLE_SERVICE_INFO_PLIST_BASE64 }}

- name: Setup App Store Connect API Key
run: |
set -euo pipefail
mkdir -p ~/.fastlane
echo "$ASC_API_KEY_BASE64" | base64 --decode > ~/.fastlane/AuthKey.p8
chmod 600 ~/.fastlane/AuthKey.p8
if [ ! -s ~/.fastlane/AuthKey.p8 ]; then
echo "AuthKey.p8 is missing or empty"
exit 1
fi
if ! grep -q "BEGIN PRIVATE KEY" ~/.fastlane/AuthKey.p8; then
echo "AuthKey.p8 does not look like an App Store Connect private key"
exit 1
fi
env:
ASC_API_KEY_BASE64: ${{ secrets.ASC_API_KEY_BASE64 }}

- name: Set build number for TestFlight
run: |
BUILD_NUM=$(date +%s)
sed -i.bak "s/CURRENT_PROJECT_VERSION=.*/CURRENT_PROJECT_VERSION=$BUILD_NUM/" iosApp/Configuration/Config.xcconfig
rm -f iosApp/Configuration/Config.xcconfig.bak

- name: Build and upload to TestFlight
run: bundle exec fastlane beta
env:
EXPECTED_FIREBASE_PROJECT_ID: ${{ inputs.expected_firebase_project_id }}
IOS_P12_PASSWORD: ${{ secrets.IOS_P12_PASSWORD }}
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.ASC_KEY_ID }}
APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}

- name: Upload IPA artifact
uses: actions/upload-artifact@v4
if: success()
with:
name: ${{ inputs.artifact_name }}
path: build/ARK-Drop.ipa
retention-days: 30

- name: Upload build logs
uses: actions/upload-artifact@v4
if: always()
with:
name: ${{ inputs.log_artifact_name }}
path: |
~/Library/Logs/gym/
build/
retention-days: 7

- name: Cleanup
if: always()
run: |
rm -f cert.p12 profile.mobileprovision profile.plist build/Bundled-GoogleService-Info.plist ~/.fastlane/AuthKey.p8
security delete-keychain "$HOME/Library/Keychains/ark_drop_ci_keychain-db" >/dev/null 2>&1 || true
Loading
Loading