From cebf341839ebae85934a94f3e6ee06b8235dc582 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 20 Nov 2025 23:05:49 -0800 Subject: [PATCH] fix(test-app): iOS permission handling and build improvements - Add BGTask identifiers and background modes to iOS Info.plist - Fix permission method calls (checkPermissionStatus vs checkPermissions) - Implement Android-style permission checking pattern - Add "Request Permissions" action card with check-then-request flow - Fix simulator selection in build script (use device ID for reliability) - Add Podfile auto-fix to fix-capacitor-plugins.js - Update build documentation with unified script usage Fixes: - BGTask registration errors (Info.plist missing identifiers) - Permission method not found errors (checkPermissions -> checkPermissionStatus) - Simulator selection failures (now uses device ID) - Podfile incorrect pod name (TimesafariDailyNotificationPlugin -> DailyNotificationPlugin) The permission flow now matches Android: check status first, then show system dialog if needed. iOS system dialog appears automatically when requestNotificationPermissions() is called. Files changed: - test-apps/daily-notification-test/ios/App/App/Info.plist (new) - test-apps/daily-notification-test/src/lib/typed-plugin.ts - test-apps/daily-notification-test/src/views/HomeView.vue - test-apps/daily-notification-test/scripts/build.sh (new) - test-apps/daily-notification-test/scripts/fix-capacitor-plugins.js - test-apps/daily-notification-test/docs/BUILD_QUICK_REFERENCE.md - test-apps/daily-notification-test/README.md - test-apps/daily-notification-test/package.json - test-apps/daily-notification-test/package-lock.json --- test-apps/daily-notification-test/README.md | 43 +- .../docs/BUILD_QUICK_REFERENCE.md | 264 +++++++- .../ios/App/App/Info.plist | 62 ++ .../daily-notification-test/package-lock.json | 2 +- .../daily-notification-test/package.json | 5 +- .../daily-notification-test/scripts/build.sh | 569 ++++++++++++++++++ .../scripts/fix-capacitor-plugins.js | 98 ++- .../src/lib/typed-plugin.ts | 33 +- .../src/views/HomeView.vue | 96 ++- 9 files changed, 1142 insertions(+), 30 deletions(-) create mode 100644 test-apps/daily-notification-test/ios/App/App/Info.plist create mode 100755 test-apps/daily-notification-test/scripts/build.sh diff --git a/test-apps/daily-notification-test/README.md b/test-apps/daily-notification-test/README.md index 481a471..b915fc1 100644 --- a/test-apps/daily-notification-test/README.md +++ b/test-apps/daily-notification-test/README.md @@ -31,20 +31,57 @@ npm install **Note**: The `postinstall` script automatically fixes Capacitor configuration files after installation. -### Capacitor Sync (Android) +## Building for Android and iOS + +### Quick Build (Recommended) + +Use the unified build script for both platforms: + +```bash +# Build and run both platforms on emulator/simulator +./scripts/build.sh --run + +# Build both platforms (no run) +./scripts/build.sh + +# Build Android only +./scripts/build.sh --android + +# Build iOS only +./scripts/build.sh --ios + +# Build and run Android on emulator +./scripts/build.sh --run-android + +# Build and run iOS on simulator +./scripts/build.sh --run-ios +``` + +**See**: `docs/BUILD_QUICK_REFERENCE.md` for detailed build instructions. + +### Manual Build Steps + +#### Capacitor Sync **Important**: Use the wrapper script instead of `npx cap sync` directly to automatically fix plugin paths: ```sh +# Sync both platforms npm run cap:sync + +# Sync Android only +npm run cap:sync:android + +# Sync iOS only +npm run cap:sync:ios ``` This will: -1. Run `npx cap sync android` +1. Run `npx cap sync` (or platform-specific sync) 2. Automatically fix `capacitor.settings.gradle` (corrects plugin path from `android/` to `android/plugin/`) 3. Ensure `capacitor.plugins.json` has the correct plugin registration -If you run `npx cap sync android` directly, you can manually fix afterward: +If you run `npx cap sync` directly, you can manually fix afterward: ```sh node scripts/fix-capacitor-plugins.js ``` diff --git a/test-apps/daily-notification-test/docs/BUILD_QUICK_REFERENCE.md b/test-apps/daily-notification-test/docs/BUILD_QUICK_REFERENCE.md index ff4d267..ff05319 100644 --- a/test-apps/daily-notification-test/docs/BUILD_QUICK_REFERENCE.md +++ b/test-apps/daily-notification-test/docs/BUILD_QUICK_REFERENCE.md @@ -1,48 +1,195 @@ # Build Process Quick Reference **Author**: Matthew Raymer -**Date**: October 17, 2025 +**Date**: October 17, 2025 +**Last Updated**: November 19, 2025 + +## ⚔ Quick Start + +**Easiest way to build and run:** + +```bash +# Build and run both platforms +./scripts/build.sh --run + +# Or build only +./scripts/build.sh +``` + +This script handles everything automatically! See [Unified Build Script](#-unified-build-script-recommended) section for all options. + +--- ## 🚨 Critical Build Steps +### Prerequisites + +**Required for All Platforms:** +- Node.js 20.19.0+ or 22.12.0+ +- npm (comes with Node.js) +- Plugin must be built (`npm run build` in plugin root directory) + - The build script will automatically build the plugin if `dist/` doesn't exist + +**For Android:** +- Java JDK 22.12 or later +- Android SDK (with `adb` in PATH) +- Gradle (comes with Android project) + +**For iOS:** +- macOS with Xcode (xcodebuild must be in PATH) +- CocoaPods (`pod --version` to check) + - Can be installed via rbenv (script handles this automatically) +- iOS deployment target: iOS 13.0+ + +**Plugin Requirements:** +- Plugin podspec must exist at: `node_modules/@timesafari/daily-notification-plugin/ios/DailyNotificationPlugin.podspec` +- Plugin must be installed: `npm install` (uses `file:../../` reference) +- Plugin must be built: `npm run build` in plugin root (creates `dist/` directory) + +### Initial Setup (One-Time) + ```bash -# 1. Build web assets +# 1. Install dependencies (includes @capacitor/ios) +npm install + +# 2. Add iOS platform (if not already added) +npx cap add ios + +# 3. Install iOS dependencies +cd ios/App +pod install +cd ../.. +``` + +### Build for Both Platforms + +```bash +# 1. Build web assets (required for both platforms) npm run build -# 2. Sync with native projects (automatically fixes plugin paths) +# 2. Sync with native projects (syncs both Android and iOS) npm run cap:sync -# 3. Build and deploy Android +# OR sync platforms individually: +# npm run cap:sync:android # Android only +# npm run cap:sync:ios # iOS only +``` + +### Android Build + +```bash +# Build Android APK cd android ./gradlew :app:assembleDebug + +# Install on device/emulator adb install -r app/build/outputs/apk/debug/app-debug.apk + +# Launch app adb shell am start -n com.timesafari.dailynotification.test/.MainActivity ``` +### iOS Build + +```bash +# Open in Xcode +cd ios/App +open App.xcworkspace + +# Or build from command line +xcodebuild -workspace App.xcworkspace \ + -scheme App \ + -configuration Debug \ + -sdk iphonesimulator \ + -destination 'platform=iOS Simulator,name=iPhone 15' \ + build + +# Or use Capacitor CLI +npx cap run ios +``` + ## āš ļø Why `npm run cap:sync` is Important **Problem**: `npx cap sync` overwrites `capacitor.plugins.json` and `capacitor.settings.gradle` with incorrect paths. **Solution**: The `cap:sync` script automatically: -1. Runs `npx cap sync android` +1. Runs `npx cap sync` (syncs both Android and iOS) 2. Fixes `capacitor.settings.gradle` (corrects plugin path from `android/` to `android/plugin/`) 3. Restores the DailyNotification plugin entry in `capacitor.plugins.json` +**Platform-specific sync:** +- `npm run cap:sync:android` - Syncs Android only (includes fix script) +- `npm run cap:sync:ios` - Syncs iOS only (no fix needed for iOS) + **Without the fix**: Plugin detection fails, build errors occur, "simplified dialog" appears. ## šŸ” Verification Checklist -After build, verify: +### Android Verification -- [ ] `capacitor.plugins.json` contains DailyNotification entry +After Android build, verify: + +- [ ] `android/app/src/main/assets/capacitor.plugins.json` contains DailyNotification entry - [ ] System Status shows "Plugin: Available" (green) - [ ] Plugin Diagnostics shows all 4 plugins - [ ] Click events work on ActionCard components - [ ] No "simplified dialog" appears -## šŸ› ļø Automated Build Script +### iOS Verification -Create `scripts/build-and-deploy.sh`: +After iOS build, verify: + +- [ ] iOS project exists at `ios/App/App.xcworkspace` +- [ ] CocoaPods installed (`pod install` completed successfully) +- [ ] Plugin framework linked in Xcode project +- [ ] App builds without errors in Xcode +- [ ] Plugin methods accessible from JavaScript +- [ ] System Status shows "Plugin: Available" (green) + +## šŸ› ļø Automated Build Scripts + +### Unified Build Script (Recommended) + +The project includes a comprehensive build script that handles both platforms: + +```bash +# Build both platforms +./scripts/build.sh + +# Build Android only +./scripts/build.sh --android + +# Build iOS only +./scripts/build.sh --ios + +# Build and run Android on emulator +./scripts/build.sh --run-android + +# Build and run iOS on simulator +./scripts/build.sh --run-ios + +# Build and run both platforms +./scripts/build.sh --run + +# Show help +./scripts/build.sh --help +``` + +**Features:** +- āœ… Automatically builds web assets +- āœ… Syncs Capacitor with both platforms +- āœ… Builds Android APK +- āœ… Builds iOS app for simulator +- āœ… Automatically finds and uses available emulator/simulator +- āœ… Installs and launches apps when using `--run` flags +- āœ… Color-coded output for easy reading +- āœ… Comprehensive error handling + +### Manual Build Scripts + +#### Build for Android + +Create `scripts/build-android.sh`: ```bash #!/bin/bash @@ -52,7 +199,7 @@ echo "šŸ”Ø Building web assets..." npm run build echo "šŸ”„ Syncing with native projects..." -npm run cap:sync +npm run cap:sync:android # This automatically syncs and fixes plugin paths echo "šŸ—ļø Building Android app..." @@ -63,7 +210,64 @@ echo "šŸ“± Installing and launching..." adb install -r app/build/outputs/apk/debug/app-debug.apk adb shell am start -n com.timesafari.dailynotification.test/.MainActivity -echo "āœ… Build and deploy complete!" +echo "āœ… Android build and deploy complete!" +``` + +### Build for iOS + +Create `scripts/build-ios.sh`: + +```bash +#!/bin/bash +set -e + +echo "šŸ”Ø Building web assets..." +npm run build + +echo "šŸ”„ Syncing with iOS..." +npm run cap:sync:ios + +echo "šŸŽ Building iOS app..." +cd ios/App +pod install +xcodebuild -workspace App.xcworkspace \ + -scheme App \ + -configuration Debug \ + -sdk iphonesimulator \ + -destination 'platform=iOS Simulator,name=iPhone 15' \ + build + +echo "āœ… iOS build complete!" +echo "šŸ“± Open Xcode to run on simulator: open App.xcworkspace" +``` + +### Build for Both Platforms + +Create `scripts/build-all.sh`: + +```bash +#!/bin/bash +set -e + +echo "šŸ”Ø Building web assets..." +npm run build + +echo "šŸ”„ Syncing with all native projects..." +npm run cap:sync + +echo "šŸ—ļø Building Android..." +cd android && ./gradlew :app:assembleDebug && cd .. + +echo "šŸŽ Building iOS..." +cd ios/App && pod install && cd ../.. +xcodebuild -workspace ios/App/App.xcworkspace \ + -scheme App \ + -configuration Debug \ + -sdk iphonesimulator \ + -destination 'platform=iOS Simulator,name=iPhone 15' \ + build + +echo "āœ… All platforms built successfully!" ``` ## šŸ› Common Issues @@ -74,9 +278,15 @@ echo "āœ… Build and deploy complete!" | Plugin not detected | "Plugin: Not Available" (red) | Check plugin registry, rebuild | | Click events not working | Buttons don't respond | Check Vue 3 compatibility, router config | | Inconsistent status | Different status in different cards | Use consistent detection logic | +| **No podspec found** | `[!] No podspec found for 'TimesafariDailyNotificationPlugin'` | Run `node scripts/fix-capacitor-plugins.js` to fix Podfile, then `pod install` | +| **Plugin not built** | Vite build fails: "Failed to resolve entry" | Run `npm run build` in plugin root directory (`../../`) | +| **CocoaPods not found** | `pod: command not found` | Install CocoaPods: `sudo gem install cocoapods` or via rbenv | +| **Xcode not found** | `xcodebuild: command not found` | Install Xcode from App Store, run `xcode-select --install` | ## šŸ“± Testing Commands +### Android Testing + ```bash # Check plugin registry cat android/app/src/main/assets/capacitor.plugins.json @@ -86,8 +296,38 @@ adb logcat | grep -i "dailynotification\|capacitor\|plugin" # Check app installation adb shell pm list packages | grep dailynotification + +# View app logs +adb logcat -s DailyNotification +``` + +### iOS Testing + +```bash +# Check if iOS project exists +ls -la ios/App/App.xcworkspace + +# Check CocoaPods installation +cd ios/App && pod install && cd ../.. + +# Monitor iOS logs (simulator) +xcrun simctl spawn booted log stream | grep -i "dailynotification\|capacitor\|plugin" + +# Check plugin in Xcode +# Open ios/App/App.xcworkspace in Xcode +# Check: Project Navigator → Frameworks → DailyNotificationPlugin.framework + +# View device logs (physical device) +# Xcode → Window → Devices and Simulators → Select device → Open Console ``` --- -**Remember**: Use `npm run cap:sync` instead of `npx cap sync android` directly - it automatically fixes the configuration files! +## šŸ“ Important Notes + +**Remember**: +- Use `npm run cap:sync` to sync both platforms (automatically fixes Android configuration files) +- Use `npm run cap:sync:android` for Android-only sync (includes fix script) +- Use `npm run cap:sync:ios` for iOS-only sync +- Always run `npm run build` before syncing to ensure latest web assets are copied +- For iOS: Run `pod install` in `ios/App/` after first sync or when dependencies change diff --git a/test-apps/daily-notification-test/ios/App/App/Info.plist b/test-apps/daily-notification-test/ios/App/App/Info.plist new file mode 100644 index 0000000..9978563 --- /dev/null +++ b/test-apps/daily-notification-test/ios/App/App/Info.plist @@ -0,0 +1,62 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + Daily Notification Test + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + BGTaskSchedulerPermittedIdentifiers + + com.timesafari.dailynotification.fetch + com.timesafari.dailynotification.notify + + UIBackgroundModes + + background-fetch + background-processing + remote-notification + + NSUserNotificationsUsageDescription + This app uses notifications to deliver daily updates and reminders. + + diff --git a/test-apps/daily-notification-test/package-lock.json b/test-apps/daily-notification-test/package-lock.json index 575d83e..609c34d 100644 --- a/test-apps/daily-notification-test/package-lock.json +++ b/test-apps/daily-notification-test/package-lock.json @@ -45,7 +45,7 @@ }, "../..": { "name": "@timesafari/daily-notification-plugin", - "version": "1.0.0", + "version": "1.0.11", "license": "MIT", "workspaces": [ "packages/*" diff --git a/test-apps/daily-notification-test/package.json b/test-apps/daily-notification-test/package.json index 74e4260..8946f48 100644 --- a/test-apps/daily-notification-test/package.json +++ b/test-apps/daily-notification-test/package.json @@ -13,11 +13,14 @@ "build-only": "vite build", "type-check": "vue-tsc --build", "lint": "eslint . --fix", - "cap:sync": "npx cap sync android && node scripts/fix-capacitor-plugins.js", + "cap:sync": "npx cap sync && node scripts/fix-capacitor-plugins.js", + "cap:sync:android": "npx cap sync android && node scripts/fix-capacitor-plugins.js", + "cap:sync:ios": "npx cap sync ios", "postinstall": "node scripts/fix-capacitor-plugins.js" }, "dependencies": { "@capacitor/android": "^6.2.1", + "@capacitor/ios": "^6.2.1", "@capacitor/cli": "^6.2.1", "@capacitor/core": "^6.2.1", "@timesafari/daily-notification-plugin": "file:../../", diff --git a/test-apps/daily-notification-test/scripts/build.sh b/test-apps/daily-notification-test/scripts/build.sh new file mode 100755 index 0000000..23baab9 --- /dev/null +++ b/test-apps/daily-notification-test/scripts/build.sh @@ -0,0 +1,569 @@ +#!/bin/bash + +# Build script for daily-notification-test Capacitor app +# Supports both Android and iOS with emulator/simulator deployment +# +# Requirements: +# - Node.js 20.19.0+ or 22.12.0+ +# - npm +# - Plugin must be built (script will auto-build if needed) +# - For Android: Java JDK 22.12+, Android SDK (adb) +# - For iOS: Xcode, CocoaPods (pod) +# +# Usage: +# ./scripts/build.sh # Build both platforms +# ./scripts/build.sh --android # Build Android only +# ./scripts/build.sh --ios # Build iOS only +# ./scripts/build.sh --run # Build and run both +# ./scripts/build.sh --help # Show help + +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 + +# Script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +# 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 + 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 requirements +check_requirements() { + log_step "Checking build requirements..." + + local missing_requirements=false + + # Check Node.js + if ! command -v node &> /dev/null; then + log_error "Node.js is not installed. Please install Node.js 20.19.0+ or 22.12.0+" + missing_requirements=true + else + log_info "āœ… Node.js: $(node --version)" + fi + + # Check npm + if ! command -v npm &> /dev/null; then + log_error "npm is not installed" + missing_requirements=true + else + log_info "āœ… npm: $(npm --version)" + fi + + # Check plugin is built + PLUGIN_ROOT="$(cd "$PROJECT_DIR/../.." && pwd)" + if [ ! -d "$PLUGIN_ROOT/dist" ]; then + log_warn "Plugin not built. Building plugin now..." + cd "$PLUGIN_ROOT" + if npm run build; then + log_info "āœ… Plugin built successfully" + else + log_error "Failed to build plugin. Please run 'npm run build' in the plugin root directory." + missing_requirements=true + fi + cd "$PROJECT_DIR" + else + log_info "āœ… Plugin built (dist/ exists)" + fi + + # Check Android requirements if building Android + if [ "$BUILD_ALL" = true ] || [ "$BUILD_ANDROID" = true ]; then + if ! command -v adb &> /dev/null; then + log_warn "Android SDK not found (adb not in PATH). Android build will be skipped." + else + log_info "āœ… Android SDK: $(adb version | head -1)" + fi + + if ! command -v java &> /dev/null; then + log_warn "Java not found. Android build may fail." + else + log_info "āœ… Java: $(java -version 2>&1 | head -1)" + fi + fi + + # Check iOS requirements if building iOS + if [ "$BUILD_ALL" = true ] || [ "$BUILD_IOS" = true ]; then + if ! command -v xcodebuild &> /dev/null; then + log_warn "Xcode not found (xcodebuild not in PATH). iOS build will be skipped." + else + log_info "āœ… Xcode: $(xcodebuild -version | head -1)" + fi + + POD_CMD=$(get_pod_command 2>/dev/null || echo "") + if [ -z "$POD_CMD" ]; then + log_warn "CocoaPods not found. iOS build will be skipped." + else + log_info "āœ… CocoaPods: $($POD_CMD --version 2>/dev/null || echo 'found')" + fi + fi + + if [ "$missing_requirements" = true ]; then + log_error "Missing required dependencies. Please install them and try again." + exit 1 + fi + + log_info "All requirements satisfied" +} + +# Parse arguments +BUILD_ANDROID=false +BUILD_IOS=false +BUILD_ALL=true +RUN_ANDROID=false +RUN_IOS=false +RUN_ALL=false + +while [[ $# -gt 0 ]]; do + case $1 in + --android) + BUILD_ANDROID=true + BUILD_ALL=false + shift + ;; + --ios) + BUILD_IOS=true + BUILD_ALL=false + shift + ;; + --run-android) + RUN_ANDROID=true + BUILD_ANDROID=true + BUILD_ALL=false + shift + ;; + --run-ios) + RUN_IOS=true + BUILD_IOS=true + BUILD_ALL=false + shift + ;; + --run) + RUN_ALL=true + BUILD_ALL=true + shift + ;; + --help|-h) + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --android Build Android only" + echo " --ios Build iOS only" + echo " --run-android Build and run Android on emulator" + echo " --run-ios Build and run iOS on simulator" + echo " --run Build and run both platforms" + echo " --help, -h Show this help message" + echo "" + echo "Default: Build both platforms (no run)" + exit 0 + ;; + *) + log_error "Unknown option: $1" + echo "Use --help for usage information" + exit 1 + ;; + esac +done + +# Change to project directory +cd "$PROJECT_DIR" + +log_info "Building daily-notification-test app" +log_info "Project directory: $PROJECT_DIR" + +# Check requirements +check_requirements + +# Step 1: Build web assets +log_step "Building web assets..." +if ! npm run build; then + log_error "Web build failed" + exit 1 +fi +log_info "Web assets built successfully" + +# Step 2: Sync Capacitor +log_step "Syncing Capacitor with native projects..." +if ! npm run cap:sync; then + log_error "Capacitor sync failed" + exit 1 +fi +log_info "Capacitor sync completed" + +# Step 2.5: Ensure fix script ran (it should have via cap:sync, but verify for iOS) +if [ "$BUILD_ALL" = true ] || [ "$BUILD_IOS" = true ]; then + if [ -d "$PROJECT_DIR/ios" ]; then + log_step "Verifying iOS Podfile configuration..." + if node "$PROJECT_DIR/scripts/fix-capacitor-plugins.js" 2>/dev/null; then + log_info "iOS Podfile verified" + fi + fi +fi + +# Android build +if [ "$BUILD_ALL" = true ] || [ "$BUILD_ANDROID" = true ]; then + log_step "Building Android app..." + + # Check for Android SDK + if ! command -v adb &> /dev/null; then + log_warn "adb not found. Android SDK may not be installed." + log_warn "Skipping Android build. Install Android SDK to build Android." + else + cd "$PROJECT_DIR/android" + + # Build APK + if ./gradlew :app:assembleDebug; then + log_info "Android APK built successfully" + + APK_PATH="$PROJECT_DIR/android/app/build/outputs/apk/debug/app-debug.apk" + + if [ -f "$APK_PATH" ]; then + log_info "APK location: $APK_PATH" + + # Run on emulator if requested + if [ "$RUN_ALL" = true ] || [ "$RUN_ANDROID" = true ]; then + log_step "Installing and launching Android app..." + + # Check for running emulator + if ! adb devices | grep -q "device$"; then + log_warn "No Android emulator/device found" + log_info "Please start an Android emulator and try again" + log_info "Or use: adb devices to check connected devices" + else + # Install APK + if adb install -r "$APK_PATH"; then + log_info "APK installed successfully" + + # Launch app + if adb shell am start -n com.timesafari.dailynotification.test/.MainActivity; then + log_info "āœ… Android app launched successfully!" + else + log_warn "Failed to launch app (may already be running)" + fi + else + log_warn "APK installation failed (may already be installed)" + fi + fi + fi + else + log_error "APK not found at expected location: $APK_PATH" + fi + else + log_error "Android build failed" + exit 1 + fi + + cd "$PROJECT_DIR" + fi +fi + +# iOS build +if [ "$BUILD_ALL" = true ] || [ "$BUILD_IOS" = true ]; then + log_step "Building iOS app..." + + # Check for Xcode + if ! command -v xcodebuild &> /dev/null; then + log_warn "xcodebuild not found. Xcode may not be installed." + log_warn "Skipping iOS build. Install Xcode to build iOS." + else + IOS_DIR="$PROJECT_DIR/ios/App" + + if [ ! -d "$IOS_DIR" ]; then + log_warn "iOS directory not found. Adding iOS platform..." + cd "$PROJECT_DIR" + npx cap add ios + fi + + cd "$IOS_DIR" + + # Install CocoaPods dependencies + log_step "Installing CocoaPods dependencies..." + POD_CMD=$(get_pod_command) + + # Check if Podfile exists and has correct plugin reference + if [ -f "$IOS_DIR/Podfile" ]; then + # Run fix script to ensure Podfile is correct + log_step "Verifying Podfile configuration..." + if node "$PROJECT_DIR/scripts/fix-capacitor-plugins.js" 2>/dev/null; then + log_info "Podfile verified" + fi + fi + + if $POD_CMD install; then + log_info "CocoaPods dependencies installed" + else + log_error "CocoaPods install failed" + log_info "Troubleshooting:" + log_info "1. Check that plugin podspec exists: ls -la $PLUGIN_ROOT/ios/DailyNotificationPlugin.podspec" + log_info "2. Verify Podfile references: pod 'DailyNotificationPlugin', :path => '../../node_modules/@timesafari/daily-notification-plugin/ios'" + log_info "3. Run fix script: node scripts/fix-capacitor-plugins.js" + exit 1 + fi + + # Find workspace + WORKSPACE="$IOS_DIR/App.xcworkspace" + if [ ! -d "$WORKSPACE" ]; then + WORKSPACE="$IOS_DIR/App.xcodeproj" + fi + + if [ ! -d "$WORKSPACE" ]; then + log_error "Xcode workspace/project not found at $IOS_DIR" + exit 1 + fi + + # Get simulator + log_step "Finding available iOS simulator..." + + # Method 1: Use xcodebuild to get available destinations (most reliable) + # This gives us the exact format xcodebuild expects + DESTINATION_STRING=$(xcodebuild -workspace "$WORKSPACE" -scheme App -showdestinations 2>/dev/null | \ + grep "iOS Simulator" | \ + grep -i "iphone" | \ + grep -v "iPhone Air" | \ + head -1) + + if [ -n "$DESTINATION_STRING" ]; then + # Extract name from destination string + # Format: "platform=iOS Simulator,id=...,name=iPhone 17 Pro,OS=26.0.1" + SIMULATOR=$(echo "$DESTINATION_STRING" | \ + sed -n 's/.*name=\([^,]*\).*/\1/p' | \ + sed 's/[[:space:]]*$//') + log_info "Found simulator via xcodebuild: $SIMULATOR" + fi + + # Method 2: Fallback to simctl if xcodebuild didn't work + if [ -z "$SIMULATOR" ] || [ "$SIMULATOR" = "Shutdown" ] || [ "$SIMULATOR" = "Booted" ]; then + # Use simctl list in JSON format for more reliable parsing + # This avoids parsing status words like "Shutdown" + SIMULATOR_JSON=$(xcrun simctl list devices available --json 2>/dev/null) + + if [ -n "$SIMULATOR_JSON" ]; then + # Extract first iPhone device name using jq if available, or grep/sed + if command -v jq &> /dev/null; then + SIMULATOR=$(echo "$SIMULATOR_JSON" | \ + jq -r '.devices | to_entries[] | .value[] | select(.name | test("iPhone"; "i")) | .name' | \ + grep -v "iPhone Air" | \ + head -1) + else + # Fallback: parse text output more carefully + # Get line with iPhone, extract name before first parenthesis + SIMULATOR_LINE=$(xcrun simctl list devices available 2>/dev/null | \ + grep -E "iPhone [0-9]" | \ + grep -v "iPhone Air" | \ + head -1) + + if [ -n "$SIMULATOR_LINE" ]; then + # Extract device name - everything before first "(" + SIMULATOR=$(echo "$SIMULATOR_LINE" | \ + sed -E 's/^[[:space:]]*([^(]+).*/\1/' | \ + sed 's/[[:space:]]*$//') + fi + fi + + # Validate it's not a status word + if [ "$SIMULATOR" = "Shutdown" ] || [ "$SIMULATOR" = "Booted" ] || [ "$SIMULATOR" = "Creating" ] || [ -z "$SIMULATOR" ]; then + SIMULATOR="" + else + log_info "Found simulator via simctl: $SIMULATOR" + fi + fi + fi + + # Method 3: Try to find iPhone 17 Pro specifically (preferred) + if [ -z "$SIMULATOR" ] || [ "$SIMULATOR" = "Shutdown" ] || [ "$SIMULATOR" = "Booted" ]; then + PRO_LINE=$(xcrun simctl list devices available 2>/dev/null | \ + grep -i "iPhone 17 Pro" | \ + head -1) + + if [ -n "$PRO_LINE" ]; then + PRO_SIM=$(echo "$PRO_LINE" | \ + awk -F'(' '{print $1}' | \ + sed 's/^[[:space:]]*//' | \ + sed 's/[[:space:]]*$//') + + if [ -n "$PRO_SIM" ] && [ "$PRO_SIM" != "Shutdown" ] && [ "$PRO_SIM" != "Booted" ] && [ "$PRO_SIM" != "Creating" ]; then + SIMULATOR="$PRO_SIM" + log_info "Using preferred simulator: $SIMULATOR" + fi + fi + fi + + # Final fallback to known good simulator + if [ -z "$SIMULATOR" ] || [ "$SIMULATOR" = "Shutdown" ] || [ "$SIMULATOR" = "Booted" ] || [ "$SIMULATOR" = "Creating" ]; then + # Try common simulator names that are likely to exist + for DEFAULT_SIM in "iPhone 17 Pro" "iPhone 17" "iPhone 16" "iPhone 15 Pro" "iPhone 15"; do + if xcrun simctl list devices available 2>/dev/null | grep -q "$DEFAULT_SIM"; then + SIMULATOR="$DEFAULT_SIM" + log_info "Using fallback simulator: $SIMULATOR" + break + fi + done + + # If still empty, use iPhone 17 Pro as final default + if [ -z "$SIMULATOR" ] || [ "$SIMULATOR" = "Shutdown" ] || [ "$SIMULATOR" = "Booted" ]; then + log_warn "Could not determine simulator. Using default: iPhone 17 Pro" + SIMULATOR="iPhone 17 Pro" + fi + fi + + log_info "Selected simulator: $SIMULATOR" + + # Extract device ID for more reliable targeting + # Format: " iPhone 17 Pro (68D19D08-4701-422C-AF61-2E21ACA1DD4C) (Shutdown)" + SIMULATOR_ID=$(xcrun simctl list devices available 2>/dev/null | \ + grep -i "$SIMULATOR" | \ + head -1 | \ + sed -n 's/.*(\([A-F0-9-]\{36\}\)).*/\1/p') + + # Verify simulator exists before building + if [ -z "$SIMULATOR_ID" ] && ! xcrun simctl list devices available 2>/dev/null | grep -q "$SIMULATOR"; then + log_warn "Simulator '$SIMULATOR' not found in available devices" + log_info "Available iPhone simulators:" + xcrun simctl list devices available 2>/dev/null | grep -i "iphone" | grep -v "iPhone Air" | head -5 + log_warn "Attempting build anyway with: $SIMULATOR" + fi + + # Build iOS app + log_step "Building iOS app for simulator..." + + # Use device ID if available, otherwise use name + if [ -n "$SIMULATOR_ID" ]; then + DESTINATION="platform=iOS Simulator,id=$SIMULATOR_ID" + log_info "Using simulator ID: $SIMULATOR_ID ($SIMULATOR)" + else + DESTINATION="platform=iOS Simulator,name=$SIMULATOR" + log_info "Using simulator name: $SIMULATOR" + fi + + if xcodebuild -workspace "$WORKSPACE" \ + -scheme App \ + -configuration Debug \ + -sdk iphonesimulator \ + -destination "$DESTINATION" \ + build; then + log_info "iOS app built successfully" + + # Find built app + DERIVED_DATA="$HOME/Library/Developer/Xcode/DerivedData" + APP_PATH=$(find "$DERIVED_DATA" -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" + + # Run on simulator if requested + if [ "$RUN_ALL" = true ] || [ "$RUN_IOS" = true ]; then + log_step "Installing and launching iOS app on simulator..." + + # Use the device ID we already extracted, or get it again + if [ -z "$SIMULATOR_ID" ]; then + SIMULATOR_ID=$(xcrun simctl list devices available 2>/dev/null | \ + grep -i "$SIMULATOR" | \ + head -1 | \ + sed -n 's/.*(\([A-F0-9-]\{36\}\)).*/\1/p') + fi + + # If we have device ID, use it; otherwise try to boot by name + if [ -n "$SIMULATOR_ID" ]; then + SIMULATOR_UDID="$SIMULATOR_ID" + log_info "Using simulator ID: $SIMULATOR_UDID" + else + # Try to boot simulator by name and get its ID + log_step "Booting simulator: $SIMULATOR..." + xcrun simctl boot "$SIMULATOR" 2>/dev/null || true + sleep 2 + SIMULATOR_UDID=$(xcrun simctl list devices 2>/dev/null | \ + grep -i "$SIMULATOR" | \ + grep -E "\([A-F0-9-]{36}\)" | \ + head -1 | \ + sed -n 's/.*(\([A-F0-9-]\{36\}\)).*/\1/p') + fi + + if [ -n "$SIMULATOR_UDID" ]; then + # Install app + if xcrun simctl install "$SIMULATOR_UDID" "$APP_PATH"; then + log_info "App installed on simulator" + + # Launch app + APP_BUNDLE_ID="com.timesafari.dailynotification.test" + if xcrun simctl launch "$SIMULATOR_UDID" "$APP_BUNDLE_ID"; then + log_info "āœ… iOS app launched successfully!" + else + log_warn "Failed to launch app (may already be running)" + fi + else + log_warn "App installation failed (may already be installed)" + fi + else + log_warn "Could not find or boot simulator" + log_info "Open Xcode and run manually: open $WORKSPACE" + fi + fi + else + log_warn "Could not find built app in DerivedData" + log_info "Build succeeded. Open Xcode to run: open $WORKSPACE" + fi + else + log_error "iOS build failed" + exit 1 + fi + + cd "$PROJECT_DIR" + fi +fi + +log_info "" +log_info "āœ… Build process complete!" +log_info "" + +# Summary +if [ "$BUILD_ANDROID" = true ] || [ "$BUILD_ALL" = true ]; then + if [ -f "$PROJECT_DIR/android/app/build/outputs/apk/debug/app-debug.apk" ]; then + log_info "Android APK: $PROJECT_DIR/android/app/build/outputs/apk/debug/app-debug.apk" + fi +fi + +if [ "$BUILD_IOS" = true ] || [ "$BUILD_ALL" = true ]; then + if [ -d "$IOS_DIR/App.xcworkspace" ]; then + log_info "iOS Workspace: $IOS_DIR/App.xcworkspace" + log_info "Open with: open $IOS_DIR/App.xcworkspace" + fi +fi + diff --git a/test-apps/daily-notification-test/scripts/fix-capacitor-plugins.js b/test-apps/daily-notification-test/scripts/fix-capacitor-plugins.js index 27d6d43..28ffe17 100755 --- a/test-apps/daily-notification-test/scripts/fix-capacitor-plugins.js +++ b/test-apps/daily-notification-test/scripts/fix-capacitor-plugins.js @@ -22,6 +22,7 @@ const __dirname = path.dirname(__filename); const PLUGINS_JSON_PATH = path.join(__dirname, '../android/app/src/main/assets/capacitor.plugins.json'); const SETTINGS_GRADLE_PATH = path.join(__dirname, '../android/capacitor.settings.gradle'); +const PODFILE_PATH = path.join(__dirname, '../ios/App/Podfile'); const PLUGIN_ENTRY = { name: "DailyNotification", @@ -103,6 +104,98 @@ ${correctPath}` } } +/** + * Fix iOS Podfile to use correct plugin pod name and path + */ +function fixPodfile() { + console.log('šŸ”§ Verifying iOS Podfile...'); + + if (!fs.existsSync(PODFILE_PATH)) { + console.log('ā„¹ļø Podfile not found (iOS platform may not be added yet)'); + return; + } + + try { + let content = fs.readFileSync(PODFILE_PATH, 'utf8'); + const originalContent = content; + + // The correct pod reference should be: + // pod 'DailyNotificationPlugin', :path => '../../node_modules/@timesafari/daily-notification-plugin/ios' + const correctPodLine = "pod 'DailyNotificationPlugin', :path => '../../node_modules/@timesafari/daily-notification-plugin/ios'"; + + // Check if Podfile already has the correct reference + if (content.includes("pod 'DailyNotificationPlugin'")) { + // Check if path is correct + if (content.includes('@timesafari/daily-notification-plugin/ios')) { + console.log('āœ… Podfile has correct DailyNotificationPlugin reference'); + } else { + // Fix the path + console.log('āš ļø Podfile has DailyNotificationPlugin but wrong path - fixing...'); + content = content.replace( + /pod ['"]DailyNotificationPlugin['"].*:path.*/, + correctPodLine + ); + + // Also fix if it's using the wrong name (TimesafariDailyNotificationPlugin) + content = content.replace( + /pod ['"]TimesafariDailyNotificationPlugin['"].*:path.*/, + correctPodLine + ); + + if (content !== originalContent) { + fs.writeFileSync(PODFILE_PATH, content); + console.log('āœ… Fixed DailyNotificationPlugin path in Podfile'); + } + } + } else if (content.includes("TimesafariDailyNotificationPlugin")) { + // Fix wrong pod name + console.log('āš ļø Podfile uses wrong pod name (TimesafariDailyNotificationPlugin) - fixing...'); + content = content.replace( + /pod ['"]TimesafariDailyNotificationPlugin['"].*:path.*/, + correctPodLine + ); + + if (content !== originalContent) { + fs.writeFileSync(PODFILE_PATH, content); + console.log('āœ… Fixed pod name in Podfile (TimesafariDailyNotificationPlugin -> DailyNotificationPlugin)'); + } + } else { + // Add the pod reference if it's missing + console.log('āš ļø Podfile missing DailyNotificationPlugin - adding...'); + + // Find the capacitor_pods function or target section + if (content.includes('def capacitor_pods')) { + // Add after capacitor_pods function + content = content.replace( + /(def capacitor_pods[\s\S]*?end)/, + `$1\n\n # Daily Notification Plugin\n ${correctPodLine}` + ); + } else if (content.includes("target 'App'")) { + // Add in target section + content = content.replace( + /(target 'App' do)/, + `$1\n ${correctPodLine}` + ); + } else { + // Add at end before post_install + content = content.replace( + /(post_install)/, + `${correctPodLine}\n\n$1` + ); + } + + if (content !== originalContent) { + fs.writeFileSync(PODFILE_PATH, content); + console.log('āœ… Added DailyNotificationPlugin to Podfile'); + } + } + + } catch (error) { + console.error('āŒ Error fixing Podfile:', error.message); + // Don't exit - iOS might not be set up yet + } +} + /** * Run all fixes */ @@ -112,9 +205,10 @@ function fixAll() { fixCapacitorPlugins(); fixCapacitorSettingsGradle(); + fixPodfile(); console.log('\nāœ… All fixes applied successfully!'); - console.log('šŸ’” These fixes will persist until the next "npx cap sync android"'); + console.log('šŸ’” These fixes will persist until the next "npx cap sync"'); } // Run if called directly @@ -122,4 +216,4 @@ if (import.meta.url === `file://${process.argv[1]}`) { fixAll(); } -export { fixCapacitorPlugins, fixCapacitorSettingsGradle, fixAll }; +export { fixCapacitorPlugins, fixCapacitorSettingsGradle, fixPodfile, fixAll }; diff --git a/test-apps/daily-notification-test/src/lib/typed-plugin.ts b/test-apps/daily-notification-test/src/lib/typed-plugin.ts index 7dda576..cdf0c67 100644 --- a/test-apps/daily-notification-test/src/lib/typed-plugin.ts +++ b/test-apps/daily-notification-test/src/lib/typed-plugin.ts @@ -72,15 +72,20 @@ export class TypedDailyNotificationPlugin implements DailyNotificationBridge { /** * Check permissions with validation + * Uses checkPermissionStatus() which is the correct method name for iOS */ async checkPermissions(): Promise { try { - const result = await (this.plugin as { checkPermissions: () => Promise }).checkPermissions() + // Use checkPermissionStatus() which is implemented on both iOS and Android + const result = await (this.plugin as { checkPermissionStatus: () => Promise }).checkPermissionStatus() - // Ensure response has required fields + // Map PermissionStatusResult to PermissionStatus format return { - notifications: result.notifications || 'denied', - notificationsEnabled: Boolean(result.notificationsEnabled) + notifications: result.notificationsEnabled ? 'granted' : 'denied', + notificationsEnabled: Boolean(result.notificationsEnabled), + exactAlarmEnabled: Boolean(result.exactAlarmEnabled), + wakeLockEnabled: Boolean(result.wakeLockEnabled), + allPermissionsGranted: Boolean(result.allPermissionsGranted) } } catch (error) { @@ -166,6 +171,26 @@ export class TypedDailyNotificationPlugin implements DailyNotificationBridge { } } + /** + * Request notification permissions (iOS method name) + * This is an alias for requestPermissions() for iOS compatibility + */ + async requestNotificationPermissions(): Promise { + try { + // Try requestNotificationPermissions first (iOS), fallback to requestPermissions + if (typeof (this.plugin as any).requestNotificationPermissions === 'function') { + await (this.plugin as { requestNotificationPermissions: () => Promise }).requestNotificationPermissions() + } else if (typeof (this.plugin as any).requestPermissions === 'function') { + await (this.plugin as { requestPermissions: () => Promise }).requestPermissions() + } else { + throw new Error('Neither requestNotificationPermissions nor requestPermissions is available') + } + } catch (error) { + logError(error, 'requestNotificationPermissions') + throw error + } + } + /** * Open exact alarm settings */ diff --git a/test-apps/daily-notification-test/src/views/HomeView.vue b/test-apps/daily-notification-test/src/views/HomeView.vue index 72b1550..448333f 100644 --- a/test-apps/daily-notification-test/src/views/HomeView.vue +++ b/test-apps/daily-notification-test/src/views/HomeView.vue @@ -47,6 +47,13 @@ @click="checkSystemStatus" :loading="isCheckingStatus" /> + => { console.log('āœ… Plugin available, checking status...') try { const status = await plugin.getNotificationStatus() - const permissions = await plugin.checkPermissions() + // Use checkPermissionStatus() which is the correct method name for iOS + const permissions = await plugin.checkPermissionStatus() const exactAlarmStatus = await plugin.getExactAlarmStatus() console.log('šŸ“Š Plugin status object:', status) @@ -232,17 +240,17 @@ const checkSystemStatus = async (): Promise => { console.log('šŸ“Š Plugin permissions:', permissions) console.log('šŸ“Š Permissions details:') - console.log(' - notifications:', permissions.notifications) - console.log(' - notificationsEnabled:', (permissions as unknown as Record).notificationsEnabled) - console.log(' - exactAlarmEnabled:', (permissions as unknown as Record).exactAlarmEnabled) - console.log(' - wakeLockEnabled:', (permissions as unknown as Record).wakeLockEnabled) - console.log(' - allPermissionsGranted:', (permissions as unknown as Record).allPermissionsGranted) + console.log(' - notificationsEnabled:', permissions.notificationsEnabled) + console.log(' - exactAlarmEnabled:', permissions.exactAlarmEnabled) + console.log(' - wakeLockEnabled:', permissions.wakeLockEnabled) + console.log(' - allPermissionsGranted:', permissions.allPermissionsGranted) console.log('šŸ“Š Exact alarm status:', exactAlarmStatus) // Map plugin response to app store format + // checkPermissionStatus() returns PermissionStatusResult with boolean flags const mappedStatus = { canScheduleNow: status.isEnabled ?? false, - postNotificationsGranted: permissions.notifications === 'granted', + postNotificationsGranted: permissions.notificationsEnabled ?? false, channelEnabled: true, // Default for now channelImportance: 3, // Default for now channelId: 'daily-notifications', @@ -351,6 +359,80 @@ const refreshSystemStatus = async (): Promise => { await checkSystemStatus() } +/** + * Check permissions and request if needed (Android pattern) + * 1. Check permission status first + * 2. If not granted, show system dialog + * 3. Refresh status after request + */ +const checkAndRequestPermissions = async (): Promise => { + console.log('šŸ” CLICK: Check and Request Permissions') + + if (isRequestingPermissions.value) { + console.log('ā³ Permission request already in progress') + return + } + + isRequestingPermissions.value = true + + try { + const { DailyNotification } = await import('@timesafari/daily-notification-plugin') + const plugin = DailyNotification + + if (!plugin) { + console.error('āŒ DailyNotification plugin not available') + return + } + + // Step 1: Check permission status first (Android pattern) + console.log('šŸ” Step 1: Checking current permission status...') + const permissionStatus = await plugin.checkPermissionStatus() + + console.log('šŸ“Š Permission status:', { + notificationsEnabled: permissionStatus.notificationsEnabled, + exactAlarmEnabled: permissionStatus.exactAlarmEnabled, + allPermissionsGranted: permissionStatus.allPermissionsGranted + }) + + // Step 2: If not granted, show system dialog + if (!permissionStatus.notificationsEnabled) { + console.log('āš ļø Permissions not granted - showing system dialog...') + console.log('šŸ“± iOS will show native permission dialog now...') + + // Request permissions - this will show the iOS system dialog + // Try requestNotificationPermissions first (iOS), fallback to requestPermissions + if (typeof (plugin as any).requestNotificationPermissions === 'function') { + await (plugin as { requestNotificationPermissions: () => Promise }).requestNotificationPermissions() + } else if (typeof (plugin as any).requestPermissions === 'function') { + await (plugin as { requestPermissions: () => Promise }).requestPermissions() + } else { + throw new Error('Permission request method not available') + } + + console.log('āœ… Permission request completed') + + // Step 3: Refresh status after request + console.log('šŸ”„ Refreshing status after permission request...') + await new Promise(resolve => setTimeout(resolve, 1000)) // Wait 1 second for system to update + await checkSystemStatus() + } else { + console.log('āœ… Permissions already granted - no dialog needed') + // Still refresh status to show current state + await checkSystemStatus() + } + + } catch (error) { + console.error('āŒ Permission check/request failed:', error) + console.error('āŒ Error details:', { + name: (error as Error).name, + message: (error as Error).message, + stack: (error as Error).stack + }) + } finally { + isRequestingPermissions.value = false + } +} + const runPluginDiagnostics = async (): Promise => { console.log('šŸ”„ CLICK: Plugin Diagnostics - METHOD CALLED!') console.log('šŸ”„ FUNCTION START: runPluginDiagnostics called at', new Date().toISOString())