ios-2 #2

Open
anomalist wants to merge 4 commits from ios-2 into master
26 changed files with 1761 additions and 31 deletions

12
package-lock.json generated
View File

@@ -100,6 +100,7 @@
"integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.26.2",
@@ -649,6 +650,7 @@
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.2.1.tgz",
"integrity": "sha512-urZwxa7hVE/BnA18oCFAdizXPse6fCKanQyEqpmz6cBJ2vObwMpyJDG5jBeoSsgocS9+Ax+9vb4ducWJn0y2qQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.1.0"
}
@@ -751,6 +753,7 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
@@ -774,6 +777,7 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
}
@@ -2925,6 +2929,7 @@
"integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==",
"dev": true,
"license": "BSD-2-Clause",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.62.0",
"@typescript-eslint/types": "5.62.0",
@@ -3128,6 +3133,7 @@
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -3543,6 +3549,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001688",
"electron-to-chromium": "^1.5.73",
@@ -4692,6 +4699,7 @@
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -6249,6 +6257,7 @@
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jest/core": "^29.7.0",
"@jest/types": "^29.6.3",
@@ -7115,6 +7124,7 @@
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"cssstyle": "^4.2.1",
"data-urls": "^5.0.0",
@@ -9574,6 +9584,7 @@
"integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"rollup": "dist/bin/rollup"
},
@@ -10526,6 +10537,7 @@
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"

View File

