diff --git a/ios/Plugin/DailyNotificationPlugin.swift b/ios/Plugin/DailyNotificationPlugin.swift index af4a0ee..332c589 100644 --- a/ios/Plugin/DailyNotificationPlugin.swift +++ b/ios/Plugin/DailyNotificationPlugin.swift @@ -80,24 +80,21 @@ public class DailyNotificationPlugin: CAPPlugin { * @param call Plugin call containing configuration parameters */ @objc func configure(_ call: CAPPluginCall) { - guard let options = call.getObject("options") else { - call.reject("Configuration options required") - return - } - + // Capacitor passes the options object directly as call data + // Read parameters directly from call (matching Android implementation) print("DNP-PLUGIN: Configuring plugin with new options") do { - // Get configuration options - let dbPath = options["dbPath"] as? String - let storageMode = options["storage"] as? String ?? "tiered" - let ttlSeconds = options["ttlSeconds"] as? Int - let prefetchLeadMinutes = options["prefetchLeadMinutes"] as? Int - let maxNotificationsPerDay = options["maxNotificationsPerDay"] as? Int - let retentionDays = options["retentionDays"] as? Int + // Get configuration options directly from call (matching Android) + let dbPath = call.getString("dbPath") + let storageMode = call.getString("storage") ?? "tiered" + let ttlSeconds = call.getInt("ttlSeconds") + let prefetchLeadMinutes = call.getInt("prefetchLeadMinutes") + let maxNotificationsPerDay = call.getInt("maxNotificationsPerDay") + let retentionDays = call.getInt("retentionDays") // Phase 1: Process activeDidIntegration configuration (deferred to Phase 3) - if let activeDidConfig = options["activeDidIntegration"] as? [String: Any] { + if let activeDidConfig = call.getObject("activeDidIntegration") { print("DNP-PLUGIN: activeDidIntegration config received (Phase 3 feature)") // TODO: Implement activeDidIntegration configuration in Phase 3 } @@ -1486,7 +1483,7 @@ public class DailyNotificationPlugin: CAPPlugin { var methods: [CAPPluginMethod] = [] // Core methods - methods.append(CAPPluginMethod(name: "configure", returnType: CAPPluginReturnNone)) + methods.append(CAPPluginMethod(name: "configure", returnType: CAPPluginReturnPromise)) methods.append(CAPPluginMethod(name: "scheduleDailyNotification", returnType: CAPPluginReturnPromise)) methods.append(CAPPluginMethod(name: "getLastNotification", returnType: CAPPluginReturnPromise)) methods.append(CAPPluginMethod(name: "cancelAllNotifications", returnType: CAPPluginReturnPromise)) diff --git a/scripts/build-ios-test-app.sh b/scripts/build-ios-test-app.sh index f332f30..dae282a 100755 --- a/scripts/build-ios-test-app.sh +++ b/scripts/build-ios-test-app.sh @@ -126,6 +126,9 @@ fi build_ios_test_app() { log_step "Building iOS test app..." + # Get repo root before changing directories (we're currently in repo root from main()) + REPO_ROOT="$(pwd)" + # Navigate to iOS App directory (where workspace is located) IOS_APP_DIR="$TEST_APP_DIR/ios/App" if [ ! -d "$IOS_APP_DIR" ]; then @@ -162,6 +165,61 @@ build_ios_test_app() { log_warn "No Podfile found, skipping pod install" fi + # Copy canonical UI from www/index.html to test app + log_step "Copying canonical UI from www/index.html..." + # Use REPO_ROOT calculated before changing directories + CANONICAL_UI="$REPO_ROOT/www/index.html" + IOS_UI_SOURCE="$REPO_ROOT/$TEST_APP_DIR/App/App/Public/index.html" + IOS_UI_RUNTIME="$REPO_ROOT/$TEST_APP_DIR/ios/App/App/public/index.html" + + if [ -f "$CANONICAL_UI" ]; then + # Copy to source location (for Capacitor sync) + if cp "$CANONICAL_UI" "$IOS_UI_SOURCE"; then + log_info "Copied canonical UI to iOS test app source" + else + log_error "Failed to copy canonical UI to source" + exit 1 + fi + + # Also copy directly to runtime location (in case sync doesn't run or is cached) + if [ -d "$(dirname "$IOS_UI_RUNTIME")" ]; then + if cp "$CANONICAL_UI" "$IOS_UI_RUNTIME"; then + log_info "Copied canonical UI to iOS test app runtime" + else + log_warn "Failed to copy canonical UI to runtime (may be synced later)" + fi + else + log_warn "Runtime directory not found, will be created by Capacitor sync" + fi + else + log_warn "Canonical UI not found at $CANONICAL_UI, skipping copy" + fi + + # Sync Capacitor from test app root (where capacitor.config.json is located) + # This must run from test-apps/ios-test-app, not from ios/App/App + log_step "Syncing Capacitor..." + TEST_APP_ROOT="$REPO_ROOT/$TEST_APP_DIR" + if [ -f "$TEST_APP_ROOT/capacitor.config.json" ] || [ -f "$TEST_APP_ROOT/capacitor.config.ts" ]; then + if command -v npx &> /dev/null; then + # Save current directory + CURRENT_DIR="$(pwd)" + # Change to test app root for sync + cd "$TEST_APP_ROOT" || exit 1 + if ! npx cap sync ios; then + log_error "Capacitor sync failed" + cd "$CURRENT_DIR" || exit 1 + exit 1 + fi + # Return to ios/App directory + cd "$CURRENT_DIR" || exit 1 + log_info "Capacitor synced" + else + log_warn "npx not found, skipping Capacitor sync" + fi + else + log_warn "Capacitor config not found at $TEST_APP_ROOT, skipping sync" + fi + # Build TypeScript/JavaScript if package.json exists if [ -f "package.json" ]; then log_step "Building web assets..." @@ -172,32 +230,6 @@ build_ios_test_app() { fi log_info "Web assets built" fi - - # Sync Capacitor if needed - # Check for config in current directory or App subdirectory - CAP_CONFIG_DIR="" - if [ -f "capacitor.config.ts" ] || [ -f "capacitor.config.json" ]; then - CAP_CONFIG_DIR="." - elif [ -f "App/capacitor.config.json" ] || [ -f "App/capacitor.config.ts" ]; then - CAP_CONFIG_DIR="App" - fi - - if command -v npx &> /dev/null && [ -n "$CAP_CONFIG_DIR" ]; then - log_step "Syncing Capacitor..." - # Run sync from directory containing config - if [ "$CAP_CONFIG_DIR" != "." ]; then - cd "$CAP_CONFIG_DIR" || exit 1 - fi - if ! npx cap sync ios; then - log_error "Capacitor sync failed" - exit 1 - fi - # Return to ios/App directory if we changed - if [ "$CAP_CONFIG_DIR" != "." ]; then - cd .. || exit 1 - fi - log_info "Capacitor synced" - fi fi # Determine SDK and destination @@ -244,7 +276,16 @@ build_ios_test_app() { ARCHIVE_PATH="build/ios-test-app-device.xcarchive" fi - # Clean build folder + # Ensure UI is copied to runtime location one more time before build + # (in case sync didn't run or was cached) + log_step "Ensuring canonical UI is in runtime location before build..." + if [ -f "$CANONICAL_UI" ] && [ -d "$(dirname "$IOS_UI_RUNTIME")" ]; then + if cp "$CANONICAL_UI" "$IOS_UI_RUNTIME"; then + log_info "Canonical UI copied to runtime location (pre-build)" + fi + fi + + # Clean build folder (removes old DerivedData) log_step "Cleaning build folder..." if [ -n "$WORKSPACE" ]; then xcodebuild clean -workspace "$WORKSPACE" -scheme "$SCHEME" -configuration "$BUILD_CONFIG" -sdk "$SDK" || true @@ -252,6 +293,15 @@ build_ios_test_app() { xcodebuild clean -project "$PROJECT" -scheme "$SCHEME" -configuration "$BUILD_CONFIG" -sdk "$SDK" || true fi + # Also clean DerivedData for this specific project to remove cached HTML + log_step "Cleaning DerivedData for fresh build..." + DERIVED_DATA_PATH="$HOME/Library/Developer/Xcode/DerivedData" + if [ -d "$DERIVED_DATA_PATH" ]; then + # Find and remove DerivedData folders for this project + find "$DERIVED_DATA_PATH" -maxdepth 1 -type d -name "App-*" -exec rm -rf {} \; 2>/dev/null || true + log_info "DerivedData cleaned" + fi + # Build log_step "Building for $TARGET ($BUILD_CONFIG)..." if [ -n "$WORKSPACE" ]; then @@ -292,6 +342,17 @@ build_ios_test_app() { if [ -n "$APP_PATH" ]; then log_info "App built at: $APP_PATH" + + # Force copy canonical UI directly into built app bundle (ensures latest version) + log_step "Copying canonical UI into built app bundle..." + BUILT_APP_HTML="$APP_PATH/public/index.html" + if [ -f "$CANONICAL_UI" ] && [ -d "$(dirname "$BUILT_APP_HTML")" ]; then + if cp "$CANONICAL_UI" "$BUILT_APP_HTML"; then + log_info "✅ Canonical UI copied directly into app bundle" + else + log_warn "Failed to copy UI into app bundle (may use cached version)" + fi + fi log_info "" # Boot simulator if not already booted @@ -363,6 +424,19 @@ build_ios_test_app() { fi fi + # Uninstall existing app (if present) to ensure clean install + log_step "Uninstalling existing app (if present)..." + APP_BUNDLE_ID="com.timesafari.dailynotification.test" + if xcrun simctl uninstall "$SIMULATOR_ID" "$APP_BUNDLE_ID" 2>&1; then + log_info "Existing app uninstalled" + else + # App may not be installed, which is fine + log_info "No existing app to uninstall (or uninstall failed - continuing anyway)" + fi + + # Wait a moment after uninstall + sleep 1 + # Install the app log_step "Installing app on simulator..." if xcrun simctl install "$SIMULATOR_ID" "$APP_PATH" 2>&1; then @@ -383,9 +457,9 @@ build_ios_test_app() { sleep 2 # Method 1: Direct launch (capture output to check for errors) - # Note: Bundle ID is com.timesafari.dailynotification (not .test) + # Note: Bundle ID is com.timesafari.dailynotification.test log_info "Attempting to launch app..." - LAUNCH_OUTPUT=$(xcrun simctl launch "$SIMULATOR_ID" com.timesafari.dailynotification 2>&1) + LAUNCH_OUTPUT=$(xcrun simctl launch "$SIMULATOR_ID" "$APP_BUNDLE_ID" 2>&1) LAUNCH_EXIT_CODE=$? if [ $LAUNCH_EXIT_CODE -eq 0 ]; then @@ -410,19 +484,19 @@ build_ios_test_app() { 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 "") + RUNNING_APPS=$(xcrun simctl listapps "$SIMULATOR_ID" 2>/dev/null | grep -A 5 "$APP_BUNDLE_ID" || 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 "") + APP_STATE=$(xcrun simctl listapps "$SIMULATOR_ID" 2>/dev/null | grep -A 10 "$APP_BUNDLE_ID" | 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 + if xcrun simctl launch "$SIMULATOR_ID" "$APP_BUNDLE_ID" >/dev/null 2>&1; then LAUNCH_SUCCESS=true log_info "✅ App launched successfully on retry!" fi @@ -434,7 +508,7 @@ build_ios_test_app() { 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 + if xcrun simctl get_app_container "$SIMULATOR_ID" "$APP_BUNDLE_ID" >/dev/null 2>&1; then log_info "✅ Verified: App is installed and accessible" else log_warn "⚠️ Launch reported success but app verification failed" @@ -446,7 +520,7 @@ build_ios_test_app() { 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" + log_info " 3. Or run: xcrun simctl launch $SIMULATOR_ID $APP_BUNDLE_ID" if [ -n "$LAUNCH_ERROR" ]; then log_info "" log_info "Launch error details:" @@ -460,7 +534,7 @@ build_ios_test_app() { 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" + log_info " xcrun simctl launch booted com.timesafari.dailynotification.test" fi else log_warn "Could not find built app in DerivedData" diff --git a/test-apps/android-test-app/app/build.gradle b/test-apps/android-test-app/app/build.gradle index 9cb7f2c..4d9e2ca 100644 --- a/test-apps/android-test-app/app/build.gradle +++ b/test-apps/android-test-app/app/build.gradle @@ -52,6 +52,28 @@ dependencies { apply from: 'capacitor.build.gradle' +// Copy canonical UI from www/index.html before each build +task copyCanonicalUI(type: Copy) { + description = 'Copies canonical UI from www/index.html to test app' + group = 'build' + + def repoRoot = project.rootProject.projectDir.parentFile.parentFile + def canonicalUI = new File(repoRoot, 'www/index.html') + def targetUI = new File(projectDir, 'src/main/assets/public/index.html') + + if (canonicalUI.exists()) { + from canonicalUI + into targetUI.parentFile + rename { 'index.html' } + println "✅ Copied canonical UI from ${canonicalUI} to ${targetUI}" + } else { + println "⚠️ Canonical UI not found at ${canonicalUI}, skipping copy" + } +} + +// Make copy task run before build +preBuild.dependsOn copyCanonicalUI + try { def servicesJSON = file('google-services.json') if (servicesJSON.text) { diff --git a/test-apps/android-test-app/app/src/main/assets/public/index.html b/test-apps/android-test-app/app/src/main/assets/public/index.html index 48908a4..99d4950 100644 --- a/test-apps/android-test-app/app/src/main/assets/public/index.html +++ b/test-apps/android-test-app/app/src/main/assets/public/index.html @@ -17,627 +17,417 @@ color: white; } .container { - max-width: 800px; + max-width: 600px; margin: 0 auto; + text-align: center; } h1 { - text-align: center; margin-bottom: 30px; font-size: 2.5em; } - .section { - background: rgba(255, 255, 255, 0.1); - border-radius: 10px; - padding: 20px; - margin: 20px 0; - } - .section h2 { - margin-top: 0; - color: #ffd700; - } .button { background: rgba(255, 255, 255, 0.2); border: 2px solid rgba(255, 255, 255, 0.3); color: white; - padding: 12px 24px; - margin: 8px; - border-radius: 20px; + padding: 15px 30px; + margin: 10px; + border-radius: 25px; cursor: pointer; - font-size: 14px; + font-size: 16px; transition: all 0.3s ease; - display: inline-block; } .button:hover { background: rgba(255, 255, 255, 0.3); transform: translateY(-2px); } - .button:disabled { - opacity: 0.5; - cursor: not-allowed; - transform: none; - } .status { - margin-top: 15px; - padding: 15px; - background: rgba(0, 0, 0, 0.2); - border-radius: 8px; - font-family: monospace; - font-size: 12px; - white-space: pre-wrap; - max-height: 200px; - overflow-y: auto; - } - .success { color: #4CAF50; } - .error { color: #f44336; } - .warning { color: #ff9800; } - .info { color: #2196F3; } - .grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 15px; - margin: 15px 0; - } - .input-group { - margin: 10px 0; - } - .input-group label { - display: block; - margin-bottom: 5px; - font-weight: bold; - } - .input-group input, .input-group select { - width: 100%; - padding: 8px; - border-radius: 5px; - border: 1px solid rgba(255, 255, 255, 0.3); + margin-top: 30px; + padding: 20px; background: rgba(255, 255, 255, 0.1); - color: white; - } - .input-group input::placeholder { - color: rgba(255, 255, 255, 0.7); + border-radius: 10px; + font-family: monospace; }