feat(ios): implement Phase 1 permission methods and fix build issues

Implement checkPermissionStatus() and requestNotificationPermissions()
methods for iOS plugin, matching Android functionality. Fix compilation
errors across plugin files and add comprehensive build/test infrastructure.

Key Changes:
- Add checkPermissionStatus() and requestNotificationPermissions() methods
- Fix 13+ categories of Swift compilation errors (type conversions, logger
  API, access control, async/await, etc.)
- Create DailyNotificationScheduler, DailyNotificationStorage,
  DailyNotificationStateActor, and DailyNotificationErrorCodes components
- Fix CoreData initialization to handle missing model gracefully for Phase 1
- Add iOS test app build script with simulator auto-detection
- Update directive with lessons learned from build and permission work

Build Status:  BUILD SUCCEEDED
Test App:  Ready for iOS Simulator testing

Files Modified:
- doc/directives/0003-iOS-Android-Parity-Directive.md (lessons learned)
- ios/Plugin/DailyNotificationPlugin.swift (Phase 1 methods)
- ios/Plugin/DailyNotificationModel.swift (CoreData fix)
- 11+ other plugin files (compilation fixes)

Files Added:
- ios/Plugin/DailyNotificationScheduler.swift
- ios/Plugin/DailyNotificationStorage.swift
- ios/Plugin/DailyNotificationStateActor.swift
- ios/Plugin/DailyNotificationErrorCodes.swift
- scripts/build-ios-test-app.sh
- scripts/setup-ios-test-app.sh
- test-apps/ios-test-app/ (full test app)
- Multiple Phase 1 documentation files
This commit is contained in:
Server
2025-11-13 05:14:24 -08:00
parent 2d84ae29ba
commit 5844b92e18
61 changed files with 9676 additions and 356 deletions

485
scripts/build-ios-test-app.sh Executable file
View File

