fix(ios): configure method parameter parsing and improve build process
Fix configure() method to read parameters directly from CAPPluginCall instead of expecting nested options object, matching Android implementation. Improve build process to ensure canonical UI is always copied: - iOS build script: Copy www/index.html to test app before build - Android build.gradle: Add copyCanonicalUI task to run before build - Ensures test apps always use latest UI from www/index.html This fixes the issue where configure() was returning 'Configuration options required' error because it expected a nested options object when Capacitor passes parameters directly on the call object.
This commit is contained in:
@@ -80,24 +80,21 @@ public class DailyNotificationPlugin: CAPPlugin {
|
|||||||
* @param call Plugin call containing configuration parameters
|
* @param call Plugin call containing configuration parameters
|
||||||
*/
|
*/
|
||||||
@objc func configure(_ call: CAPPluginCall) {
|
@objc func configure(_ call: CAPPluginCall) {
|
||||||
guard let options = call.getObject("options") else {
|
// Capacitor passes the options object directly as call data
|
||||||
call.reject("Configuration options required")
|
// Read parameters directly from call (matching Android implementation)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
print("DNP-PLUGIN: Configuring plugin with new options")
|
print("DNP-PLUGIN: Configuring plugin with new options")
|
||||||
|
|
||||||
do {
|
do {
|
||||||
// Get configuration options
|
// Get configuration options directly from call (matching Android)
|
||||||
let dbPath = options["dbPath"] as? String
|
let dbPath = call.getString("dbPath")
|
||||||
let storageMode = options["storage"] as? String ?? "tiered"
|
let storageMode = call.getString("storage") ?? "tiered"
|
||||||
let ttlSeconds = options["ttlSeconds"] as? Int
|
let ttlSeconds = call.getInt("ttlSeconds")
|
||||||
let prefetchLeadMinutes = options["prefetchLeadMinutes"] as? Int
|
let prefetchLeadMinutes = call.getInt("prefetchLeadMinutes")
|
||||||
let maxNotificationsPerDay = options["maxNotificationsPerDay"] as? Int
|
let maxNotificationsPerDay = call.getInt("maxNotificationsPerDay")
|
||||||
let retentionDays = options["retentionDays"] as? Int
|
let retentionDays = call.getInt("retentionDays")
|
||||||
|
|
||||||
// Phase 1: Process activeDidIntegration configuration (deferred to Phase 3)
|
// 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)")
|
print("DNP-PLUGIN: activeDidIntegration config received (Phase 3 feature)")
|
||||||
// TODO: Implement activeDidIntegration configuration in Phase 3
|
// TODO: Implement activeDidIntegration configuration in Phase 3
|
||||||
}
|
}
|
||||||
@@ -1486,7 +1483,7 @@ public class DailyNotificationPlugin: CAPPlugin {
|
|||||||
var methods: [CAPPluginMethod] = []
|
var methods: [CAPPluginMethod] = []
|
||||||
|
|
||||||
// Core methods
|
// 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: "scheduleDailyNotification", returnType: CAPPluginReturnPromise))
|
||||||
methods.append(CAPPluginMethod(name: "getLastNotification", returnType: CAPPluginReturnPromise))
|
methods.append(CAPPluginMethod(name: "getLastNotification", returnType: CAPPluginReturnPromise))
|
||||||
methods.append(CAPPluginMethod(name: "cancelAllNotifications", returnType: CAPPluginReturnPromise))
|
methods.append(CAPPluginMethod(name: "cancelAllNotifications", returnType: CAPPluginReturnPromise))
|
||||||
|
|||||||
@@ -126,6 +126,9 @@ fi
|
|||||||
build_ios_test_app() {
|
build_ios_test_app() {
|
||||||
log_step "Building 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)
|
# Navigate to iOS App directory (where workspace is located)
|
||||||
IOS_APP_DIR="$TEST_APP_DIR/ios/App"
|
IOS_APP_DIR="$TEST_APP_DIR/ios/App"
|
||||||
if [ ! -d "$IOS_APP_DIR" ]; then
|
if [ ! -d "$IOS_APP_DIR" ]; then
|
||||||
@@ -162,6 +165,61 @@ build_ios_test_app() {
|
|||||||
log_warn "No Podfile found, skipping pod install"
|
log_warn "No Podfile found, skipping pod install"
|
||||||
fi
|
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
|
# Build TypeScript/JavaScript if package.json exists
|
||||||
if [ -f "package.json" ]; then
|
if [ -f "package.json" ]; then
|
||||||
log_step "Building web assets..."
|
log_step "Building web assets..."
|
||||||
@@ -172,32 +230,6 @@ build_ios_test_app() {
|
|||||||
fi
|
fi
|
||||||
log_info "Web assets built"
|
log_info "Web assets built"
|
||||||
fi
|
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
|
fi
|
||||||
|
|
||||||
# Determine SDK and destination
|
# Determine SDK and destination
|
||||||
@@ -244,7 +276,16 @@ build_ios_test_app() {
|
|||||||
ARCHIVE_PATH="build/ios-test-app-device.xcarchive"
|
ARCHIVE_PATH="build/ios-test-app-device.xcarchive"
|
||||||
fi
|
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..."
|
log_step "Cleaning build folder..."
|
||||||
if [ -n "$WORKSPACE" ]; then
|
if [ -n "$WORKSPACE" ]; then
|
||||||
xcodebuild clean -workspace "$WORKSPACE" -scheme "$SCHEME" -configuration "$BUILD_CONFIG" -sdk "$SDK" || true
|
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
|
xcodebuild clean -project "$PROJECT" -scheme "$SCHEME" -configuration "$BUILD_CONFIG" -sdk "$SDK" || true
|
||||||
fi
|
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
|
# Build
|
||||||
log_step "Building for $TARGET ($BUILD_CONFIG)..."
|
log_step "Building for $TARGET ($BUILD_CONFIG)..."
|
||||||
if [ -n "$WORKSPACE" ]; then
|
if [ -n "$WORKSPACE" ]; then
|
||||||
@@ -292,6 +342,17 @@ build_ios_test_app() {
|
|||||||
|
|
||||||
if [ -n "$APP_PATH" ]; then
|
if [ -n "$APP_PATH" ]; then
|
||||||
log_info "App built at: $APP_PATH"
|
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 ""
|
log_info ""
|
||||||
|
|
||||||
# Boot simulator if not already booted
|
# Boot simulator if not already booted
|
||||||
@@ -363,6 +424,19 @@ build_ios_test_app() {
|
|||||||
fi
|
fi
|
||||||
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
|
# Install the app
|
||||||
log_step "Installing app on simulator..."
|
log_step "Installing app on simulator..."
|
||||||
if xcrun simctl install "$SIMULATOR_ID" "$APP_PATH" 2>&1; then
|
if xcrun simctl install "$SIMULATOR_ID" "$APP_PATH" 2>&1; then
|
||||||
@@ -383,9 +457,9 @@ build_ios_test_app() {
|
|||||||
sleep 2
|
sleep 2
|
||||||
|
|
||||||
# Method 1: Direct launch (capture output to check for errors)
|
# 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..."
|
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=$?
|
LAUNCH_EXIT_CODE=$?
|
||||||
|
|
||||||
if [ $LAUNCH_EXIT_CODE -eq 0 ]; then
|
if [ $LAUNCH_EXIT_CODE -eq 0 ]; then
|
||||||
@@ -410,19 +484,19 @@ build_ios_test_app() {
|
|||||||
if [ "$LAUNCH_SUCCESS" = false ]; then
|
if [ "$LAUNCH_SUCCESS" = false ]; then
|
||||||
log_info "Checking if app is already running..."
|
log_info "Checking if app is already running..."
|
||||||
sleep 2
|
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
|
if [ -n "$RUNNING_APPS" ]; then
|
||||||
log_info "App appears to be installed. Trying to verify it's running..."
|
log_info "App appears to be installed. Trying to verify it's running..."
|
||||||
# Try to get app state
|
# 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
|
if [ -n "$APP_STATE" ]; then
|
||||||
log_info "App found in simulator. Attempting manual launch..."
|
log_info "App found in simulator. Attempting manual launch..."
|
||||||
# Try opening via Simulator app
|
# Try opening via Simulator app
|
||||||
open -a Simulator
|
open -a Simulator
|
||||||
sleep 1
|
sleep 1
|
||||||
# Try launch one more time
|
# 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
|
LAUNCH_SUCCESS=true
|
||||||
log_info "✅ App launched successfully on retry!"
|
log_info "✅ App launched successfully on retry!"
|
||||||
fi
|
fi
|
||||||
@@ -434,7 +508,7 @@ build_ios_test_app() {
|
|||||||
if [ "$LAUNCH_SUCCESS" = true ]; then
|
if [ "$LAUNCH_SUCCESS" = true ]; then
|
||||||
sleep 2
|
sleep 2
|
||||||
# Try to verify app is running by checking if we can get its container
|
# 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"
|
log_info "✅ Verified: App is installed and accessible"
|
||||||
else
|
else
|
||||||
log_warn "⚠️ Launch reported success but app verification failed"
|
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 "The app is installed. To launch manually:"
|
||||||
log_info " 1. Open Simulator app (if not already open)"
|
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 " 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
|
if [ -n "$LAUNCH_ERROR" ]; then
|
||||||
log_info ""
|
log_info ""
|
||||||
log_info "Launch error details:"
|
log_info "Launch error details:"
|
||||||
@@ -460,7 +534,7 @@ build_ios_test_app() {
|
|||||||
log_info ""
|
log_info ""
|
||||||
log_info "To run on simulator manually:"
|
log_info "To run on simulator manually:"
|
||||||
log_info " xcrun simctl install booted \"$APP_PATH\""
|
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
|
fi
|
||||||
else
|
else
|
||||||
log_warn "Could not find built app in DerivedData"
|
log_warn "Could not find built app in DerivedData"
|
||||||
|
|||||||
@@ -52,6 +52,28 @@ dependencies {
|
|||||||
|
|
||||||
apply from: 'capacitor.build.gradle'
|
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 {
|
try {
|
||||||
def servicesJSON = file('google-services.json')
|
def servicesJSON = file('google-services.json')
|
||||||
if (servicesJSON.text) {
|
if (servicesJSON.text) {
|
||||||
|
|||||||
@@ -17,627 +17,417 @@
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
.container {
|
.container {
|
||||||
max-width: 800px;
|
max-width: 600px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
font-size: 2.5em;
|
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 {
|
.button {
|
||||||
background: rgba(255, 255, 255, 0.2);
|
background: rgba(255, 255, 255, 0.2);
|
||||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||||
color: white;
|
color: white;
|
||||||
padding: 12px 24px;
|
padding: 15px 30px;
|
||||||
margin: 8px;
|
margin: 10px;
|
||||||
border-radius: 20px;
|
border-radius: 25px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 14px;
|
font-size: 16px;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
display: inline-block;
|
|
||||||
}
|
}
|
||||||
.button:hover {
|
.button:hover {
|
||||||
background: rgba(255, 255, 255, 0.3);
|
background: rgba(255, 255, 255, 0.3);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
.button:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
.status {
|
.status {
|
||||||
margin-top: 15px;
|
margin-top: 30px;
|
||||||
padding: 15px;
|
padding: 20px;
|
||||||
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);
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
background: rgba(255, 255, 255, 0.1);
|
||||||
color: white;
|
border-radius: 10px;
|
||||||
}
|
font-family: monospace;
|
||||||
.input-group input::placeholder {
|
|
||||||
color: rgba(255, 255, 255, 0.7);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>🔔 DailyNotification Plugin Test</h1>
|
<div id="statusCard" class="status" style="margin-bottom: 20px; font-size: 14px;">
|
||||||
|
<strong>Plugin Status</strong><br>
|
||||||
<!-- Plugin Status Section -->
|
<div style="margin-top: 10px;">
|
||||||
<div class="section">
|
⚙️ Plugin Settings: <span id="configStatus">Not configured</span><br>
|
||||||
<h2>📊 Plugin Status</h2>
|
🔌 Native Fetcher: <span id="fetcherStatus">Not configured</span><br>
|
||||||
<div class="grid">
|
🔔 Notifications: <span id="notificationPermStatus">Checking...</span><br>
|
||||||
<button class="button" onclick="checkPluginAvailability()">Check Availability</button>
|
⏰ Exact Alarms: <span id="exactAlarmPermStatus">Checking...</span><br>
|
||||||
<button class="button" onclick="getNotificationStatus()">Get Status</button>
|
📢 Channel: <span id="channelStatus">Checking...</span><br>
|
||||||
<button class="button" onclick="checkPermissions()">Check Permissions</button>
|
<div id="pluginStatusContent" style="margin-top: 8px;">
|
||||||
<button class="button" onclick="getBatteryStatus()">Battery Status</button>
|
Loading plugin status...
|
||||||
</div>
|
</div>
|
||||||
<div id="status" class="status">Ready to test...</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Permission Management Section -->
|
|
||||||
<div class="section">
|
|
||||||
<h2>🔐 Permission Management</h2>
|
|
||||||
<div class="grid">
|
|
||||||
<button class="button" onclick="requestPermissions()">Request Permissions</button>
|
|
||||||
<button class="button" onclick="requestExactAlarmPermission()">Request Exact Alarm</button>
|
|
||||||
<button class="button" onclick="openExactAlarmSettings()">Open Settings</button>
|
|
||||||
<button class="button" onclick="requestBatteryOptimizationExemption()">Battery Exemption</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Notification Scheduling Section -->
|
|
||||||
<div class="section">
|
|
||||||
<h2>⏰ Notification Scheduling</h2>
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="notificationUrl">Content URL:</label>
|
|
||||||
<input type="text" id="notificationUrl" placeholder="https://api.example.com/daily-content" value="https://api.example.com/daily-content">
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="notificationTime">Schedule Time:</label>
|
|
||||||
<input type="time" id="notificationTime" value="09:00">
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="notificationTitle">Title:</label>
|
|
||||||
<input type="text" id="notificationTitle" placeholder="Daily Notification" value="Daily Notification">
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="notificationBody">Body:</label>
|
|
||||||
<input type="text" id="notificationBody" placeholder="Your daily content is ready!" value="Your daily content is ready!">
|
|
||||||
</div>
|
|
||||||
<div class="grid">
|
|
||||||
<button class="button" onclick="scheduleNotification()">Schedule Notification</button>
|
|
||||||
<button class="button" onclick="cancelAllNotifications()">Cancel All</button>
|
|
||||||
<button class="button" onclick="getLastNotification()">Get Last</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Configuration Section -->
|
|
||||||
<div class="section">
|
|
||||||
<h2>⚙️ Plugin Configuration</h2>
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="configUrl">Fetch URL:</label>
|
|
||||||
<input type="text" id="configUrl" placeholder="https://api.example.com/content" value="https://api.example.com/content">
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="configTime">Schedule Time:</label>
|
|
||||||
<input type="time" id="configTime" value="09:00">
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="configRetryCount">Retry Count:</label>
|
|
||||||
<input type="number" id="configRetryCount" value="3" min="0" max="10">
|
|
||||||
</div>
|
|
||||||
<div class="grid">
|
|
||||||
<button class="button" onclick="configurePlugin()">Configure Plugin</button>
|
<button class="button" onclick="configurePlugin()">Configure Plugin</button>
|
||||||
<button class="button" onclick="updateSettings()">Update Settings</button>
|
<button class="button" onclick="requestPermissions()">Request Permissions</button>
|
||||||
</div>
|
<button class="button" onclick="testNotification()">Test Notification</button>
|
||||||
</div>
|
<button class="button" onclick="checkComprehensiveStatus()">Full System Status</button>
|
||||||
|
|
||||||
<!-- Advanced Features Section -->
|
<div id="status" class="status">
|
||||||
<div class="section">
|
Ready to test...
|
||||||
<h2>🚀 Advanced Features</h2>
|
|
||||||
<div class="grid">
|
|
||||||
<button class="button" onclick="getExactAlarmStatus()">Exact Alarm Status</button>
|
|
||||||
<button class="button" onclick="getRebootRecoveryStatus()">Reboot Recovery</button>
|
|
||||||
<button class="button" onclick="getRollingWindowStats()">Rolling Window</button>
|
|
||||||
<button class="button" onclick="maintainRollingWindow()">Maintain Window</button>
|
|
||||||
<button class="button" onclick="getContentCache()">Content Cache</button>
|
|
||||||
<button class="button" onclick="clearContentCache()">Clear Cache</button>
|
|
||||||
<button class="button" onclick="getContentHistory()">Content History</button>
|
|
||||||
<button class="button" onclick="getDualScheduleStatus()">Dual Schedule</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
console.log('🔔 DailyNotification Plugin Test Interface Loading...');
|
console.log('Script loading...');
|
||||||
|
console.log('JavaScript is working!');
|
||||||
|
|
||||||
// Global variables
|
// Use real DailyNotification plugin
|
||||||
let plugin = null;
|
console.log('Using real DailyNotification plugin...');
|
||||||
let isPluginAvailable = false;
|
window.DailyNotification = window.Capacitor.Plugins.DailyNotification;
|
||||||
|
|
||||||
// Initialize plugin on page load
|
// Define functions immediately and attach to window
|
||||||
document.addEventListener('DOMContentLoaded', async function() {
|
|
||||||
console.log('📱 DOM loaded, initializing plugin...');
|
function configurePlugin() {
|
||||||
await initializePlugin();
|
console.log('configurePlugin called');
|
||||||
});
|
const status = document.getElementById('status');
|
||||||
|
const configStatus = document.getElementById('configStatus');
|
||||||
|
const fetcherStatus = document.getElementById('fetcherStatus');
|
||||||
|
|
||||||
|
status.innerHTML = 'Configuring plugin...';
|
||||||
|
status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background
|
||||||
|
|
||||||
|
// Update top status to show configuring
|
||||||
|
configStatus.innerHTML = '⏳ Configuring...';
|
||||||
|
fetcherStatus.innerHTML = '⏳ Waiting...';
|
||||||
|
|
||||||
// Initialize the real DailyNotification plugin
|
|
||||||
async function initializePlugin() {
|
|
||||||
try {
|
try {
|
||||||
// Try to access the real plugin through Capacitor
|
if (!window.DailyNotification) {
|
||||||
if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.DailyNotification) {
|
status.innerHTML = 'DailyNotification plugin not available';
|
||||||
plugin = window.Capacitor.Plugins.DailyNotification;
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
isPluginAvailable = true;
|
configStatus.innerHTML = '❌ Plugin unavailable';
|
||||||
console.log('✅ Real DailyNotification plugin found!');
|
fetcherStatus.innerHTML = '❌ Plugin unavailable';
|
||||||
updateStatus('success', '✅ Real DailyNotification plugin loaded successfully!');
|
return;
|
||||||
} else {
|
|
||||||
// Fallback to mock for development
|
|
||||||
console.log('⚠️ Real plugin not available, using mock for development');
|
|
||||||
plugin = createMockPlugin();
|
|
||||||
isPluginAvailable = false;
|
|
||||||
updateStatus('warning', '⚠️ Using mock plugin (real plugin not available)');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Configure plugin settings
|
||||||
|
window.DailyNotification.configure({
|
||||||
|
storage: 'tiered',
|
||||||
|
ttlSeconds: 86400,
|
||||||
|
prefetchLeadMinutes: 60,
|
||||||
|
maxNotificationsPerDay: 3,
|
||||||
|
retentionDays: 7
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.log('Plugin settings configured, now configuring native fetcher...');
|
||||||
|
// Update top status
|
||||||
|
configStatus.innerHTML = '✅ Configured';
|
||||||
|
|
||||||
|
// Configure native fetcher with demo credentials
|
||||||
|
// Note: DemoNativeFetcher uses hardcoded mock data, so this is optional
|
||||||
|
// but demonstrates the API. In production, this would be real credentials.
|
||||||
|
return window.DailyNotification.configureNativeFetcher({
|
||||||
|
apiBaseUrl: 'http://10.0.2.2:3000', // Android emulator → host localhost
|
||||||
|
activeDid: 'did:ethr:0xDEMO1234567890', // Demo DID
|
||||||
|
jwtSecret: 'demo-jwt-secret-for-development-testing'
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// Update top status
|
||||||
|
fetcherStatus.innerHTML = '✅ Configured';
|
||||||
|
|
||||||
|
// Update bottom status for user feedback
|
||||||
|
status.innerHTML = 'Plugin configured successfully!';
|
||||||
|
status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
// Update top status with error
|
||||||
|
if (configStatus.innerHTML.includes('Configuring')) {
|
||||||
|
configStatus.innerHTML = '❌ Failed';
|
||||||
|
}
|
||||||
|
if (fetcherStatus.innerHTML.includes('Waiting') || fetcherStatus.innerHTML.includes('Configuring')) {
|
||||||
|
fetcherStatus.innerHTML = '❌ Failed';
|
||||||
|
}
|
||||||
|
|
||||||
|
status.innerHTML = `Configuration failed: ${error.message}`;
|
||||||
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Plugin initialization failed:', error);
|
configStatus.innerHTML = '❌ Error';
|
||||||
updateStatus('error', `❌ Plugin initialization failed: ${error.message}`);
|
fetcherStatus.innerHTML = '❌ Error';
|
||||||
|
status.innerHTML = `Configuration failed: ${error.message}`;
|
||||||
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create mock plugin for development/testing
|
function loadPluginStatus() {
|
||||||
function createMockPlugin() {
|
console.log('loadPluginStatus called');
|
||||||
return {
|
const pluginStatusContent = document.getElementById('pluginStatusContent');
|
||||||
configure: async (options) => {
|
const statusCard = document.getElementById('statusCard');
|
||||||
console.log('Mock configure called with:', options);
|
|
||||||
return Promise.resolve();
|
try {
|
||||||
},
|
if (!window.DailyNotification) {
|
||||||
getNotificationStatus: async () => {
|
pluginStatusContent.innerHTML = '❌ DailyNotification plugin not available';
|
||||||
return Promise.resolve({
|
statusCard.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
isEnabled: true,
|
return;
|
||||||
isScheduled: true,
|
|
||||||
lastNotificationTime: Date.now() - 86400000,
|
|
||||||
nextNotificationTime: Date.now() + 3600000,
|
|
||||||
pending: 1,
|
|
||||||
settings: { url: 'https://api.example.com/content', time: '09:00' },
|
|
||||||
error: null
|
|
||||||
});
|
|
||||||
},
|
|
||||||
checkPermissions: async () => {
|
|
||||||
return Promise.resolve({
|
|
||||||
notifications: 'granted',
|
|
||||||
backgroundRefresh: 'granted',
|
|
||||||
alert: true,
|
|
||||||
badge: true,
|
|
||||||
sound: true
|
|
||||||
});
|
|
||||||
},
|
|
||||||
requestPermissions: async () => {
|
|
||||||
return Promise.resolve({
|
|
||||||
notifications: 'granted',
|
|
||||||
backgroundRefresh: 'granted',
|
|
||||||
alert: true,
|
|
||||||
badge: true,
|
|
||||||
sound: true
|
|
||||||
});
|
|
||||||
},
|
|
||||||
scheduleDailyNotification: async (options) => {
|
|
||||||
console.log('Mock scheduleDailyNotification called with:', options);
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
cancelAllNotifications: async () => {
|
|
||||||
console.log('Mock cancelAllNotifications called');
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
getLastNotification: async () => {
|
|
||||||
return Promise.resolve({
|
|
||||||
id: 'mock-123',
|
|
||||||
title: 'Mock Notification',
|
|
||||||
body: 'This is a mock notification',
|
|
||||||
timestamp: Date.now() - 3600000,
|
|
||||||
url: 'https://example.com'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getBatteryStatus: async () => {
|
|
||||||
return Promise.resolve({
|
|
||||||
level: 85,
|
|
||||||
isCharging: false,
|
|
||||||
powerState: 1,
|
|
||||||
isOptimizationExempt: false
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getExactAlarmStatus: async () => {
|
|
||||||
return Promise.resolve({
|
|
||||||
supported: true,
|
|
||||||
enabled: true,
|
|
||||||
canSchedule: true,
|
|
||||||
fallbackWindow: '±15 minutes'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
requestExactAlarmPermission: async () => {
|
|
||||||
console.log('Mock requestExactAlarmPermission called');
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
openExactAlarmSettings: async () => {
|
|
||||||
console.log('Mock openExactAlarmSettings called');
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
requestBatteryOptimizationExemption: async () => {
|
|
||||||
console.log('Mock requestBatteryOptimizationExemption called');
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
getRebootRecoveryStatus: async () => {
|
|
||||||
return Promise.resolve({
|
|
||||||
inProgress: false,
|
|
||||||
lastRecoveryTime: Date.now() - 86400000,
|
|
||||||
timeSinceLastRecovery: 86400000,
|
|
||||||
recoveryNeeded: false
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getRollingWindowStats: async () => {
|
|
||||||
return Promise.resolve({
|
|
||||||
stats: 'Window: 7 days, Notifications: 5, Success rate: 100%',
|
|
||||||
maintenanceNeeded: false,
|
|
||||||
timeUntilNextMaintenance: 3600000
|
|
||||||
});
|
|
||||||
},
|
|
||||||
maintainRollingWindow: async () => {
|
|
||||||
console.log('Mock maintainRollingWindow called');
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
getContentCache: async () => {
|
|
||||||
return Promise.resolve({
|
|
||||||
'cache-key-1': { content: 'Mock cached content', timestamp: Date.now() },
|
|
||||||
'cache-key-2': { content: 'Another mock item', timestamp: Date.now() - 3600000 }
|
|
||||||
});
|
|
||||||
},
|
|
||||||
clearContentCache: async () => {
|
|
||||||
console.log('Mock clearContentCache called');
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
getContentHistory: async () => {
|
|
||||||
return Promise.resolve([
|
|
||||||
{ id: '1', timestamp: Date.now() - 86400000, success: true, content: 'Mock content 1' },
|
|
||||||
{ id: '2', timestamp: Date.now() - 172800000, success: true, content: 'Mock content 2' }
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
getDualScheduleStatus: async () => {
|
|
||||||
return Promise.resolve({
|
|
||||||
isActive: true,
|
|
||||||
contentSchedule: { nextRun: Date.now() + 3600000, isEnabled: true },
|
|
||||||
userSchedule: { nextRun: Date.now() + 7200000, isEnabled: true },
|
|
||||||
lastContentFetch: Date.now() - 3600000,
|
|
||||||
lastUserNotification: Date.now() - 7200000
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
window.DailyNotification.getNotificationStatus()
|
||||||
|
.then(result => {
|
||||||
|
const nextTime = result.nextNotificationTime ? new Date(result.nextNotificationTime).toLocaleString() : 'None scheduled';
|
||||||
|
const hasSchedules = result.isEnabled || (result.pending && result.pending > 0);
|
||||||
|
const statusIcon = hasSchedules ? '✅' : '⏸️';
|
||||||
|
pluginStatusContent.innerHTML = `${statusIcon} Active Schedules: ${hasSchedules ? 'Yes' : 'No'}<br>
|
||||||
|
📅 Next Notification: ${nextTime}<br>
|
||||||
|
⏳ Pending: ${result.pending || 0}`;
|
||||||
|
statusCard.style.background = hasSchedules ?
|
||||||
|
'rgba(0, 255, 0, 0.15)' : 'rgba(255, 255, 255, 0.1)'; // Green if active, light gray if none
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
pluginStatusContent.innerHTML = `⚠️ Status check failed: ${error.message}`;
|
||||||
|
statusCard.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
pluginStatusContent.innerHTML = `⚠️ Status check failed: ${error.message}`;
|
||||||
|
statusCard.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility function to update status display
|
// Notification test functions
|
||||||
function updateStatus(type, message) {
|
function testNotification() {
|
||||||
const statusEl = document.getElementById('status');
|
console.log('testNotification called');
|
||||||
statusEl.className = `status ${type}`;
|
|
||||||
statusEl.textContent = message;
|
// Quick sanity check - test plugin availability
|
||||||
console.log(`[${type.toUpperCase()}] ${message}`);
|
if (window.Capacitor && window.Capacitor.isPluginAvailable) {
|
||||||
}
|
const isAvailable = window.Capacitor.isPluginAvailable('DailyNotification');
|
||||||
|
console.log('is plugin available?', isAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = document.getElementById('status');
|
||||||
|
status.innerHTML = 'Testing plugin connection...';
|
||||||
|
status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background
|
||||||
|
|
||||||
// Plugin availability check
|
|
||||||
async function checkPluginAvailability() {
|
|
||||||
updateStatus('info', '🔍 Checking plugin availability...');
|
|
||||||
try {
|
try {
|
||||||
if (plugin) {
|
if (!window.DailyNotification) {
|
||||||
updateStatus('success', `✅ Plugin available: ${isPluginAvailable ? 'Real plugin' : 'Mock plugin'}`);
|
status.innerHTML = 'DailyNotification plugin not available';
|
||||||
} else {
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
updateStatus('error', '❌ Plugin not available');
|
return;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Availability check failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get notification status
|
// Test the notification method directly
|
||||||
async function getNotificationStatus() {
|
console.log('Testing notification scheduling...');
|
||||||
updateStatus('info', '📊 Getting notification status...');
|
|
||||||
try {
|
|
||||||
const status = await plugin.getNotificationStatus();
|
|
||||||
updateStatus('success', `📊 Status: ${JSON.stringify(status, null, 2)}`);
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Status check failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check permissions
|
|
||||||
async function checkPermissions() {
|
|
||||||
updateStatus('info', '🔐 Checking permissions...');
|
|
||||||
try {
|
|
||||||
const permissions = await plugin.checkPermissions();
|
|
||||||
updateStatus('success', `🔐 Permissions: ${JSON.stringify(permissions, null, 2)}`);
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Permission check failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request permissions
|
|
||||||
async function requestPermissions() {
|
|
||||||
updateStatus('info', '🔐 Requesting permissions...');
|
|
||||||
try {
|
|
||||||
const result = await plugin.requestPermissions();
|
|
||||||
updateStatus('success', `🔐 Permission result: ${JSON.stringify(result, null, 2)}`);
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Permission request failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get battery status
|
|
||||||
async function getBatteryStatus() {
|
|
||||||
updateStatus('info', '🔋 Getting battery status...');
|
|
||||||
try {
|
|
||||||
const battery = await plugin.getBatteryStatus();
|
|
||||||
updateStatus('success', `🔋 Battery: ${JSON.stringify(battery, null, 2)}`);
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Battery check failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schedule notification
|
|
||||||
async function scheduleNotification() {
|
|
||||||
updateStatus('info', '⏰ Scheduling notification...');
|
|
||||||
try {
|
|
||||||
const timeInput = document.getElementById('notificationTime').value;
|
|
||||||
const [hours, minutes] = timeInput.split(':');
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const scheduledTime = new Date();
|
const notificationTime = new Date(now.getTime() + 240000); // 4 minutes from now
|
||||||
scheduledTime.setHours(parseInt(hours), parseInt(minutes), 0, 0);
|
const prefetchTime = new Date(now.getTime() + 120000); // 2 minutes from now (2 min before notification)
|
||||||
|
const notificationTimeString = notificationTime.getHours().toString().padStart(2, '0') + ':' +
|
||||||
// If scheduled time is in the past, schedule for tomorrow
|
notificationTime.getMinutes().toString().padStart(2, '0');
|
||||||
if (scheduledTime <= now) {
|
|
||||||
scheduledTime.setDate(scheduledTime.getDate() + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate prefetch time (2 minutes before notification)
|
|
||||||
const prefetchTime = new Date(scheduledTime.getTime() - 120000); // 2 minutes
|
|
||||||
const prefetchTimeReadable = prefetchTime.toLocaleTimeString();
|
|
||||||
const notificationTimeReadable = scheduledTime.toLocaleTimeString();
|
|
||||||
const prefetchTimeString = prefetchTime.getHours().toString().padStart(2, '0') + ':' +
|
const prefetchTimeString = prefetchTime.getHours().toString().padStart(2, '0') + ':' +
|
||||||
prefetchTime.getMinutes().toString().padStart(2, '0');
|
prefetchTime.getMinutes().toString().padStart(2, '0');
|
||||||
const notificationTimeString = scheduledTime.getHours().toString().padStart(2, '0') + ':' +
|
|
||||||
scheduledTime.getMinutes().toString().padStart(2, '0');
|
|
||||||
|
|
||||||
const options = {
|
window.DailyNotification.scheduleDailyNotification({
|
||||||
url: document.getElementById('notificationUrl').value,
|
time: notificationTimeString,
|
||||||
time: timeInput,
|
title: 'Test Notification',
|
||||||
title: document.getElementById('notificationTitle').value,
|
body: 'This is a test notification from the DailyNotification plugin!',
|
||||||
body: document.getElementById('notificationBody').value,
|
|
||||||
sound: true,
|
sound: true,
|
||||||
priority: 'high'
|
priority: 'high'
|
||||||
};
|
})
|
||||||
await plugin.scheduleDailyNotification(options);
|
.then(() => {
|
||||||
updateStatus('success', `✅ Notification scheduled!<br>` +
|
const prefetchTimeReadable = prefetchTime.toLocaleTimeString();
|
||||||
`📥 Prefetch: ${prefetchTimeReadable} (${prefetchTimeString})<br>` +
|
const notificationTimeReadable = notificationTime.toLocaleTimeString();
|
||||||
`🔔 Notification: ${notificationTimeReadable} (${notificationTimeString})`);
|
status.innerHTML = '✅ Notification scheduled!<br>' +
|
||||||
|
'📥 Prefetch: ' + prefetchTimeReadable + ' (' + prefetchTimeString + ')<br>' +
|
||||||
|
'🔔 Notification: ' + notificationTimeReadable + ' (' + notificationTimeString + ')';
|
||||||
|
status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background
|
||||||
|
// Refresh plugin status display
|
||||||
|
setTimeout(() => loadPluginStatus(), 500);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
// Check if this is an exact alarm permission error
|
||||||
|
if (error.code === 'EXACT_ALARM_PERMISSION_REQUIRED' ||
|
||||||
|
error.message.includes('Exact alarm permission') ||
|
||||||
|
error.message.includes('Alarms & reminders')) {
|
||||||
|
status.innerHTML = '⚠️ Exact Alarm Permission Required<br><br>' +
|
||||||
|
'Settings opened automatically.<br>' +
|
||||||
|
'Please enable "Allow exact alarms" and return to try again.';
|
||||||
|
status.style.background = 'rgba(255, 165, 0, 0.3)'; // Orange background
|
||||||
|
} else {
|
||||||
|
status.innerHTML = `❌ Notification failed: ${error.message}`;
|
||||||
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
updateStatus('error', `❌ Scheduling failed: ${error.message}`);
|
status.innerHTML = `Notification test failed: ${error.message}`;
|
||||||
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel all notifications
|
|
||||||
async function cancelAllNotifications() {
|
// Permission management functions
|
||||||
updateStatus('info', '❌ Cancelling all notifications...');
|
function requestPermissions() {
|
||||||
|
console.log('requestPermissions called');
|
||||||
|
const status = document.getElementById('status');
|
||||||
|
status.innerHTML = 'Requesting permissions...';
|
||||||
|
status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await plugin.cancelAllNotifications();
|
if (!window.DailyNotification) {
|
||||||
updateStatus('success', '❌ All notifications cancelled');
|
status.innerHTML = 'DailyNotification plugin not available';
|
||||||
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.DailyNotification.requestNotificationPermissions()
|
||||||
|
.then(() => {
|
||||||
|
status.innerHTML = 'Permission request completed! Check your device settings if needed.';
|
||||||
|
status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background
|
||||||
|
|
||||||
|
// Refresh permission and channel status display after request
|
||||||
|
setTimeout(() => {
|
||||||
|
loadPermissionStatus();
|
||||||
|
loadChannelStatus();
|
||||||
|
}, 1000);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
status.innerHTML = `Permission request failed: ${error.message}`;
|
||||||
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
updateStatus('error', `❌ Cancel failed: ${error.message}`);
|
status.innerHTML = `Permission request failed: ${error.message}`;
|
||||||
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get last notification
|
function loadChannelStatus() {
|
||||||
async function getLastNotification() {
|
const channelStatus = document.getElementById('channelStatus');
|
||||||
updateStatus('info', '📱 Getting last notification...');
|
|
||||||
try {
|
try {
|
||||||
const notification = await plugin.getLastNotification();
|
if (!window.DailyNotification) {
|
||||||
updateStatus('success', `📱 Last notification: ${JSON.stringify(notification, null, 2)}`);
|
channelStatus.innerHTML = '❌ Plugin unavailable';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.DailyNotification.isChannelEnabled()
|
||||||
|
.then(result => {
|
||||||
|
const importanceText = getImportanceText(result.importance);
|
||||||
|
if (result.enabled) {
|
||||||
|
channelStatus.innerHTML = `✅ Enabled (${importanceText})`;
|
||||||
|
} else {
|
||||||
|
channelStatus.innerHTML = `❌ Disabled (${importanceText})`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
channelStatus.innerHTML = '⚠️ Error';
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
updateStatus('error', `❌ Get last notification failed: ${error.message}`);
|
channelStatus.innerHTML = '⚠️ Error';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure plugin
|
function checkComprehensiveStatus() {
|
||||||
async function configurePlugin() {
|
const status = document.getElementById('status');
|
||||||
updateStatus('info', '⚙️ Configuring plugin...');
|
status.innerHTML = 'Checking comprehensive status...';
|
||||||
|
status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const config = {
|
if (!window.DailyNotification) {
|
||||||
fetchUrl: document.getElementById('configUrl').value,
|
status.innerHTML = 'DailyNotification plugin not available';
|
||||||
scheduleTime: document.getElementById('configTime').value,
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
retryCount: parseInt(document.getElementById('configRetryCount').value),
|
return;
|
||||||
enableNotifications: true,
|
}
|
||||||
offlineFallback: true
|
|
||||||
};
|
window.DailyNotification.checkStatus()
|
||||||
await plugin.configure(config);
|
.then(result => {
|
||||||
updateStatus('success', `⚙️ Plugin configured: ${JSON.stringify(config, null, 2)}`);
|
const canSchedule = result.canScheduleNow;
|
||||||
|
const issues = [];
|
||||||
|
|
||||||
|
if (!result.postNotificationsGranted) {
|
||||||
|
issues.push('POST_NOTIFICATIONS permission');
|
||||||
|
}
|
||||||
|
if (!result.channelEnabled) {
|
||||||
|
issues.push('notification channel disabled');
|
||||||
|
}
|
||||||
|
if (!result.exactAlarmsGranted) {
|
||||||
|
issues.push('exact alarm permission');
|
||||||
|
}
|
||||||
|
|
||||||
|
let statusText = `Status: ${canSchedule ? 'Ready to schedule' : 'Issues found'}`;
|
||||||
|
if (issues.length > 0) {
|
||||||
|
statusText += `\nIssues: ${issues.join(', ')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
statusText += `\nChannel: ${getImportanceText(result.channelImportance)}`;
|
||||||
|
statusText += `\nChannel ID: ${result.channelId}`;
|
||||||
|
|
||||||
|
status.innerHTML = statusText;
|
||||||
|
status.style.background = canSchedule ? 'rgba(0, 255, 0, 0.3)' : 'rgba(255, 0, 0, 0.3)';
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
status.innerHTML = `Status check failed: ${error.message}`;
|
||||||
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
updateStatus('error', `❌ Configuration failed: ${error.message}`);
|
status.innerHTML = `Status check failed: ${error.message}`;
|
||||||
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update settings
|
function getImportanceText(importance) {
|
||||||
async function updateSettings() {
|
switch (importance) {
|
||||||
updateStatus('info', '⚙️ Updating settings...');
|
case 0: return 'None (blocked)';
|
||||||
|
case 1: return 'Min';
|
||||||
|
case 2: return 'Low';
|
||||||
|
case 3: return 'Default';
|
||||||
|
case 4: return 'High';
|
||||||
|
case 5: return 'Max';
|
||||||
|
default: return `Unknown (${importance})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach to window object
|
||||||
|
window.configurePlugin = configurePlugin;
|
||||||
|
window.testNotification = testNotification;
|
||||||
|
window.requestPermissions = requestPermissions;
|
||||||
|
window.checkComprehensiveStatus = checkComprehensiveStatus;
|
||||||
|
|
||||||
|
function loadPermissionStatus() {
|
||||||
|
const notificationPermStatus = document.getElementById('notificationPermStatus');
|
||||||
|
const exactAlarmPermStatus = document.getElementById('exactAlarmPermStatus');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const settings = {
|
if (!window.DailyNotification) {
|
||||||
url: document.getElementById('configUrl').value,
|
notificationPermStatus.innerHTML = '❌ Plugin unavailable';
|
||||||
time: document.getElementById('configTime').value,
|
exactAlarmPermStatus.innerHTML = '❌ Plugin unavailable';
|
||||||
retryCount: parseInt(document.getElementById('configRetryCount').value),
|
return;
|
||||||
sound: true,
|
}
|
||||||
priority: 'high'
|
|
||||||
};
|
window.DailyNotification.checkPermissionStatus()
|
||||||
await plugin.updateSettings(settings);
|
.then(result => {
|
||||||
updateStatus('success', `⚙️ Settings updated: ${JSON.stringify(settings, null, 2)}`);
|
notificationPermStatus.innerHTML = result.notificationsEnabled ? '✅ Granted' : '❌ Not granted';
|
||||||
|
exactAlarmPermStatus.innerHTML = result.exactAlarmEnabled ? '✅ Granted' : '❌ Not granted';
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
notificationPermStatus.innerHTML = '⚠️ Error';
|
||||||
|
exactAlarmPermStatus.innerHTML = '⚠️ Error';
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
updateStatus('error', `❌ Settings update failed: ${error.message}`);
|
notificationPermStatus.innerHTML = '⚠️ Error';
|
||||||
|
exactAlarmPermStatus.innerHTML = '⚠️ Error';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get exact alarm status
|
// Load plugin status automatically on page load
|
||||||
async function getExactAlarmStatus() {
|
window.addEventListener('load', () => {
|
||||||
updateStatus('info', '⏰ Getting exact alarm status...');
|
console.log('Page loaded, loading plugin status...');
|
||||||
try {
|
// Small delay to ensure Capacitor is ready
|
||||||
const status = await plugin.getExactAlarmStatus();
|
setTimeout(() => {
|
||||||
updateStatus('success', `⏰ Exact alarm status: ${JSON.stringify(status, null, 2)}`);
|
loadPluginStatus();
|
||||||
} catch (error) {
|
loadPermissionStatus();
|
||||||
updateStatus('error', `❌ Exact alarm check failed: ${error.message}`);
|
loadChannelStatus();
|
||||||
}
|
}, 500);
|
||||||
}
|
});
|
||||||
|
|
||||||
// Request exact alarm permission
|
|
||||||
async function requestExactAlarmPermission() {
|
|
||||||
updateStatus('info', '⏰ Requesting exact alarm permission...');
|
|
||||||
try {
|
|
||||||
await plugin.requestExactAlarmPermission();
|
|
||||||
updateStatus('success', '⏰ Exact alarm permission requested');
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Exact alarm permission request failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open exact alarm settings
|
|
||||||
async function openExactAlarmSettings() {
|
|
||||||
updateStatus('info', '⚙️ Opening exact alarm settings...');
|
|
||||||
try {
|
|
||||||
await plugin.openExactAlarmSettings();
|
|
||||||
updateStatus('success', '⚙️ Exact alarm settings opened');
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Open settings failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request battery optimization exemption
|
console.log('Functions attached to window:', {
|
||||||
async function requestBatteryOptimizationExemption() {
|
configurePlugin: typeof window.configurePlugin,
|
||||||
updateStatus('info', '🔋 Requesting battery optimization exemption...');
|
testNotification: typeof window.testNotification
|
||||||
try {
|
});
|
||||||
await plugin.requestBatteryOptimizationExemption();
|
|
||||||
updateStatus('success', '🔋 Battery optimization exemption requested');
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Battery exemption request failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get reboot recovery status
|
|
||||||
async function getRebootRecoveryStatus() {
|
|
||||||
updateStatus('info', '🔄 Getting reboot recovery status...');
|
|
||||||
try {
|
|
||||||
const status = await plugin.getRebootRecoveryStatus();
|
|
||||||
updateStatus('success', `🔄 Reboot recovery status: ${JSON.stringify(status, null, 2)}`);
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Reboot recovery check failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get rolling window stats
|
|
||||||
async function getRollingWindowStats() {
|
|
||||||
updateStatus('info', '📊 Getting rolling window stats...');
|
|
||||||
try {
|
|
||||||
const stats = await plugin.getRollingWindowStats();
|
|
||||||
updateStatus('success', `📊 Rolling window stats: ${JSON.stringify(stats, null, 2)}`);
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Rolling window stats failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maintain rolling window
|
|
||||||
async function maintainRollingWindow() {
|
|
||||||
updateStatus('info', '🔧 Maintaining rolling window...');
|
|
||||||
try {
|
|
||||||
await plugin.maintainRollingWindow();
|
|
||||||
updateStatus('success', '🔧 Rolling window maintenance completed');
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Rolling window maintenance failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get content cache
|
|
||||||
async function getContentCache() {
|
|
||||||
updateStatus('info', '💾 Getting content cache...');
|
|
||||||
try {
|
|
||||||
const cache = await plugin.getContentCache();
|
|
||||||
updateStatus('success', `💾 Content cache: ${JSON.stringify(cache, null, 2)}`);
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Content cache check failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear content cache
|
|
||||||
async function clearContentCache() {
|
|
||||||
updateStatus('info', '🗑️ Clearing content cache...');
|
|
||||||
try {
|
|
||||||
await plugin.clearContentCache();
|
|
||||||
updateStatus('success', '🗑️ Content cache cleared');
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Clear cache failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get content history
|
|
||||||
async function getContentHistory() {
|
|
||||||
updateStatus('info', '📚 Getting content history...');
|
|
||||||
try {
|
|
||||||
const history = await plugin.getContentHistory();
|
|
||||||
updateStatus('success', `📚 Content history: ${JSON.stringify(history, null, 2)}`);
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Content history check failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get dual schedule status
|
|
||||||
async function getDualScheduleStatus() {
|
|
||||||
updateStatus('info', '🔄 Getting dual schedule status...');
|
|
||||||
try {
|
|
||||||
const status = await plugin.getDualScheduleStatus();
|
|
||||||
updateStatus('success', `🔄 Dual schedule status: ${JSON.stringify(status, null, 2)}`);
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Dual schedule check failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('🔔 DailyNotification Plugin Test Interface Loaded Successfully!');
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -17,627 +17,417 @@
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
.container {
|
.container {
|
||||||
max-width: 800px;
|
max-width: 600px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
font-size: 2.5em;
|
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 {
|
.button {
|
||||||
background: rgba(255, 255, 255, 0.2);
|
background: rgba(255, 255, 255, 0.2);
|
||||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||||
color: white;
|
color: white;
|
||||||
padding: 12px 24px;
|
padding: 15px 30px;
|
||||||
margin: 8px;
|
margin: 10px;
|
||||||
border-radius: 20px;
|
border-radius: 25px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 14px;
|
font-size: 16px;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
display: inline-block;
|
|
||||||
}
|
}
|
||||||
.button:hover {
|
.button:hover {
|
||||||
background: rgba(255, 255, 255, 0.3);
|
background: rgba(255, 255, 255, 0.3);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
.button:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
.status {
|
.status {
|
||||||
margin-top: 15px;
|
margin-top: 30px;
|
||||||
padding: 15px;
|
padding: 20px;
|
||||||
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);
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
background: rgba(255, 255, 255, 0.1);
|
||||||
color: white;
|
border-radius: 10px;
|
||||||
}
|
font-family: monospace;
|
||||||
.input-group input::placeholder {
|
|
||||||
color: rgba(255, 255, 255, 0.7);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>🔔 DailyNotification Plugin Test</h1>
|
<div id="statusCard" class="status" style="margin-bottom: 20px; font-size: 14px;">
|
||||||
|
<strong>Plugin Status</strong><br>
|
||||||
<!-- Plugin Status Section -->
|
<div style="margin-top: 10px;">
|
||||||
<div class="section">
|
⚙️ Plugin Settings: <span id="configStatus">Not configured</span><br>
|
||||||
<h2>📊 Plugin Status</h2>
|
🔌 Native Fetcher: <span id="fetcherStatus">Not configured</span><br>
|
||||||
<div class="grid">
|
🔔 Notifications: <span id="notificationPermStatus">Checking...</span><br>
|
||||||
<button class="button" onclick="checkPluginAvailability()">Check Availability</button>
|
⏰ Exact Alarms: <span id="exactAlarmPermStatus">Checking...</span><br>
|
||||||
<button class="button" onclick="getNotificationStatus()">Get Status</button>
|
📢 Channel: <span id="channelStatus">Checking...</span><br>
|
||||||
<button class="button" onclick="checkPermissions()">Check Permissions</button>
|
<div id="pluginStatusContent" style="margin-top: 8px;">
|
||||||
<button class="button" onclick="getBatteryStatus()">Battery Status</button>
|
Loading plugin status...
|
||||||
</div>
|
</div>
|
||||||
<div id="status" class="status">Ready to test...</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Permission Management Section -->
|
|
||||||
<div class="section">
|
|
||||||
<h2>🔐 Permission Management</h2>
|
|
||||||
<div class="grid">
|
|
||||||
<button class="button" onclick="requestPermissions()">Request Permissions</button>
|
|
||||||
<button class="button" onclick="requestExactAlarmPermission()">Request Exact Alarm</button>
|
|
||||||
<button class="button" onclick="openExactAlarmSettings()">Open Settings</button>
|
|
||||||
<button class="button" onclick="requestBatteryOptimizationExemption()">Battery Exemption</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Notification Scheduling Section -->
|
|
||||||
<div class="section">
|
|
||||||
<h2>⏰ Notification Scheduling</h2>
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="notificationUrl">Content URL:</label>
|
|
||||||
<input type="text" id="notificationUrl" placeholder="https://api.example.com/daily-content" value="https://api.example.com/daily-content">
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="notificationTime">Schedule Time:</label>
|
|
||||||
<input type="time" id="notificationTime" value="09:00">
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="notificationTitle">Title:</label>
|
|
||||||
<input type="text" id="notificationTitle" placeholder="Daily Notification" value="Daily Notification">
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="notificationBody">Body:</label>
|
|
||||||
<input type="text" id="notificationBody" placeholder="Your daily content is ready!" value="Your daily content is ready!">
|
|
||||||
</div>
|
|
||||||
<div class="grid">
|
|
||||||
<button class="button" onclick="scheduleNotification()">Schedule Notification</button>
|
|
||||||
<button class="button" onclick="cancelAllNotifications()">Cancel All</button>
|
|
||||||
<button class="button" onclick="getLastNotification()">Get Last</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Configuration Section -->
|
|
||||||
<div class="section">
|
|
||||||
<h2>⚙️ Plugin Configuration</h2>
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="configUrl">Fetch URL:</label>
|
|
||||||
<input type="text" id="configUrl" placeholder="https://api.example.com/content" value="https://api.example.com/content">
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="configTime">Schedule Time:</label>
|
|
||||||
<input type="time" id="configTime" value="09:00">
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="configRetryCount">Retry Count:</label>
|
|
||||||
<input type="number" id="configRetryCount" value="3" min="0" max="10">
|
|
||||||
</div>
|
|
||||||
<div class="grid">
|
|
||||||
<button class="button" onclick="configurePlugin()">Configure Plugin</button>
|
<button class="button" onclick="configurePlugin()">Configure Plugin</button>
|
||||||
<button class="button" onclick="updateSettings()">Update Settings</button>
|
<button class="button" onclick="requestPermissions()">Request Permissions</button>
|
||||||
</div>
|
<button class="button" onclick="testNotification()">Test Notification</button>
|
||||||
</div>
|
<button class="button" onclick="checkComprehensiveStatus()">Full System Status</button>
|
||||||
|
|
||||||
<!-- Advanced Features Section -->
|
<div id="status" class="status">
|
||||||
<div class="section">
|
Ready to test...
|
||||||
<h2>🚀 Advanced Features</h2>
|
|
||||||
<div class="grid">
|
|
||||||
<button class="button" onclick="getExactAlarmStatus()">Exact Alarm Status</button>
|
|
||||||
<button class="button" onclick="getRebootRecoveryStatus()">Reboot Recovery</button>
|
|
||||||
<button class="button" onclick="getRollingWindowStats()">Rolling Window</button>
|
|
||||||
<button class="button" onclick="maintainRollingWindow()">Maintain Window</button>
|
|
||||||
<button class="button" onclick="getContentCache()">Content Cache</button>
|
|
||||||
<button class="button" onclick="clearContentCache()">Clear Cache</button>
|
|
||||||
<button class="button" onclick="getContentHistory()">Content History</button>
|
|
||||||
<button class="button" onclick="getDualScheduleStatus()">Dual Schedule</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
console.log('🔔 DailyNotification Plugin Test Interface Loading...');
|
console.log('Script loading...');
|
||||||
|
console.log('JavaScript is working!');
|
||||||
|
|
||||||
// Global variables
|
// Use real DailyNotification plugin
|
||||||
let plugin = null;
|
console.log('Using real DailyNotification plugin...');
|
||||||
let isPluginAvailable = false;
|
window.DailyNotification = window.Capacitor.Plugins.DailyNotification;
|
||||||
|
|
||||||
// Initialize plugin on page load
|
// Define functions immediately and attach to window
|
||||||
document.addEventListener('DOMContentLoaded', async function() {
|
|
||||||
console.log('📱 DOM loaded, initializing plugin...');
|
function configurePlugin() {
|
||||||
await initializePlugin();
|
console.log('configurePlugin called');
|
||||||
});
|
const status = document.getElementById('status');
|
||||||
|
const configStatus = document.getElementById('configStatus');
|
||||||
|
const fetcherStatus = document.getElementById('fetcherStatus');
|
||||||
|
|
||||||
|
status.innerHTML = 'Configuring plugin...';
|
||||||
|
status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background
|
||||||
|
|
||||||
|
// Update top status to show configuring
|
||||||
|
configStatus.innerHTML = '⏳ Configuring...';
|
||||||
|
fetcherStatus.innerHTML = '⏳ Waiting...';
|
||||||
|
|
||||||
// Initialize the real DailyNotification plugin
|
|
||||||
async function initializePlugin() {
|
|
||||||
try {
|
try {
|
||||||
// Try to access the real plugin through Capacitor
|
if (!window.DailyNotification) {
|
||||||
if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.DailyNotification) {
|
status.innerHTML = 'DailyNotification plugin not available';
|
||||||
plugin = window.Capacitor.Plugins.DailyNotification;
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
isPluginAvailable = true;
|
configStatus.innerHTML = '❌ Plugin unavailable';
|
||||||
console.log('✅ Real DailyNotification plugin found!');
|
fetcherStatus.innerHTML = '❌ Plugin unavailable';
|
||||||
updateStatus('success', '✅ Real DailyNotification plugin loaded successfully!');
|
return;
|
||||||
} else {
|
|
||||||
// Fallback to mock for development
|
|
||||||
console.log('⚠️ Real plugin not available, using mock for development');
|
|
||||||
plugin = createMockPlugin();
|
|
||||||
isPluginAvailable = false;
|
|
||||||
updateStatus('warning', '⚠️ Using mock plugin (real plugin not available)');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Configure plugin settings
|
||||||
|
window.DailyNotification.configure({
|
||||||
|
storage: 'tiered',
|
||||||
|
ttlSeconds: 86400,
|
||||||
|
prefetchLeadMinutes: 60,
|
||||||
|
maxNotificationsPerDay: 3,
|
||||||
|
retentionDays: 7
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.log('Plugin settings configured, now configuring native fetcher...');
|
||||||
|
// Update top status
|
||||||
|
configStatus.innerHTML = '✅ Configured';
|
||||||
|
|
||||||
|
// Configure native fetcher with demo credentials
|
||||||
|
// Note: DemoNativeFetcher uses hardcoded mock data, so this is optional
|
||||||
|
// but demonstrates the API. In production, this would be real credentials.
|
||||||
|
return window.DailyNotification.configureNativeFetcher({
|
||||||
|
apiBaseUrl: 'http://10.0.2.2:3000', // Android emulator → host localhost
|
||||||
|
activeDid: 'did:ethr:0xDEMO1234567890', // Demo DID
|
||||||
|
jwtSecret: 'demo-jwt-secret-for-development-testing'
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// Update top status
|
||||||
|
fetcherStatus.innerHTML = '✅ Configured';
|
||||||
|
|
||||||
|
// Update bottom status for user feedback
|
||||||
|
status.innerHTML = 'Plugin configured successfully!';
|
||||||
|
status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
// Update top status with error
|
||||||
|
if (configStatus.innerHTML.includes('Configuring')) {
|
||||||
|
configStatus.innerHTML = '❌ Failed';
|
||||||
|
}
|
||||||
|
if (fetcherStatus.innerHTML.includes('Waiting') || fetcherStatus.innerHTML.includes('Configuring')) {
|
||||||
|
fetcherStatus.innerHTML = '❌ Failed';
|
||||||
|
}
|
||||||
|
|
||||||
|
status.innerHTML = `Configuration failed: ${error.message}`;
|
||||||
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Plugin initialization failed:', error);
|
configStatus.innerHTML = '❌ Error';
|
||||||
updateStatus('error', `❌ Plugin initialization failed: ${error.message}`);
|
fetcherStatus.innerHTML = '❌ Error';
|
||||||
|
status.innerHTML = `Configuration failed: ${error.message}`;
|
||||||
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create mock plugin for development/testing
|
function loadPluginStatus() {
|
||||||
function createMockPlugin() {
|
console.log('loadPluginStatus called');
|
||||||
return {
|
const pluginStatusContent = document.getElementById('pluginStatusContent');
|
||||||
configure: async (options) => {
|
const statusCard = document.getElementById('statusCard');
|
||||||
console.log('Mock configure called with:', options);
|
|
||||||
return Promise.resolve();
|
try {
|
||||||
},
|
if (!window.DailyNotification) {
|
||||||
getNotificationStatus: async () => {
|
pluginStatusContent.innerHTML = '❌ DailyNotification plugin not available';
|
||||||
return Promise.resolve({
|
statusCard.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
isEnabled: true,
|
return;
|
||||||
isScheduled: true,
|
|
||||||
lastNotificationTime: Date.now() - 86400000,
|
|
||||||
nextNotificationTime: Date.now() + 3600000,
|
|
||||||
pending: 1,
|
|
||||||
settings: { url: 'https://api.example.com/content', time: '09:00' },
|
|
||||||
error: null
|
|
||||||
});
|
|
||||||
},
|
|
||||||
checkPermissions: async () => {
|
|
||||||
return Promise.resolve({
|
|
||||||
notifications: 'granted',
|
|
||||||
backgroundRefresh: 'granted',
|
|
||||||
alert: true,
|
|
||||||
badge: true,
|
|
||||||
sound: true
|
|
||||||
});
|
|
||||||
},
|
|
||||||
requestPermissions: async () => {
|
|
||||||
return Promise.resolve({
|
|
||||||
notifications: 'granted',
|
|
||||||
backgroundRefresh: 'granted',
|
|
||||||
alert: true,
|
|
||||||
badge: true,
|
|
||||||
sound: true
|
|
||||||
});
|
|
||||||
},
|
|
||||||
scheduleDailyNotification: async (options) => {
|
|
||||||
console.log('Mock scheduleDailyNotification called with:', options);
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
cancelAllNotifications: async () => {
|
|
||||||
console.log('Mock cancelAllNotifications called');
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
getLastNotification: async () => {
|
|
||||||
return Promise.resolve({
|
|
||||||
id: 'mock-123',
|
|
||||||
title: 'Mock Notification',
|
|
||||||
body: 'This is a mock notification',
|
|
||||||
timestamp: Date.now() - 3600000,
|
|
||||||
url: 'https://example.com'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getBatteryStatus: async () => {
|
|
||||||
return Promise.resolve({
|
|
||||||
level: 85,
|
|
||||||
isCharging: false,
|
|
||||||
powerState: 1,
|
|
||||||
isOptimizationExempt: false
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getExactAlarmStatus: async () => {
|
|
||||||
return Promise.resolve({
|
|
||||||
supported: true,
|
|
||||||
enabled: true,
|
|
||||||
canSchedule: true,
|
|
||||||
fallbackWindow: '±15 minutes'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
requestExactAlarmPermission: async () => {
|
|
||||||
console.log('Mock requestExactAlarmPermission called');
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
openExactAlarmSettings: async () => {
|
|
||||||
console.log('Mock openExactAlarmSettings called');
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
requestBatteryOptimizationExemption: async () => {
|
|
||||||
console.log('Mock requestBatteryOptimizationExemption called');
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
getRebootRecoveryStatus: async () => {
|
|
||||||
return Promise.resolve({
|
|
||||||
inProgress: false,
|
|
||||||
lastRecoveryTime: Date.now() - 86400000,
|
|
||||||
timeSinceLastRecovery: 86400000,
|
|
||||||
recoveryNeeded: false
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getRollingWindowStats: async () => {
|
|
||||||
return Promise.resolve({
|
|
||||||
stats: 'Window: 7 days, Notifications: 5, Success rate: 100%',
|
|
||||||
maintenanceNeeded: false,
|
|
||||||
timeUntilNextMaintenance: 3600000
|
|
||||||
});
|
|
||||||
},
|
|
||||||
maintainRollingWindow: async () => {
|
|
||||||
console.log('Mock maintainRollingWindow called');
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
getContentCache: async () => {
|
|
||||||
return Promise.resolve({
|
|
||||||
'cache-key-1': { content: 'Mock cached content', timestamp: Date.now() },
|
|
||||||
'cache-key-2': { content: 'Another mock item', timestamp: Date.now() - 3600000 }
|
|
||||||
});
|
|
||||||
},
|
|
||||||
clearContentCache: async () => {
|
|
||||||
console.log('Mock clearContentCache called');
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
getContentHistory: async () => {
|
|
||||||
return Promise.resolve([
|
|
||||||
{ id: '1', timestamp: Date.now() - 86400000, success: true, content: 'Mock content 1' },
|
|
||||||
{ id: '2', timestamp: Date.now() - 172800000, success: true, content: 'Mock content 2' }
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
getDualScheduleStatus: async () => {
|
|
||||||
return Promise.resolve({
|
|
||||||
isActive: true,
|
|
||||||
contentSchedule: { nextRun: Date.now() + 3600000, isEnabled: true },
|
|
||||||
userSchedule: { nextRun: Date.now() + 7200000, isEnabled: true },
|
|
||||||
lastContentFetch: Date.now() - 3600000,
|
|
||||||
lastUserNotification: Date.now() - 7200000
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
window.DailyNotification.getNotificationStatus()
|
||||||
|
.then(result => {
|
||||||
|
const nextTime = result.nextNotificationTime ? new Date(result.nextNotificationTime).toLocaleString() : 'None scheduled';
|
||||||
|
const hasSchedules = result.isEnabled || (result.pending && result.pending > 0);
|
||||||
|
const statusIcon = hasSchedules ? '✅' : '⏸️';
|
||||||
|
pluginStatusContent.innerHTML = `${statusIcon} Active Schedules: ${hasSchedules ? 'Yes' : 'No'}<br>
|
||||||
|
📅 Next Notification: ${nextTime}<br>
|
||||||
|
⏳ Pending: ${result.pending || 0}`;
|
||||||
|
statusCard.style.background = hasSchedules ?
|
||||||
|
'rgba(0, 255, 0, 0.15)' : 'rgba(255, 255, 255, 0.1)'; // Green if active, light gray if none
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
pluginStatusContent.innerHTML = `⚠️ Status check failed: ${error.message}`;
|
||||||
|
statusCard.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
pluginStatusContent.innerHTML = `⚠️ Status check failed: ${error.message}`;
|
||||||
|
statusCard.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility function to update status display
|
// Notification test functions
|
||||||
function updateStatus(type, message) {
|
function testNotification() {
|
||||||
const statusEl = document.getElementById('status');
|
console.log('testNotification called');
|
||||||
statusEl.className = `status ${type}`;
|
|
||||||
statusEl.textContent = message;
|
// Quick sanity check - test plugin availability
|
||||||
console.log(`[${type.toUpperCase()}] ${message}`);
|
if (window.Capacitor && window.Capacitor.isPluginAvailable) {
|
||||||
}
|
const isAvailable = window.Capacitor.isPluginAvailable('DailyNotification');
|
||||||
|
console.log('is plugin available?', isAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = document.getElementById('status');
|
||||||
|
status.innerHTML = 'Testing plugin connection...';
|
||||||
|
status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background
|
||||||
|
|
||||||
// Plugin availability check
|
|
||||||
async function checkPluginAvailability() {
|
|
||||||
updateStatus('info', '🔍 Checking plugin availability...');
|
|
||||||
try {
|
try {
|
||||||
if (plugin) {
|
if (!window.DailyNotification) {
|
||||||
updateStatus('success', `✅ Plugin available: ${isPluginAvailable ? 'Real plugin' : 'Mock plugin'}`);
|
status.innerHTML = 'DailyNotification plugin not available';
|
||||||
} else {
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
updateStatus('error', '❌ Plugin not available');
|
return;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Availability check failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get notification status
|
// Test the notification method directly
|
||||||
async function getNotificationStatus() {
|
console.log('Testing notification scheduling...');
|
||||||
updateStatus('info', '📊 Getting notification status...');
|
|
||||||
try {
|
|
||||||
const status = await plugin.getNotificationStatus();
|
|
||||||
updateStatus('success', `📊 Status: ${JSON.stringify(status, null, 2)}`);
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Status check failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check permissions
|
|
||||||
async function checkPermissions() {
|
|
||||||
updateStatus('info', '🔐 Checking permissions...');
|
|
||||||
try {
|
|
||||||
const permissions = await plugin.checkPermissions();
|
|
||||||
updateStatus('success', `🔐 Permissions: ${JSON.stringify(permissions, null, 2)}`);
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Permission check failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request permissions
|
|
||||||
async function requestPermissions() {
|
|
||||||
updateStatus('info', '🔐 Requesting permissions...');
|
|
||||||
try {
|
|
||||||
const result = await plugin.requestPermissions();
|
|
||||||
updateStatus('success', `🔐 Permission result: ${JSON.stringify(result, null, 2)}`);
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Permission request failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get battery status
|
|
||||||
async function getBatteryStatus() {
|
|
||||||
updateStatus('info', '🔋 Getting battery status...');
|
|
||||||
try {
|
|
||||||
const battery = await plugin.getBatteryStatus();
|
|
||||||
updateStatus('success', `🔋 Battery: ${JSON.stringify(battery, null, 2)}`);
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Battery check failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schedule notification
|
|
||||||
async function scheduleNotification() {
|
|
||||||
updateStatus('info', '⏰ Scheduling notification...');
|
|
||||||
try {
|
|
||||||
const timeInput = document.getElementById('notificationTime').value;
|
|
||||||
const [hours, minutes] = timeInput.split(':');
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const scheduledTime = new Date();
|
const notificationTime = new Date(now.getTime() + 240000); // 4 minutes from now
|
||||||
scheduledTime.setHours(parseInt(hours), parseInt(minutes), 0, 0);
|
const prefetchTime = new Date(now.getTime() + 120000); // 2 minutes from now (2 min before notification)
|
||||||
|
const notificationTimeString = notificationTime.getHours().toString().padStart(2, '0') + ':' +
|
||||||
// If scheduled time is in the past, schedule for tomorrow
|
notificationTime.getMinutes().toString().padStart(2, '0');
|
||||||
if (scheduledTime <= now) {
|
|
||||||
scheduledTime.setDate(scheduledTime.getDate() + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate prefetch time (2 minutes before notification)
|
|
||||||
const prefetchTime = new Date(scheduledTime.getTime() - 120000); // 2 minutes
|
|
||||||
const prefetchTimeReadable = prefetchTime.toLocaleTimeString();
|
|
||||||
const notificationTimeReadable = scheduledTime.toLocaleTimeString();
|
|
||||||
const prefetchTimeString = prefetchTime.getHours().toString().padStart(2, '0') + ':' +
|
const prefetchTimeString = prefetchTime.getHours().toString().padStart(2, '0') + ':' +
|
||||||
prefetchTime.getMinutes().toString().padStart(2, '0');
|
prefetchTime.getMinutes().toString().padStart(2, '0');
|
||||||
const notificationTimeString = scheduledTime.getHours().toString().padStart(2, '0') + ':' +
|
|
||||||
scheduledTime.getMinutes().toString().padStart(2, '0');
|
|
||||||
|
|
||||||
const options = {
|
window.DailyNotification.scheduleDailyNotification({
|
||||||
url: document.getElementById('notificationUrl').value,
|
time: notificationTimeString,
|
||||||
time: timeInput,
|
title: 'Test Notification',
|
||||||
title: document.getElementById('notificationTitle').value,
|
body: 'This is a test notification from the DailyNotification plugin!',
|
||||||
body: document.getElementById('notificationBody').value,
|
|
||||||
sound: true,
|
sound: true,
|
||||||
priority: 'high'
|
priority: 'high'
|
||||||
};
|
})
|
||||||
await plugin.scheduleDailyNotification(options);
|
.then(() => {
|
||||||
updateStatus('success', `✅ Notification scheduled!<br>` +
|
const prefetchTimeReadable = prefetchTime.toLocaleTimeString();
|
||||||
`📥 Prefetch: ${prefetchTimeReadable} (${prefetchTimeString})<br>` +
|
const notificationTimeReadable = notificationTime.toLocaleTimeString();
|
||||||
`🔔 Notification: ${notificationTimeReadable} (${notificationTimeString})`);
|
status.innerHTML = '✅ Notification scheduled!<br>' +
|
||||||
|
'📥 Prefetch: ' + prefetchTimeReadable + ' (' + prefetchTimeString + ')<br>' +
|
||||||
|
'🔔 Notification: ' + notificationTimeReadable + ' (' + notificationTimeString + ')';
|
||||||
|
status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background
|
||||||
|
// Refresh plugin status display
|
||||||
|
setTimeout(() => loadPluginStatus(), 500);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
// Check if this is an exact alarm permission error
|
||||||
|
if (error.code === 'EXACT_ALARM_PERMISSION_REQUIRED' ||
|
||||||
|
error.message.includes('Exact alarm permission') ||
|
||||||
|
error.message.includes('Alarms & reminders')) {
|
||||||
|
status.innerHTML = '⚠️ Exact Alarm Permission Required<br><br>' +
|
||||||
|
'Settings opened automatically.<br>' +
|
||||||
|
'Please enable "Allow exact alarms" and return to try again.';
|
||||||
|
status.style.background = 'rgba(255, 165, 0, 0.3)'; // Orange background
|
||||||
|
} else {
|
||||||
|
status.innerHTML = `❌ Notification failed: ${error.message}`;
|
||||||
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
updateStatus('error', `❌ Scheduling failed: ${error.message}`);
|
status.innerHTML = `Notification test failed: ${error.message}`;
|
||||||
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel all notifications
|
|
||||||
async function cancelAllNotifications() {
|
// Permission management functions
|
||||||
updateStatus('info', '❌ Cancelling all notifications...');
|
function requestPermissions() {
|
||||||
|
console.log('requestPermissions called');
|
||||||
|
const status = document.getElementById('status');
|
||||||
|
status.innerHTML = 'Requesting permissions...';
|
||||||
|
status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await plugin.cancelAllNotifications();
|
if (!window.DailyNotification) {
|
||||||
updateStatus('success', '❌ All notifications cancelled');
|
status.innerHTML = 'DailyNotification plugin not available';
|
||||||
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.DailyNotification.requestNotificationPermissions()
|
||||||
|
.then(() => {
|
||||||
|
status.innerHTML = 'Permission request completed! Check your device settings if needed.';
|
||||||
|
status.style.background = 'rgba(0, 255, 0, 0.3)'; // Green background
|
||||||
|
|
||||||
|
// Refresh permission and channel status display after request
|
||||||
|
setTimeout(() => {
|
||||||
|
loadPermissionStatus();
|
||||||
|
loadChannelStatus();
|
||||||
|
}, 1000);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
status.innerHTML = `Permission request failed: ${error.message}`;
|
||||||
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
updateStatus('error', `❌ Cancel failed: ${error.message}`);
|
status.innerHTML = `Permission request failed: ${error.message}`;
|
||||||
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get last notification
|
function loadChannelStatus() {
|
||||||
async function getLastNotification() {
|
const channelStatus = document.getElementById('channelStatus');
|
||||||
updateStatus('info', '📱 Getting last notification...');
|
|
||||||
try {
|
try {
|
||||||
const notification = await plugin.getLastNotification();
|
if (!window.DailyNotification) {
|
||||||
updateStatus('success', `📱 Last notification: ${JSON.stringify(notification, null, 2)}`);
|
channelStatus.innerHTML = '❌ Plugin unavailable';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.DailyNotification.isChannelEnabled()
|
||||||
|
.then(result => {
|
||||||
|
const importanceText = getImportanceText(result.importance);
|
||||||
|
if (result.enabled) {
|
||||||
|
channelStatus.innerHTML = `✅ Enabled (${importanceText})`;
|
||||||
|
} else {
|
||||||
|
channelStatus.innerHTML = `❌ Disabled (${importanceText})`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
channelStatus.innerHTML = '⚠️ Error';
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
updateStatus('error', `❌ Get last notification failed: ${error.message}`);
|
channelStatus.innerHTML = '⚠️ Error';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure plugin
|
function checkComprehensiveStatus() {
|
||||||
async function configurePlugin() {
|
const status = document.getElementById('status');
|
||||||
updateStatus('info', '⚙️ Configuring plugin...');
|
status.innerHTML = 'Checking comprehensive status...';
|
||||||
|
status.style.background = 'rgba(255, 255, 0, 0.3)'; // Yellow background
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const config = {
|
if (!window.DailyNotification) {
|
||||||
fetchUrl: document.getElementById('configUrl').value,
|
status.innerHTML = 'DailyNotification plugin not available';
|
||||||
scheduleTime: document.getElementById('configTime').value,
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
retryCount: parseInt(document.getElementById('configRetryCount').value),
|
return;
|
||||||
enableNotifications: true,
|
}
|
||||||
offlineFallback: true
|
|
||||||
};
|
window.DailyNotification.checkStatus()
|
||||||
await plugin.configure(config);
|
.then(result => {
|
||||||
updateStatus('success', `⚙️ Plugin configured: ${JSON.stringify(config, null, 2)}`);
|
const canSchedule = result.canScheduleNow;
|
||||||
|
const issues = [];
|
||||||
|
|
||||||
|
if (!result.postNotificationsGranted) {
|
||||||
|
issues.push('POST_NOTIFICATIONS permission');
|
||||||
|
}
|
||||||
|
if (!result.channelEnabled) {
|
||||||
|
issues.push('notification channel disabled');
|
||||||
|
}
|
||||||
|
if (!result.exactAlarmsGranted) {
|
||||||
|
issues.push('exact alarm permission');
|
||||||
|
}
|
||||||
|
|
||||||
|
let statusText = `Status: ${canSchedule ? 'Ready to schedule' : 'Issues found'}`;
|
||||||
|
if (issues.length > 0) {
|
||||||
|
statusText += `\nIssues: ${issues.join(', ')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
statusText += `\nChannel: ${getImportanceText(result.channelImportance)}`;
|
||||||
|
statusText += `\nChannel ID: ${result.channelId}`;
|
||||||
|
|
||||||
|
status.innerHTML = statusText;
|
||||||
|
status.style.background = canSchedule ? 'rgba(0, 255, 0, 0.3)' : 'rgba(255, 0, 0, 0.3)';
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
status.innerHTML = `Status check failed: ${error.message}`;
|
||||||
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
updateStatus('error', `❌ Configuration failed: ${error.message}`);
|
status.innerHTML = `Status check failed: ${error.message}`;
|
||||||
|
status.style.background = 'rgba(255, 0, 0, 0.3)'; // Red background
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update settings
|
function getImportanceText(importance) {
|
||||||
async function updateSettings() {
|
switch (importance) {
|
||||||
updateStatus('info', '⚙️ Updating settings...');
|
case 0: return 'None (blocked)';
|
||||||
|
case 1: return 'Min';
|
||||||
|
case 2: return 'Low';
|
||||||
|
case 3: return 'Default';
|
||||||
|
case 4: return 'High';
|
||||||
|
case 5: return 'Max';
|
||||||
|
default: return `Unknown (${importance})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach to window object
|
||||||
|
window.configurePlugin = configurePlugin;
|
||||||
|
window.testNotification = testNotification;
|
||||||
|
window.requestPermissions = requestPermissions;
|
||||||
|
window.checkComprehensiveStatus = checkComprehensiveStatus;
|
||||||
|
|
||||||
|
function loadPermissionStatus() {
|
||||||
|
const notificationPermStatus = document.getElementById('notificationPermStatus');
|
||||||
|
const exactAlarmPermStatus = document.getElementById('exactAlarmPermStatus');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const settings = {
|
if (!window.DailyNotification) {
|
||||||
url: document.getElementById('configUrl').value,
|
notificationPermStatus.innerHTML = '❌ Plugin unavailable';
|
||||||
time: document.getElementById('configTime').value,
|
exactAlarmPermStatus.innerHTML = '❌ Plugin unavailable';
|
||||||
retryCount: parseInt(document.getElementById('configRetryCount').value),
|
return;
|
||||||
sound: true,
|
}
|
||||||
priority: 'high'
|
|
||||||
};
|
window.DailyNotification.checkPermissionStatus()
|
||||||
await plugin.updateSettings(settings);
|
.then(result => {
|
||||||
updateStatus('success', `⚙️ Settings updated: ${JSON.stringify(settings, null, 2)}`);
|
notificationPermStatus.innerHTML = result.notificationsEnabled ? '✅ Granted' : '❌ Not granted';
|
||||||
|
exactAlarmPermStatus.innerHTML = result.exactAlarmEnabled ? '✅ Granted' : '❌ Not granted';
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
notificationPermStatus.innerHTML = '⚠️ Error';
|
||||||
|
exactAlarmPermStatus.innerHTML = '⚠️ Error';
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
updateStatus('error', `❌ Settings update failed: ${error.message}`);
|
notificationPermStatus.innerHTML = '⚠️ Error';
|
||||||
|
exactAlarmPermStatus.innerHTML = '⚠️ Error';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get exact alarm status
|
// Load plugin status automatically on page load
|
||||||
async function getExactAlarmStatus() {
|
window.addEventListener('load', () => {
|
||||||
updateStatus('info', '⏰ Getting exact alarm status...');
|
console.log('Page loaded, loading plugin status...');
|
||||||
try {
|
// Small delay to ensure Capacitor is ready
|
||||||
const status = await plugin.getExactAlarmStatus();
|
setTimeout(() => {
|
||||||
updateStatus('success', `⏰ Exact alarm status: ${JSON.stringify(status, null, 2)}`);
|
loadPluginStatus();
|
||||||
} catch (error) {
|
loadPermissionStatus();
|
||||||
updateStatus('error', `❌ Exact alarm check failed: ${error.message}`);
|
loadChannelStatus();
|
||||||
}
|
}, 500);
|
||||||
}
|
});
|
||||||
|
|
||||||
// Request exact alarm permission
|
|
||||||
async function requestExactAlarmPermission() {
|
|
||||||
updateStatus('info', '⏰ Requesting exact alarm permission...');
|
|
||||||
try {
|
|
||||||
await plugin.requestExactAlarmPermission();
|
|
||||||
updateStatus('success', '⏰ Exact alarm permission requested');
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Exact alarm permission request failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open exact alarm settings
|
|
||||||
async function openExactAlarmSettings() {
|
|
||||||
updateStatus('info', '⚙️ Opening exact alarm settings...');
|
|
||||||
try {
|
|
||||||
await plugin.openExactAlarmSettings();
|
|
||||||
updateStatus('success', '⚙️ Exact alarm settings opened');
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Open settings failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request battery optimization exemption
|
console.log('Functions attached to window:', {
|
||||||
async function requestBatteryOptimizationExemption() {
|
configurePlugin: typeof window.configurePlugin,
|
||||||
updateStatus('info', '🔋 Requesting battery optimization exemption...');
|
testNotification: typeof window.testNotification
|
||||||
try {
|
});
|
||||||
await plugin.requestBatteryOptimizationExemption();
|
|
||||||
updateStatus('success', '🔋 Battery optimization exemption requested');
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Battery exemption request failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get reboot recovery status
|
|
||||||
async function getRebootRecoveryStatus() {
|
|
||||||
updateStatus('info', '🔄 Getting reboot recovery status...');
|
|
||||||
try {
|
|
||||||
const status = await plugin.getRebootRecoveryStatus();
|
|
||||||
updateStatus('success', `🔄 Reboot recovery status: ${JSON.stringify(status, null, 2)}`);
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Reboot recovery check failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get rolling window stats
|
|
||||||
async function getRollingWindowStats() {
|
|
||||||
updateStatus('info', '📊 Getting rolling window stats...');
|
|
||||||
try {
|
|
||||||
const stats = await plugin.getRollingWindowStats();
|
|
||||||
updateStatus('success', `📊 Rolling window stats: ${JSON.stringify(stats, null, 2)}`);
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Rolling window stats failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maintain rolling window
|
|
||||||
async function maintainRollingWindow() {
|
|
||||||
updateStatus('info', '🔧 Maintaining rolling window...');
|
|
||||||
try {
|
|
||||||
await plugin.maintainRollingWindow();
|
|
||||||
updateStatus('success', '🔧 Rolling window maintenance completed');
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Rolling window maintenance failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get content cache
|
|
||||||
async function getContentCache() {
|
|
||||||
updateStatus('info', '💾 Getting content cache...');
|
|
||||||
try {
|
|
||||||
const cache = await plugin.getContentCache();
|
|
||||||
updateStatus('success', `💾 Content cache: ${JSON.stringify(cache, null, 2)}`);
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Content cache check failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear content cache
|
|
||||||
async function clearContentCache() {
|
|
||||||
updateStatus('info', '🗑️ Clearing content cache...');
|
|
||||||
try {
|
|
||||||
await plugin.clearContentCache();
|
|
||||||
updateStatus('success', '🗑️ Content cache cleared');
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Clear cache failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get content history
|
|
||||||
async function getContentHistory() {
|
|
||||||
updateStatus('info', '📚 Getting content history...');
|
|
||||||
try {
|
|
||||||
const history = await plugin.getContentHistory();
|
|
||||||
updateStatus('success', `📚 Content history: ${JSON.stringify(history, null, 2)}`);
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Content history check failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get dual schedule status
|
|
||||||
async function getDualScheduleStatus() {
|
|
||||||
updateStatus('info', '🔄 Getting dual schedule status...');
|
|
||||||
try {
|
|
||||||
const status = await plugin.getDualScheduleStatus();
|
|
||||||
updateStatus('success', `🔄 Dual schedule status: ${JSON.stringify(status, null, 2)}`);
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus('error', `❌ Dual schedule check failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('🔔 DailyNotification Plugin Test Interface Loaded Successfully!');
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user