@@ -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
```

View File

@@ -2,47 +2,194 @@
**Author**: Matthew Raymer
**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

View File

@@ -0,0 +1,13 @@
App/build
App/Pods
App/output
App/App/public
DerivedData
xcuserdata
# Cordova plugins for Capacitor
capacitor-cordova-ios-plugins
# Generated Config files
App/App/capacitor.config.json
App/App/config.xml

View File

@@ -0,0 +1,408 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 48;
objects = {
/* Begin PBXBuildFile section */
2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; };
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */ = {isa = PBXBuildFile; fileRef = 50379B222058CBB4000EE86E /* capacitor.config.json */; };
504EC3081FED79650016851F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504EC3071FED79650016851F /* AppDelegate.swift */; };
504EC30D1FED79650016851F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30B1FED79650016851F /* Main.storyboard */; };
504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; };
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; };
50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; };
A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = "<group>"; };
50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = "<group>"; };
504EC3041FED79650016851F /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; };
504EC3071FED79650016851F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
504EC30C1FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
504EC30E1FED79650016851F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = "<group>"; };
AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; };
AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = "<group>"; };
FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
504EC3011FED79650016851F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
27E2DDA53C4D2A4D1A88CE4A /* Frameworks */ = {
isa = PBXGroup;
children = (
AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
504EC2FB1FED79650016851F = {
isa = PBXGroup;
children = (
504EC3061FED79650016851F /* App */,
504EC3051FED79650016851F /* Products */,
7F8756D8B27F46E3366F6CEA /* Pods */,
27E2DDA53C4D2A4D1A88CE4A /* Frameworks */,
);
sourceTree = "<group>";
};
504EC3051FED79650016851F /* Products */ = {
isa = PBXGroup;
children = (
504EC3041FED79650016851F /* App.app */,
);
name = Products;
sourceTree = "<group>";
};
504EC3061FED79650016851F /* App */ = {
isa = PBXGroup;
children = (
50379B222058CBB4000EE86E /* capacitor.config.json */,
504EC3071FED79650016851F /* AppDelegate.swift */,
504EC30B1FED79650016851F /* Main.storyboard */,
504EC30E1FED79650016851F /* Assets.xcassets */,
504EC3101FED79650016851F /* LaunchScreen.storyboard */,
504EC3131FED79650016851F /* Info.plist */,
2FAD9762203C412B000D30F8 /* config.xml */,
50B271D01FEDC1A000F3C39B /* public */,
);
path = App;
sourceTree = "<group>";
};
7F8756D8B27F46E3366F6CEA /* Pods */ = {
isa = PBXGroup;
children = (
FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */,
AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
504EC3031FED79650016851F /* App */ = {
isa = PBXNativeTarget;
buildConfigurationList = 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */;
buildPhases = (
6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */,
504EC3001FED79650016851F /* Sources */,
504EC3011FED79650016851F /* Frameworks */,
504EC3021FED79650016851F /* Resources */,
9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = App;
productName = App;
productReference = 504EC3041FED79650016851F /* App.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
504EC2FC1FED79650016851F /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 0920;
TargetAttributes = {
504EC3031FED79650016851F = {
CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1100;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = 504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */;
compatibilityVersion = "Xcode 8.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 504EC2FB1FED79650016851F;
packageReferences = (
);
productRefGroup = 504EC3051FED79650016851F /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
504EC3031FED79650016851F /* App */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
504EC3021FED79650016851F /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */,
50B271D11FEDC1A000F3C39B /* public in Resources */,
504EC30F1FED79650016851F /* Assets.xcassets in Resources */,
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */,
504EC30D1FED79650016851F /* Main.storyboard in Resources */,
2FAD9763203C412B000D30F8 /* config.xml in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-App-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-App/Pods-App-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
504EC3001FED79650016851F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
504EC30B1FED79650016851F /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
504EC30C1FED79650016851F /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
504EC3101FED79650016851F /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
504EC3111FED79650016851F /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
504EC3141FED79650016851F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
504EC3151FED79650016851F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
504EC3171FED79650016851F /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 1.0;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = com.timesafari.dailynotification.test;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
504EC3181FED79650016851F /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.timesafari.dailynotification.test;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */ = {
isa = XCConfigurationList;
buildConfigurations = (
504EC3141FED79650016851F /* Debug */,
504EC3151FED79650016851F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */ = {
isa = XCConfigurationList;
buildConfigurations = (
504EC3171FED79650016851F /* Debug */,
504EC3181FED79650016851F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 504EC2FC1FED79650016851F /* Project object */;
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:App.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,49 @@
import UIKit
import Capacitor
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
// Called when the app was launched with a url. Feel free to add additional processing here,
// but if you want the App API to support tracking app url opens, make sure to keep this call
return ApplicationDelegateProxy.shared.application(app, open: url, options: options)
}
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// Called when the app was launched with an activity, including Universal Links.
// Feel free to add additional processing here, but if you want the App API to support
// tracking app url opens, make sure to keep this call
return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

View File

@@ -0,0 +1,14 @@
{
"images" : [
{
"filename" : "AppIcon-512@2x.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "splash-2732x2732-2.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "splash-2732x2732-1.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "splash-2732x2732.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17132" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17105"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<imageView key="view" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Splash" id="snD-IY-ifK">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</imageView>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="Splash" width="1366" height="1366"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14111" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
</dependencies>
<scenes>
<!--Bridge View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="CAPBridgeViewController" customModule="Capacitor" sceneMemberID="viewController"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>Daily Notification Test</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.timesafari.dailynotification.fetch</string>
<string>com.timesafari.dailynotification.notify</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>background-fetch</string>
<string>background-processing</string>
<string>remote-notification</string>
</array>
<key>NSUserNotificationsUsageDescription</key>
<string>This app uses notifications to deliver daily updates and reminders.</string>
</dict>
</plist>

View File

@@ -0,0 +1,24 @@
require_relative '../../../../node_modules/@capacitor/ios/scripts/pods_helpers'
platform :ios, '13.0'
use_frameworks!
# workaround to avoid Xcode caching of Pods that requires
# Product -> Clean Build Folder after new Cordova plugins installed
# Requires CocoaPods 1.6 or newer
install! 'cocoapods', :disable_input_output_paths => true
def capacitor_pods
pod 'Capacitor', :path => '../../../../node_modules/@capacitor/ios'
pod 'CapacitorCordova', :path => '../../../../node_modules/@capacitor/ios'
pod 'DailyNotificationPlugin', :path => '../../node_modules/@timesafari/daily-notification-plugin/ios'
end
target 'App' do
capacitor_pods
# Add your Pods here
end
post_install do |installer|
assertDeploymentTarget(installer)
end

View File

@@ -45,7 +45,7 @@
},
"../..": {
"name": "@timesafari/daily-notification-plugin",
"version": "1.0.0",
"version": "1.0.11",
"license": "MIT",
"workspaces": [
"packages/*"

View File

@@ -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:../../",

View File

@@ -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

View File

@@ -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 };

View File

@@ -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<PermissionStatus> {
try {
const result = await (this.plugin as { checkPermissions: () => Promise<PermissionStatus> }).checkPermissions()
// Use checkPermissionStatus() which is implemented on both iOS and Android
const result = await (this.plugin as { checkPermissionStatus: () => Promise<any> }).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<void> {
try {
// Try requestNotificationPermissions first (iOS), fallback to requestPermissions
if (typeof (this.plugin as any).requestNotificationPermissions === 'function') {
await (this.plugin as { requestNotificationPermissions: () => Promise<void> }).requestNotificationPermissions()
} else if (typeof (this.plugin as any).requestPermissions === 'function') {
await (this.plugin as { requestPermissions: () => Promise<PermissionStatus> }).requestPermissions()
} else {
throw new Error('Neither requestNotificationPermissions nor requestPermissions is available')
}
} catch (error) {
logError(error, 'requestNotificationPermissions')
throw error
}
}
/**
* Open exact alarm settings
*/

View File

@@ -47,6 +47,13 @@
@click="checkSystemStatus"
:loading="isCheckingStatus"
/>
<ActionCard
icon="🔐"
title="Request Permissions"
description="Check and request notification permissions"
@click="checkAndRequestPermissions"
:loading="isRequestingPermissions"
/>
<ActionCard
icon="🔔"
title="View Notifications"
@@ -218,7 +225,8 @@ const checkSystemStatus = async (): Promise<void> => {
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<void> => {
console.log('📊 Plugin permissions:', permissions)
console.log('📊 Permissions details:')
console.log(' - notifications:', permissions.notifications)
console.log(' - notificationsEnabled:', (permissions as unknown as Record<string, unknown>).notificationsEnabled)
console.log(' - exactAlarmEnabled:', (permissions as unknown as Record<string, unknown>).exactAlarmEnabled)
console.log(' - wakeLockEnabled:', (permissions as unknown as Record<string, unknown>).wakeLockEnabled)
console.log(' - allPermissionsGranted:', (permissions as unknown as Record<string, unknown>).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<void> => {
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<void> => {
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<void> }).requestNotificationPermissions()
} else if (typeof (plugin as any).requestPermissions === 'function') {
await (plugin as { requestPermissions: () => Promise<any> }).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<void> => {
console.log('🔄 CLICK: Plugin Diagnostics - METHOD CALLED!')
console.log('🔄 FUNCTION START: runPluginDiagnostics called at', new Date().toISOString())

View File

@@ -92,7 +92,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
// Test if class can be cast to CapacitorPlugin.Type
if let pluginType = aClass as? CAPPlugin.Type {
// Try casting to CapacitorPlugin (which is CAPPlugin & CAPBridgedPlugin)
if let capacitorPluginType = pluginType as? (CAPPlugin & CAPBridgedPlugin).Type {
if pluginType is (CAPPlugin & CAPBridgedPlugin).Type {
NSLog("DNP-DEBUG: ✅ Can cast to (CAPPlugin & CAPBridgedPlugin).Type")
} else {
NSLog("DNP-DEBUG: ❌ Cannot cast to (CAPPlugin & CAPBridgedPlugin).Type")