@@ -0,0 +1,485 @@
#!/bin/bash
# Exit on error
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Logging functions
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_step() {
echo -e "${BLUE}[STEP]${NC} $1"
}
# Validation functions
check_command() {
if ! command -v $1 &> /dev/null; then
# Try rbenv shims for pod command
if [ "$1" = "pod" ] && [ -f "$HOME/.rbenv/shims/pod" ]; then
log_info "Found pod in rbenv shims"
return 0
fi
log_error "$1 is not installed. Please install it first."
exit 1
fi
}
# Get pod command (handles rbenv)
get_pod_command() {
if command -v pod &> /dev/null; then
echo "pod"
elif [ -f "$HOME/.rbenv/shims/pod" ]; then
echo "$HOME/.rbenv/shims/pod"
else
log_error "CocoaPods (pod) not found. Please install CocoaPods first."
exit 1
fi
}
check_environment() {
log_step "Checking environment..."
# Check for required tools
check_command "xcodebuild"
check_command "pod"
check_command "node"
check_command "npm"
# Check for Xcode
if ! xcodebuild -version &> /dev/null; then
log_error "Xcode is not installed or not properly configured"
exit 1
fi
# Check Node.js version
NODE_VERSION=$(node -v | cut -d. -f1 | tr -d 'v')
if [ "$NODE_VERSION" -lt 14 ]; then
log_error "Node.js version 14 or higher is required"
exit 1
fi
log_info "Environment check passed"
}
# Parse arguments
TARGET="simulator"
BUILD_CONFIG="Debug"
while [[ $# -gt 0 ]]; do
case $1 in
--simulator)
TARGET="simulator"
shift
;;
--device)
TARGET="device"
shift
;;
--release)
BUILD_CONFIG="Release"
shift
;;
--help)
echo "Usage: $0 [--simulator|--device] [--release]"
echo ""
echo "Options:"
echo " --simulator Build for iOS Simulator (default)"
echo " --device Build for physical device"
echo " --release Build Release configuration (default: Debug)"
echo " --help Show this help message"
exit 0
;;
*)
log_error "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
done
# Check if iOS test app exists
TEST_APP_DIR="test-apps/ios-test-app"
if [ ! -d "$TEST_APP_DIR" ]; then
log_error "iOS test app not found at $TEST_APP_DIR"
log_info "The iOS test app needs to be created first."
log_info "See doc/directives/0003-iOS-Android-Parity-Directive.md for requirements."
exit 1
fi
# Main build function
build_ios_test_app() {
log_step "Building iOS test app..."
# Navigate to iOS App directory (where workspace is located)
IOS_APP_DIR="$TEST_APP_DIR/ios/App"
if [ ! -d "$IOS_APP_DIR" ]; then
log_error "iOS App directory not found: $IOS_APP_DIR"
exit 1
fi
cd "$IOS_APP_DIR" || exit 1
# Check for workspace or project (these are directories, not files)
if [ -d "App.xcworkspace" ]; then
WORKSPACE="App.xcworkspace"
SCHEME="App"
elif [ -d "App.xcodeproj" ]; then
PROJECT="App.xcodeproj"
SCHEME="App"
else
log_error "No Xcode workspace or project found in $IOS_APP_DIR"
log_info "Expected: App.xcworkspace or App.xcodeproj"
log_info "Found files: $(ls -la | head -10)"
exit 1
fi
# Install CocoaPods dependencies
log_step "Installing CocoaPods dependencies..."
POD_CMD=$(get_pod_command)
if [ -f "Podfile" ]; then
if ! $POD_CMD install; then
log_error "CocoaPods installation failed"
exit 1
fi
log_info "CocoaPods dependencies installed"
else
log_warn "No Podfile found, skipping pod install"
fi
# Build TypeScript/JavaScript if package.json exists
if [ -f "package.json" ]; then
log_step "Building web assets..."
if [ -f "package.json" ] && grep -q "\"build\"" package.json; then
if ! npm run build; then
log_error "Web assets build failed"
exit 1
fi
log_info "Web assets built"
fi
# Sync Capacitor if needed
if command -v npx &> /dev/null && [ -f "capacitor.config.ts" ] || [ -f "capacitor.config.json" ]; then
log_step "Syncing Capacitor..."
if ! npx cap sync ios; then
log_error "Capacitor sync failed"
exit 1
fi
log_info "Capacitor synced"
fi
fi
# Determine SDK and destination
if [ "$TARGET" = "simulator" ]; then
SDK="iphonesimulator"
# Initialize simulator variables
SIMULATOR_ID=""
SIMULATOR_NAME=""
# Auto-detect available iPhone simulator using device ID (more reliable)
log_step "Detecting available iPhone simulator..."
SIMULATOR_LINE=$(xcrun simctl list devices available 2>&1 | grep -i "iPhone" | head -1)
if [ -n "$SIMULATOR_LINE" ]; then
# Extract device ID (UUID in parentheses)
SIMULATOR_ID=$(echo "$SIMULATOR_LINE" | sed -E 's/.*\(([A-F0-9-]+)\).*/\1/')
# Extract device name (everything before the first parenthesis)
SIMULATOR_NAME=$(echo "$SIMULATOR_LINE" | sed -E 's/^[[:space:]]*([^(]+).*/\1/' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
if [ -n "$SIMULATOR_ID" ] && [ "$SIMULATOR_ID" != "Shutdown" ] && [ "$SIMULATOR_ID" != "Booted" ]; then
# Use device ID (most reliable)
DESTINATION="platform=iOS Simulator,id=$SIMULATOR_ID"
log_info "Building for iOS Simulator ($SIMULATOR_NAME, ID: $SIMULATOR_ID)..."
elif [ -n "$SIMULATOR_NAME" ]; then
# Fallback to device name
DESTINATION="platform=iOS Simulator,name=$SIMULATOR_NAME"
log_info "Building for iOS Simulator ($SIMULATOR_NAME)..."
else
# Last resort: generic destination
DESTINATION="platform=iOS Simulator,name=Any iOS Simulator Device"
log_warn "Using generic simulator destination"
fi
else
# No iPhone simulators found, use generic
DESTINATION="platform=iOS Simulator,name=Any iOS Simulator Device"
log_warn "No iPhone simulators found, using generic destination"
fi
ARCHIVE_PATH="build/ios-test-app-simulator.xcarchive"
else
SDK="iphoneos"
DESTINATION="generic/platform=iOS"
ARCHIVE_PATH="build/ios-test-app-device.xcarchive"
fi
# Clean build folder
log_step "Cleaning build folder..."
if [ -n "$WORKSPACE" ]; then
xcodebuild clean -workspace "$WORKSPACE" -scheme "$SCHEME" -configuration "$BUILD_CONFIG" -sdk "$SDK" || true
else
xcodebuild clean -project "$PROJECT" -scheme "$SCHEME" -configuration "$BUILD_CONFIG" -sdk "$SDK" || true
fi
# Build
log_step "Building for $TARGET ($BUILD_CONFIG)..."
if [ -n "$WORKSPACE" ]; then
if ! xcodebuild build \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-configuration "$BUILD_CONFIG" \
-sdk "$SDK" \
-destination "$DESTINATION" \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=NO; then
log_error "Build failed"
exit 1
fi
else
if ! xcodebuild build \
-project "$PROJECT" \
-scheme "$SCHEME" \
-configuration "$BUILD_CONFIG" \
-sdk "$SDK" \
-destination "$DESTINATION" \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=NO; then
log_error "Build failed"
exit 1
fi
fi
log_info "Build successful!"
# Find the built app in DerivedData
if [ "$TARGET" = "simulator" ]; then
# Xcode builds to DerivedData, find the app there
DERIVED_DATA_PATH="$HOME/Library/Developer/Xcode/DerivedData"
APP_PATH=$(find "$DERIVED_DATA_PATH" -name "App.app" -path "*/Build/Products/Debug-iphonesimulator/*" -type d 2>/dev/null | head -1)
if [ -n "$APP_PATH" ]; then
log_info "App built at: $APP_PATH"
log_info ""
# Boot simulator if not already booted
log_step "Checking simulator status..."
if [ -n "$SIMULATOR_ID" ]; then
SIMULATOR_STATE=$(xcrun simctl list devices | grep "$SIMULATOR_ID" | grep -o "(Booted\|Shutdown)" | head -1)
if [ "$SIMULATOR_STATE" != "Booted" ]; then
log_step "Booting simulator ($SIMULATOR_NAME)..."
xcrun simctl boot "$SIMULATOR_ID" 2>/dev/null || log_warn "Simulator may already be booting"
# Open Simulator app if not already open
if ! pgrep -x "Simulator" > /dev/null; then
log_step "Opening Simulator app..."
open -a Simulator
fi
# Wait for simulator to fully boot (up to 60 seconds)
log_step "Waiting for simulator to boot (this may take up to 60 seconds)..."
BOOT_TIMEOUT=60
ELAPSED=0
CURRENT_STATE="Shutdown"
while [ $ELAPSED -lt $BOOT_TIMEOUT ]; do
CURRENT_STATE=$(xcrun simctl list devices | grep "$SIMULATOR_ID" | grep -o "(Booted\|Shutdown)" | head -1)
if [ "$CURRENT_STATE" = "Booted" ]; then
log_info "Simulator booted successfully (took ${ELAPSED}s)"
# Give it a few more seconds to fully initialize
sleep 3
break
fi
if [ $((ELAPSED % 5)) -eq 0 ] && [ $ELAPSED -gt 0 ]; then
log_info "Still waiting... (${ELAPSED}s elapsed)"
fi
sleep 1
ELAPSED=$((ELAPSED + 1))
done
if [ "$CURRENT_STATE" != "Booted" ]; then
log_warn "Simulator may not have finished booting (waited ${ELAPSED}s)"
log_warn "You may need to manually boot the simulator and try again"
else
# Verify simulator is actually ready (not just booted)
log_info "Verifying simulator is ready..."
READY_ATTEMPTS=0
MAX_READY_ATTEMPTS=10
while [ $READY_ATTEMPTS -lt $MAX_READY_ATTEMPTS ]; do
# Try a simple command to verify simulator is responsive
if xcrun simctl list devices | grep "$SIMULATOR_ID" | grep -q "Booted"; then
# Try to get device info to verify it's responsive
if xcrun simctl get_app_container "$SIMULATOR_ID" com.apple.Preferences >/dev/null 2>&1; then
log_info "Simulator is ready"
break
fi
fi
sleep 1
READY_ATTEMPTS=$((READY_ATTEMPTS + 1))
done
if [ $READY_ATTEMPTS -eq $MAX_READY_ATTEMPTS ]; then
log_warn "Simulator may not be fully ready yet"
fi
fi
else
log_info "Simulator already booted"
# Verify it's actually ready
if ! xcrun simctl get_app_container "$SIMULATOR_ID" com.apple.Preferences >/dev/null 2>&1; then
log_warn "Simulator is booted but may not be fully ready"
log_info "Waiting a few seconds for simulator to be ready..."
sleep 5
fi
fi
# Install the app
log_step "Installing app on simulator..."
if xcrun simctl install "$SIMULATOR_ID" "$APP_PATH" 2>&1; then
log_info "App installed successfully"
else
log_warn "Install may have failed (app may already be installed)"
fi
# Wait a moment for install to complete
sleep 1
# Launch the app (try multiple methods)
log_step "Launching app..."
LAUNCH_SUCCESS=false
LAUNCH_ERROR=""
# Wait a moment for simulator to be fully ready
sleep 2
# Method 1: Direct launch (capture output to check for errors)
# Note: Bundle ID is com.timesafari.dailynotification (not .test)
log_info "Attempting to launch app..."
LAUNCH_OUTPUT=$(xcrun simctl launch "$SIMULATOR_ID" com.timesafari.dailynotification 2>&1)
LAUNCH_EXIT_CODE=$?
if [ $LAUNCH_EXIT_CODE -eq 0 ]; then
# Check if output contains process ID (successful launch)
# Format can be either "PID" or "bundle: PID"
if echo "$LAUNCH_OUTPUT" | grep -qE "^[0-9]+$|^[^:]+: [0-9]+$"; then
LAUNCH_SUCCESS=true
# Extract PID (either standalone number or after colon)
APP_PID=$(echo "$LAUNCH_OUTPUT" | sed -E 's/^[^:]*:? *([0-9]+).*/\1/' | head -1)
log_info "✅ App launched successfully! (PID: $APP_PID)"
else
# Launch command succeeded but may not have actually launched
log_warn "Launch command returned success but output unexpected: $LAUNCH_OUTPUT"
fi
else
# Capture error message
LAUNCH_ERROR="$LAUNCH_OUTPUT"
log_warn "Launch failed: $LAUNCH_ERROR"
fi
# Method 2: Verify app is actually running
if [ "$LAUNCH_SUCCESS" = false ]; then
log_info "Checking if app is already running..."
sleep 2
RUNNING_APPS=$(xcrun simctl listapps "$SIMULATOR_ID" 2>/dev/null | grep -A 5 "com.timesafari.dailynotification" || echo "")
if [ -n "$RUNNING_APPS" ]; then
log_info "App appears to be installed. Trying to verify it's running..."
# Try to get app state
APP_STATE=$(xcrun simctl listapps "$SIMULATOR_ID" 2>/dev/null | grep -A 10 "com.timesafari.dailynotification" | grep "ApplicationType" || echo "")
if [ -n "$APP_STATE" ]; then
log_info "App found in simulator. Attempting manual launch..."
# Try opening via Simulator app
open -a Simulator
sleep 1
# Try launch one more time
if xcrun simctl launch "$SIMULATOR_ID" com.timesafari.dailynotification >/dev/null 2>&1; then
LAUNCH_SUCCESS=true
log_info "✅ App launched successfully on retry!"
fi
fi
fi
fi
# Final verification: check if app process is running
if [ "$LAUNCH_SUCCESS" = true ]; then
sleep 2
# Try to verify app is running by checking if we can get its container
if xcrun simctl get_app_container "$SIMULATOR_ID" com.timesafari.dailynotification >/dev/null 2>&1; then
log_info "✅ Verified: App is installed and accessible"
else
log_warn "⚠️ Launch reported success but app verification failed"
log_warn " The app may still be starting. Check the Simulator."
fi
else
log_warn "❌ Automatic launch failed"
log_info ""
log_info "The app is installed. To launch manually:"
log_info " 1. Open Simulator app (if not already open)"
log_info " 2. Find the app icon on the home screen and tap it"
log_info " 3. Or run: xcrun simctl launch $SIMULATOR_ID com.timesafari.dailynotification"
if [ -n "$LAUNCH_ERROR" ]; then
log_info ""
log_info "Launch error details:"
log_info " $LAUNCH_ERROR"
fi
fi
log_info ""
log_info "✅ Build and deployment complete!"
else
log_info ""
log_info "To run on simulator manually:"
log_info " xcrun simctl install booted \"$APP_PATH\""
log_info " xcrun simctl launch booted com.timesafari.dailynotification"
fi
else
log_warn "Could not find built app in DerivedData"
log_info "App was built successfully, but path detection failed."
log_info "You can find it in Xcode's DerivedData folder or run from Xcode directly."
fi
else
log_info ""
log_info "To install on device:"
log_info " Open App.xcworkspace in Xcode"
log_info " Select your device"
log_info " Press Cmd+R to build and run"
fi
cd - > /dev/null
}
# Main execution
main() {
log_info "iOS Test App Build Script"
log_info "Target: $TARGET | Configuration: $BUILD_CONFIG"
log_info ""
check_environment
# Get absolute path to repo root
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
REPO_ROOT="$( cd "$SCRIPT_DIR/.." && pwd )"
cd "$REPO_ROOT"
build_ios_test_app
log_info ""
log_info "✅ Build complete!"
}
main "$@"