Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03658340f8 | ||
|
|
821d3b7d05 | ||
|
|
8e6c83021f | ||
|
|
cfe90fd04e | ||
|
|
fb9da10fd2 | ||
|
|
6d221ee1ca | ||
|
|
823db447ca | ||
|
|
87ffa025e8 | ||
|
|
15c9088736 | ||
|
|
ec41dd52d5 | ||
|
|
463db39a6b | ||
|
|
fe97dff752 | ||
|
|
903047f13b | ||
|
|
48be234af4 | ||
|
|
6c0907d905 | ||
|
|
8d8bcf2a7e | ||
|
|
a4b47904c8 |
@@ -0,0 +1,20 @@
|
|||||||
|
# Agent Instructions for crowd-funder-for-time-pwa
|
||||||
|
|
||||||
|
## Android Build — Google Play Services / FOSS Compatibility
|
||||||
|
|
||||||
|
**Firebase is opt-in. Do NOT enable it accidentally.**
|
||||||
|
|
||||||
|
`android/google-services.json` is gitignored and may be present on disk for push notification development, but Firebase is only activated when you explicitly pass `-PfirebaseEnabled` to Gradle:
|
||||||
|
|
||||||
|
- **FOSS / APK / Aurora / Zapstore / F-Droid builds**: just `./gradlew assembleRelease` — Firebase stays off even if `google-services.json` is on disk.
|
||||||
|
- **Firebase / FCM / Play Store builds**: `./gradlew bundleRelease -PfirebaseEnabled` — explicitly opt in.
|
||||||
|
|
||||||
|
This guard is in `android/app/build.gradle`. Do NOT change this conditional to activate Firebase unconditionally based on file presence alone — that was the bug that broke FOSS distribution in June 2026.
|
||||||
|
|
||||||
|
`google-services.json` is intentionally excluded from git (`android/.gitignore`). Never commit it.
|
||||||
|
|
||||||
|
Full details, incident history, and F-Droid notes: `doc/development/android-firebase-gms.md`
|
||||||
|
|
||||||
|
## Android Build — MLKit Barcode Scanner
|
||||||
|
|
||||||
|
`@capacitor-mlkit/barcode-scanning` depends on `com.google.android.gms:play-services-code-scanner`, which merges `com.google.android.gms.version` into the APK manifest. This is a known long-term issue for strict FOSS/F-Droid builds. For now, the dependency is accepted; barcode scanning simply will not work on GMS-less devices (it fails gracefully at scan time, not at startup). Do not add additional GMS/Firebase dependencies without explicitly acknowledging this trade-off.
|
||||||
@@ -1140,7 +1140,7 @@ export GEM_PATH=$shortened_path
|
|||||||
##### 1. Bump the version in package.json & CHANGELOG.md for `MARKETING_VERSION`, then `grep CURRENT_PROJECT_VERSION ios/App/App.xcodeproj/project.pbxproj` and add 1 for the numbered version here:
|
##### 1. Bump the version in package.json & CHANGELOG.md for `MARKETING_VERSION`, then `grep CURRENT_PROJECT_VERSION ios/App/App.xcodeproj/project.pbxproj` and add 1 for the numbered version here:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ios/App && xcrun agvtool new-version 68 && perl -p -i -e "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 1.4.2;/g" App.xcodeproj/project.pbxproj && cd -
|
cd ios/App && xcrun agvtool new-version 70 && perl -p -i -e "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 1.4.4;/g" App.xcodeproj/project.pbxproj && cd -
|
||||||
# Unfortunately this edits Info.plist directly.
|
# Unfortunately this edits Info.plist directly.
|
||||||
#xcrun agvtool new-marketing-version 0.4.5
|
#xcrun agvtool new-marketing-version 0.4.5
|
||||||
```
|
```
|
||||||
@@ -1360,7 +1360,7 @@ npm run assets:validate
|
|||||||
**Source Assets (Required):**
|
**Source Assets (Required):**
|
||||||
- `resources/icon.png` - App icon source
|
- `resources/icon.png` - App icon source
|
||||||
- `resources/splash.png` - Splash screen source
|
- `resources/splash.png` - Splash screen source
|
||||||
- `resources/splash_dark.png` - Dark mode splash source
|
- `resources/splash-dark.png` - Dark mode splash source
|
||||||
|
|
||||||
**Android Resources (Generated):**
|
**Android Resources (Generated):**
|
||||||
- `android/app/src/main/res/drawable/splash.png` - Splash screen drawable
|
- `android/app/src/main/res/drawable/splash.png` - Splash screen drawable
|
||||||
@@ -1419,8 +1419,8 @@ The recommended way to build for Android is using the automated build script:
|
|||||||
##### 1. Bump the version in package.json, then update these versions & run:
|
##### 1. Bump the version in package.json, then update these versions & run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
perl -p -i -e 's/versionCode .*/versionCode 68/g' android/app/build.gradle
|
perl -p -i -e 's/versionCode .*/versionCode 70/g' android/app/build.gradle
|
||||||
perl -p -i -e 's/versionName .*/versionName "1.4.2"/g' android/app/build.gradle
|
perl -p -i -e 's/versionName .*/versionName "1.4.4"/g' android/app/build.gradle
|
||||||
```
|
```
|
||||||
|
|
||||||
##### 2. Build
|
##### 2. Build
|
||||||
@@ -1458,17 +1458,18 @@ cd -
|
|||||||
|
|
||||||
- Setup by adding the app/gradle.properties.secrets file (see properties at top
|
- Setup by adding the app/gradle.properties.secrets file (see properties at top
|
||||||
of app/build.gradle) and the app/time-safari-upload-key-pkcs12.jks file
|
of app/build.gradle) and the app/time-safari-upload-key-pkcs12.jks file
|
||||||
- In app/build.gradle, bump the versionCode and maybe the versionName
|
|
||||||
- Then `bundleRelease`:
|
- Then `bundleRelease`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd android
|
cd android
|
||||||
./gradlew bundleRelease -Dlint.baselines.continue=true
|
./gradlew bundleRelease -Dlint.baselines.continue=true -PfirebaseEnabled
|
||||||
cd -
|
cd -
|
||||||
```
|
```
|
||||||
|
|
||||||
... and find your `aab` file at app/build/outputs/bundle/release
|
... and find your `aab` file at app/build/outputs/bundle/release
|
||||||
|
|
||||||
|
* Note that F-Droid builds should omit `-PfirebaseEnabled`.
|
||||||
|
|
||||||
At play.google.com/console:
|
At play.google.com/console:
|
||||||
|
|
||||||
- Go to Production or the Closed Testing and either Create Track or Manage Track.
|
- Go to Production or the Closed Testing and either Create Track or Manage Track.
|
||||||
@@ -1603,6 +1604,11 @@ Use the commands above to check and fix code quality issues.
|
|||||||
4. **Native Build**: Platform-specific compilation
|
4. **Native Build**: Platform-specific compilation
|
||||||
5. **Package Creation**: APK/IPA generation
|
5. **Package Creation**: APK/IPA generation
|
||||||
|
|
||||||
|
`resources/` is the canonical source for app icons and splash screens.
|
||||||
|
Do not keep a legacy top-level `assets/` directory unless it is intentionally
|
||||||
|
used: `@capacitor/assets` prioritizes `assets/` over `resources/`, which can
|
||||||
|
prevent the canonical assets from being discovered.
|
||||||
|
|
||||||
## Architecture Environment Configuration
|
## Architecture Environment Configuration
|
||||||
|
|
||||||
### Environment Files
|
### Environment Files
|
||||||
|
|||||||
@@ -6,6 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
|
||||||
|
## [1.4.4] - 2026.06.21
|
||||||
|
### Changed
|
||||||
|
- More checks for Firebase so that it won't break, eg in Aurora store.
|
||||||
|
|
||||||
|
|
||||||
|
## [1.4.3] - 2026.06.19
|
||||||
|
### Removed
|
||||||
|
- Automatic "Check your starred projects" daily notification
|
||||||
|
### Changed
|
||||||
|
- Positioning for 'Thank' button and entity-type toggle link
|
||||||
|
|
||||||
|
|
||||||
## [1.4.2] - 2026.05.24
|
## [1.4.2] - 2026.05.24
|
||||||
### Changed
|
### Changed
|
||||||
- Support 16 KB page sizes
|
- Support 16 KB page sizes
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ icon and splash screen generation across all platforms.
|
|||||||
### Asset Sources
|
### Asset Sources
|
||||||
|
|
||||||
- **Single source of truth**: `resources/` directory (Capacitor default)
|
- **Single source of truth**: `resources/` directory (Capacitor default)
|
||||||
- **Source files**: `icon.png`, `splash.png`, `splash_dark.png`
|
- **Source files**: `icon.png`, `splash.png`, `splash-dark.png`
|
||||||
- **Format**: PNG or SVG files for optimal quality
|
- **Format**: PNG or SVG files for optimal quality
|
||||||
|
|
||||||
### Asset Generation
|
### Asset Generation
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ android {
|
|||||||
applicationId "app.timesafari.app"
|
applicationId "app.timesafari.app"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 68
|
versionCode 70
|
||||||
versionName "1.4.2"
|
versionName "1.4.4"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||||
@@ -130,11 +130,20 @@ dependencies {
|
|||||||
|
|
||||||
apply from: 'capacitor.build.gradle'
|
apply from: 'capacitor.build.gradle'
|
||||||
|
|
||||||
|
// Firebase / Google Play Services are opt-in. Pass -PfirebaseEnabled to any Gradle command
|
||||||
|
// to activate Firebase (FCM push notifications). Without this flag the build works on
|
||||||
|
// F-Droid, Aurora, Zapstore, and plain APK sideloading even when google-services.json
|
||||||
|
// is present on disk (it is gitignored; see AGENTS.md for the full story).
|
||||||
try {
|
try {
|
||||||
def servicesJSON = file('google-services.json')
|
def servicesJSON = file('google-services.json')
|
||||||
if (servicesJSON.text) {
|
if (servicesJSON.exists() && servicesJSON.text && project.hasProperty('firebaseEnabled')) {
|
||||||
apply plugin: 'com.google.gms.google-services'
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
logger.info("Firebase enabled: google-services plugin applied")
|
||||||
|
} else if (servicesJSON.exists() && !project.hasProperty('firebaseEnabled')) {
|
||||||
|
logger.info("google-services.json present but firebaseEnabled not set — skipping Firebase plugin (pass -PfirebaseEnabled to enable)")
|
||||||
|
} else {
|
||||||
|
logger.info("google-services.json not found — Firebase plugin not applied")
|
||||||
}
|
}
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
|
logger.info("google-services plugin not applied: ${e.message}")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import org.timesafari.dailynotification.FetchContext;
|
|||||||
import org.timesafari.dailynotification.NativeNotificationContentFetcher;
|
import org.timesafari.dailynotification.NativeNotificationContentFetcher;
|
||||||
import org.timesafari.dailynotification.NotificationContent;
|
import org.timesafari.dailynotification.NotificationContent;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
@@ -47,22 +46,11 @@ public class TimeSafariNativeFetcher implements NativeNotificationContentFetcher
|
|||||||
// This should query the TimeSafari API for notification content
|
// This should query the TimeSafari API for notification content
|
||||||
// using the configured apiBaseUrl, activeDid, and jwtToken
|
// using the configured apiBaseUrl, activeDid, and jwtToken
|
||||||
|
|
||||||
// For now, return a placeholder notification
|
// Not implemented yet: return no content rather than fabricating a
|
||||||
long scheduledTime = fetchContext.scheduledTime != null
|
// placeholder notification (previously hardcoded "Check your starred
|
||||||
? fetchContext.scheduledTime
|
// projects for updates!", which showed on every app startup).
|
||||||
: System.currentTimeMillis() + 60000; // 1 minute from now
|
Log.d(TAG, "Content fetching not yet implemented; returning no notifications");
|
||||||
|
return Collections.<NotificationContent>emptyList();
|
||||||
NotificationContent content = new NotificationContent(
|
|
||||||
"TimeSafari Update",
|
|
||||||
"Check your starred projects for updates!",
|
|
||||||
scheduledTime
|
|
||||||
);
|
|
||||||
|
|
||||||
List<NotificationContent> results = new ArrayList<>();
|
|
||||||
results.add(content);
|
|
||||||
|
|
||||||
Log.d(TAG, "Returning " + results.size() + " notification(s)");
|
|
||||||
return results;
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Fetch failed", e);
|
Log.e(TAG, "Fetch failed", e);
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
"icon": {
|
"icon": {
|
||||||
"android": {
|
"android": {
|
||||||
"adaptive": {
|
"adaptive": {
|
||||||
"background": "#121212",
|
"background": "resources/android/icon/icon-background.png",
|
||||||
"foreground": "resources/icon.png",
|
"foreground": "resources/android/icon/icon-foreground.png",
|
||||||
"monochrome": "resources/icon.png"
|
"monochrome": "resources/android/icon/icon-monochrome.png"
|
||||||
},
|
},
|
||||||
"target": "android/app/src/main/res"
|
"target": "android/app/src/main/res"
|
||||||
},
|
},
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
"scale": "cover",
|
"scale": "cover",
|
||||||
"target": "android/app/src/main/res"
|
"target": "android/app/src/main/res"
|
||||||
},
|
},
|
||||||
"darkSource": "resources/splash_dark.png",
|
"darkSource": "resources/splash-dark.png",
|
||||||
"ios": {
|
"ios": {
|
||||||
"target": "ios/App/App/Assets.xcassets",
|
"target": "ios/App/App/Assets.xcassets",
|
||||||
"useStoryBoard": true
|
"useStoryBoard": true
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
"scale": "cover",
|
"scale": "cover",
|
||||||
"target": "android/app/src/main/res"
|
"target": "android/app/src/main/res"
|
||||||
},
|
},
|
||||||
"darkSource": "resources/splash_dark.png",
|
"darkSource": "resources/splash-dark.png",
|
||||||
"ios": {
|
"ios": {
|
||||||
"target": "ios/App/App/Assets.xcassets",
|
"target": "ios/App/App/Assets.xcassets",
|
||||||
"useStoryBoard": true
|
"useStoryBoard": true
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ npm run build:android:studio
|
|||||||
#### Source Assets (Required)
|
#### Source Assets (Required)
|
||||||
- `resources/icon.png` - App icon source
|
- `resources/icon.png` - App icon source
|
||||||
- `resources/splash.png` - Splash screen source
|
- `resources/splash.png` - Splash screen source
|
||||||
- `resources/splash_dark.png` - Dark mode splash source
|
- `resources/splash-dark.png` - Dark mode splash source
|
||||||
|
|
||||||
#### Android Resources (Generated)
|
#### Android Resources (Generated)
|
||||||
- `android/app/src/main/res/drawable/splash.png` - Splash screen drawable
|
- `android/app/src/main/res/drawable/splash.png` - Splash screen drawable
|
||||||
@@ -201,13 +201,13 @@ mkdir -p android/app/src/main/res/mipmap-{mdpi,hdpi,xhdpi,xxhdpi,xxxhdpi}
|
|||||||
# Copy source assets to assets directory
|
# Copy source assets to assets directory
|
||||||
cp resources/icon.png assets/
|
cp resources/icon.png assets/
|
||||||
cp resources/splash.png assets/
|
cp resources/splash.png assets/
|
||||||
cp resources/splash_dark.png assets/
|
cp resources/splash-dark.png assets/
|
||||||
|
|
||||||
# Generate assets manually
|
# Generate assets manually
|
||||||
npx @capacitor/assets generate
|
npx @capacitor/assets generate
|
||||||
|
|
||||||
# Clean up
|
# Clean up
|
||||||
rm assets/icon.png assets/splash.png assets/splash_dark.png
|
rm assets/icon.png assets/splash.png assets/splash-dark.png
|
||||||
```
|
```
|
||||||
|
|
||||||
## Future Enhancements
|
## Future Enhancements
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# Android — Firebase, Google Play Services, and FOSS Distribution
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The app is designed to work on Android devices with and without Google Play Services (GMS). Firebase/FCM push notifications are an opt-in feature at build time; all other functionality works on GMS-less devices (F-Droid, LineageOS without OpenGApps, etc.).
|
||||||
|
|
||||||
|
## How the opt-in guard works
|
||||||
|
|
||||||
|
`android/app/build.gradle` applies the `com.google.gms.google-services` Gradle plugin only when **both** conditions are true:
|
||||||
|
|
||||||
|
1. `android/google-services.json` is present on disk
|
||||||
|
2. The Gradle property `firebaseEnabled` is explicitly passed
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
if (servicesJSON.exists() && servicesJSON.text && project.hasProperty('firebaseEnabled')) {
|
||||||
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This means the file can live on disk for development purposes without accidentally activating Firebase.
|
||||||
|
|
||||||
|
## Build commands
|
||||||
|
|
||||||
|
| Target | Command | Firebase |
|
||||||
|
|---|---|---|
|
||||||
|
| APK / sideload / Zapstore | `./gradlew assembleRelease` | off |
|
||||||
|
| Aurora / Play Store without FCM | `./gradlew bundleRelease` | off |
|
||||||
|
| Play Store with FCM push notifications | `./gradlew bundleRelease -PfirebaseEnabled` | on |
|
||||||
|
| F-Droid | `./gradlew assembleRelease` | off (required) |
|
||||||
|
|
||||||
|
## Behavior on non-GMS devices
|
||||||
|
|
||||||
|
When built with `-PfirebaseEnabled`, Firebase SDKs check for GMS availability at startup and degrade gracefully if it is absent:
|
||||||
|
|
||||||
|
- Firebase initializes but detects no GMS
|
||||||
|
- FCM skips token registration silently (no token, no notifications)
|
||||||
|
- The app continues to work normally
|
||||||
|
|
||||||
|
This means a single Play Store AAB (`bundleRelease -PfirebaseEnabled`) covers both GMS and non-GMS users. GMS users get push notifications; non-GMS users get a fully functional app without them.
|
||||||
|
|
||||||
|
**Aurora Store** pulls the exact APK from Play Store servers, so Aurora users get whichever variant was uploaded. The Play Store AAB built with `-PfirebaseEnabled` is correct for Aurora.
|
||||||
|
|
||||||
|
**F-Droid** is stricter: their build policy rejects any APK with GMS dependencies at the binary level, even with graceful degradation. F-Droid submission would require a separate `assembleRelease` build (no flag) and a dedicated F-Droid listing.
|
||||||
|
|
||||||
|
## The `google-services.json` file
|
||||||
|
|
||||||
|
- Gitignored (`android/.gitignore` line 80) — never commit it
|
||||||
|
- Contains Firebase project credentials (project number, app ID, API key)
|
||||||
|
- Safe to leave on disk; has no effect unless `-PfirebaseEnabled` is passed
|
||||||
|
- Obtain from the Firebase console: Project Settings → Your apps → Android app → Download `google-services.json`
|
||||||
|
|
||||||
|
## Known GMS dependency: MLKit barcode scanner
|
||||||
|
|
||||||
|
`@capacitor-mlkit/barcode-scanning` unconditionally depends on `com.google.android.gms:play-services-code-scanner` (present since the plugin was first added at v6.0.0). This merges `com.google.android.gms.version` and `GoogleApiActivity` into the APK manifest regardless of the `-PfirebaseEnabled` flag.
|
||||||
|
|
||||||
|
Practical impact:
|
||||||
|
- **GMS devices**: barcode scanning works normally
|
||||||
|
- **Non-GMS devices**: barcode scanning fails at scan time (not at startup); the app launches and runs normally otherwise
|
||||||
|
|
||||||
|
This is an accepted trade-off. Removing it would require either forking the plugin or introducing a `foss` product flavor that excludes the MLKit plugin entirely — work to undertake if/when F-Droid submission is planned.
|
||||||
|
|
||||||
|
## Incident: June 2026
|
||||||
|
|
||||||
|
Jose Olarte III's `notify-api` branch placed a production `google-services.json` in `android/` to test Firebase Cloud Messaging. The branch was never merged to `master`, but because the file is gitignored it persisted on disk after switching branches. At the time, the Gradle conditional activated Firebase based on file presence alone (no opt-in flag), so all subsequent local builds embedded Firebase and required Google Play Services. This silently broke APK/Aurora/Zapstore distribution.
|
||||||
|
|
||||||
|
**Fix applied:** deleted `google-services.json` from disk, changed the Gradle conditional to require `-PfirebaseEnabled`, and documented the rule in `AGENTS.md`.
|
||||||
@@ -89,7 +89,7 @@ system to a standardized, single-source asset configuration approach using
|
|||||||
resources/ # Image sources ONLY
|
resources/ # Image sources ONLY
|
||||||
icon.png
|
icon.png
|
||||||
splash.png
|
splash.png
|
||||||
splash_dark.png
|
splash-dark.png
|
||||||
|
|
||||||
config/assets/ # Versioned config & schema
|
config/assets/ # Versioned config & schema
|
||||||
capacitor-assets.config.json
|
capacitor-assets.config.json
|
||||||
|
|||||||
@@ -0,0 +1,308 @@
|
|||||||
|
# Deep Link Debugging Guide for TimeSafari
|
||||||
|
|
||||||
|
This guide helps you debug and fix deep link issues in the TimeSafari Capacitor application.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
1. **Check logs**: Use browser dev tools or device console to see detailed logging
|
||||||
|
2. **Test manually**: Use the testing script or browser console commands
|
||||||
|
3. **Verify configuration**: Ensure all platform configurations are correct
|
||||||
|
|
||||||
|
## Common Issues and Solutions
|
||||||
|
|
||||||
|
### Issue 1: App opens but doesn't navigate to the correct page
|
||||||
|
|
||||||
|
**Symptoms:**
|
||||||
|
- Deep link opens the app
|
||||||
|
- App stays on home screen or current page
|
||||||
|
- No error messages visible
|
||||||
|
|
||||||
|
**Debugging Steps:**
|
||||||
|
|
||||||
|
1. **Check console logs** in browser dev tools or device console:
|
||||||
|
```bash
|
||||||
|
# Android
|
||||||
|
adb logcat | grep -E "(TimeSafari|DeepLink|appUrlOpen)"
|
||||||
|
|
||||||
|
# iOS Simulator
|
||||||
|
xcrun simctl spawn booted log stream --predicate 'process == "TimeSafari"'
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Verify listener registration**:
|
||||||
|
Look for these log messages:
|
||||||
|
```
|
||||||
|
[DeepLink] Registering appUrlOpen listener...
|
||||||
|
[DeepLink] Listener registered successfully
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Check for event reception**:
|
||||||
|
Look for:
|
||||||
|
```
|
||||||
|
[DeepLink] ========== DEEP LINK EVENT RECEIVED ==========
|
||||||
|
[DeepLink] URL: timesafari://your/url/here
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Verify URL parsing**:
|
||||||
|
Check if URL components are parsed correctly:
|
||||||
|
```
|
||||||
|
[DeepLinkHandler.parseDeepLink] Parse result: {"path":"claim","params":{"id":"123"},"query":{}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue 2: Listener not receiving events
|
||||||
|
|
||||||
|
**Symptoms:**
|
||||||
|
- No deep link logs appear
|
||||||
|
- App opens but no event processing
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
1. **Rebuild and reinstall** the app completely:
|
||||||
|
```bash
|
||||||
|
npm run build:capacitor
|
||||||
|
npx cap sync
|
||||||
|
npx cap run android # or ios
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Check capacitor.config.json**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plugins": {
|
||||||
|
"App": {
|
||||||
|
"appUrlOpen": {
|
||||||
|
"handlers": [
|
||||||
|
{
|
||||||
|
"url": "timesafari://*",
|
||||||
|
"autoVerify": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Verify native configuration**:
|
||||||
|
- **Android**: Check `android/app/src/main/AndroidManifest.xml`
|
||||||
|
- **iOS**: Check `ios/App/App/Info.plist`
|
||||||
|
|
||||||
|
### Issue 3: URL scheme not recognized by OS
|
||||||
|
|
||||||
|
**Symptoms:**
|
||||||
|
- "No app found to handle this link" error
|
||||||
|
- OS doesn't open your app
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
1. **Android**: Verify intent filter in AndroidManifest.xml:
|
||||||
|
```xml
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="timesafari" />
|
||||||
|
</intent-filter>
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **iOS**: Verify URL types in Info.plist:
|
||||||
|
```xml
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>app.timesafari</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>timesafari</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Tools
|
||||||
|
|
||||||
|
### 1. Automated Testing Script
|
||||||
|
|
||||||
|
Use the provided testing script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test all URLs on Android
|
||||||
|
./scripts/test-deep-links.sh android
|
||||||
|
|
||||||
|
# Test specific URL on iOS
|
||||||
|
./scripts/test-deep-links.sh ios "timesafari://claim/test123"
|
||||||
|
|
||||||
|
# Monitor logs
|
||||||
|
./scripts/test-deep-links.sh android-logs
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Manual Testing Commands
|
||||||
|
|
||||||
|
**Android Emulator:**
|
||||||
|
```bash
|
||||||
|
adb shell am start -W -a android.intent.action.VIEW -d "timesafari://claim/test123" app.timesafari
|
||||||
|
```
|
||||||
|
|
||||||
|
**iOS Simulator:**
|
||||||
|
```bash
|
||||||
|
xcrun simctl openurl booted "timesafari://claim/test123"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Browser Console Testing
|
||||||
|
|
||||||
|
For web/PWA testing, use the browser console:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Test deep link processing directly
|
||||||
|
window.testSingleDeepLink("timesafari://claim/test123");
|
||||||
|
|
||||||
|
// Run all test URLs
|
||||||
|
window.testDeepLinks();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging Steps Checklist
|
||||||
|
|
||||||
|
### Pre-Testing Setup
|
||||||
|
|
||||||
|
- [ ] App is installed on device/emulator
|
||||||
|
- [ ] App has been launched at least once
|
||||||
|
- [ ] Device/emulator is properly connected
|
||||||
|
- [ ] Debugging tools are accessible
|
||||||
|
|
||||||
|
### During Testing
|
||||||
|
|
||||||
|
- [ ] Check console for initialization logs
|
||||||
|
- [ ] Verify listener registration
|
||||||
|
- [ ] Test with simple URL first (e.g., `timesafari://claim/test`)
|
||||||
|
- [ ] Monitor URL parsing logs
|
||||||
|
- [ ] Check router navigation logs
|
||||||
|
|
||||||
|
### Post-Testing Analysis
|
||||||
|
|
||||||
|
- [ ] Review complete log sequence
|
||||||
|
- [ ] Identify where process fails
|
||||||
|
- [ ] Check error messages for clues
|
||||||
|
- [ ] Test with different URL formats
|
||||||
|
|
||||||
|
## Common Log Patterns
|
||||||
|
|
||||||
|
### Successful Deep Link Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
[DeepLink] Registering appUrlOpen listener...
|
||||||
|
[DeepLink] Listener registered successfully
|
||||||
|
[DeepLink] ========== DEEP LINK EVENT RECEIVED ==========
|
||||||
|
[DeepLink] URL: timesafari://claim/test123
|
||||||
|
[DeepLinkHandler] Starting handleDeepLink with URL: timesafari://claim/test123
|
||||||
|
[DeepLinkHandler.parseDeepLink] Parse result: {"path":"claim","params":{"id":"test123"},"query":{}}
|
||||||
|
[DeepLinkHandler.validateAndRoute] Route validation passed. Route name: claim
|
||||||
|
[DeepLinkHandler.validateAndRoute] Router navigation completed successfully
|
||||||
|
[DeepLink] Deep link handled successfully
|
||||||
|
```
|
||||||
|
|
||||||
|
### Failed URL Parsing
|
||||||
|
|
||||||
|
```
|
||||||
|
[DeepLinkHandler.parseDeepLink] Route not found: invalid-route
|
||||||
|
[DeepLinkHandler.parseDeepLink] Available routes: ["claim","project","contact-import",...]
|
||||||
|
[DeepLinkHandler.validateAndRoute] Redirecting to deep-link-error page
|
||||||
|
```
|
||||||
|
|
||||||
|
### Router Navigation Issues
|
||||||
|
|
||||||
|
```
|
||||||
|
[DeepLinkHandler.validateAndRoute] Error routing to route name claim
|
||||||
|
[DeepLinkHandler.validateAndRoute] Navigation params: {"name":"claim","params":{"id":"test123"}}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Platform-Specific Issues
|
||||||
|
|
||||||
|
### Android
|
||||||
|
|
||||||
|
**Issue**: Deep links work in development but not in production build
|
||||||
|
- **Solution**: Ensure `android:exported="true"` in MainActivity
|
||||||
|
|
||||||
|
**Issue**: App doesn't respond to links when running in background
|
||||||
|
- **Solution**: Check `android:launchMode="singleTask"` in AndroidManifest.xml
|
||||||
|
|
||||||
|
### iOS
|
||||||
|
|
||||||
|
**Issue**: Deep links don't work in iOS simulator
|
||||||
|
- **Solution**: Use `xcrun simctl openurl` instead of opening URLs in Safari
|
||||||
|
|
||||||
|
**Issue**: App launches but doesn't process URL
|
||||||
|
- **Solution**: Check for Associated Domains if using universal links
|
||||||
|
|
||||||
|
## Advanced Debugging
|
||||||
|
|
||||||
|
### Enable Capacitor Native Logging
|
||||||
|
|
||||||
|
Add to `capacitor.config.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ios": {
|
||||||
|
"loggingBehavior": "debug"
|
||||||
|
},
|
||||||
|
"android": {
|
||||||
|
"loggingBehavior": "debug"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add Custom Debug Points
|
||||||
|
|
||||||
|
Insert additional logging in your deep link handler:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Add at strategic points in DeepLinkHandler
|
||||||
|
console.log('[DEBUG] Custom checkpoint:', { data: yourData });
|
||||||
|
```
|
||||||
|
|
||||||
|
### Network Debugging
|
||||||
|
|
||||||
|
If deep links involve network requests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Monitor network traffic (Android)
|
||||||
|
adb shell dumpsys connectivity
|
||||||
|
|
||||||
|
# Monitor network traffic (iOS)
|
||||||
|
# Use Xcode Network Debugger
|
||||||
|
```
|
||||||
|
|
||||||
|
## Recovery Strategies
|
||||||
|
|
||||||
|
### If deep links stop working completely:
|
||||||
|
|
||||||
|
1. **Clean rebuild**:
|
||||||
|
```bash
|
||||||
|
rm -rf node_modules
|
||||||
|
npm install
|
||||||
|
npm run build:capacitor
|
||||||
|
npx cap sync
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Reset device/emulator**:
|
||||||
|
- Clear app data
|
||||||
|
- Uninstall and reinstall
|
||||||
|
- Restart emulator
|
||||||
|
|
||||||
|
3. **Verify basic functionality**:
|
||||||
|
- Test simple navigation within app
|
||||||
|
- Test URL schemes with minimal URLs
|
||||||
|
- Gradually increase complexity
|
||||||
|
|
||||||
|
## Support Resources
|
||||||
|
|
||||||
|
- [Capacitor Deep Links Documentation](https://capacitorjs.com/docs/guides/deep-links)
|
||||||
|
- [Android Intent Filter Guide](https://developer.android.com/guide/components/intents-filters)
|
||||||
|
- [iOS URL Scheme Guide](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app)
|
||||||
|
|
||||||
|
## Contact and Feedback
|
||||||
|
|
||||||
|
If you encounter issues not covered in this guide:
|
||||||
|
|
||||||
|
1. Check the project's issue tracker
|
||||||
|
2. Review recent commits for deep link changes
|
||||||
|
3. Test with minimal reproduction case
|
||||||
|
4. Document exact steps and environment details
|
||||||
@@ -524,7 +524,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
|
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 68;
|
CURRENT_PROJECT_VERSION = 70;
|
||||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||||
ENABLE_APP_SANDBOX = NO;
|
ENABLE_APP_SANDBOX = NO;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
@@ -535,7 +535,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.4.2;
|
MARKETING_VERSION = 1.4.4;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
@@ -553,7 +553,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
|
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 68;
|
CURRENT_PROJECT_VERSION = 70;
|
||||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||||
ENABLE_APP_SANDBOX = NO;
|
ENABLE_APP_SANDBOX = NO;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
@@ -564,7 +564,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.4.2;
|
MARKETING_VERSION = 1.4.4;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
||||||
@@ -582,7 +582,7 @@
|
|||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = TimeSafariShareExtension/TimeSafariShareExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = TimeSafariShareExtension/TimeSafariShareExtension.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 68;
|
CURRENT_PROJECT_VERSION = 70;
|
||||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -596,7 +596,7 @@
|
|||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MARKETING_VERSION = 1.4.2;
|
MARKETING_VERSION = 1.4.4;
|
||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari.TimeSafariShareExtension;
|
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari.TimeSafariShareExtension;
|
||||||
@@ -620,7 +620,7 @@
|
|||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = TimeSafariShareExtension/TimeSafariShareExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = TimeSafariShareExtension/TimeSafariShareExtension.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 68;
|
CURRENT_PROJECT_VERSION = 70;
|
||||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -634,7 +634,7 @@
|
|||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MARKETING_VERSION = 1.4.2;
|
MARKETING_VERSION = 1.4.4;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari.TimeSafariShareExtension;
|
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari.TimeSafariShareExtension;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"filename" : "splash@1x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "splash@2x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "splash@3x.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 156 KiB |
@@ -76,7 +76,7 @@ PODS:
|
|||||||
- SQLCipher/common (4.10.0)
|
- SQLCipher/common (4.10.0)
|
||||||
- SQLCipher/standard (4.10.0):
|
- SQLCipher/standard (4.10.0):
|
||||||
- SQLCipher/common
|
- SQLCipher/common
|
||||||
- TimesafariDailyNotificationPlugin (2.0.0):
|
- TimesafariDailyNotificationPlugin (4.0.1):
|
||||||
- Capacitor
|
- Capacitor
|
||||||
- ZIPFoundation (0.9.20)
|
- ZIPFoundation (0.9.20)
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@ SPEC CHECKSUMS:
|
|||||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||||
SQLCipher: eb79c64049cb002b4e9fcb30edb7979bf4706dfc
|
SQLCipher: eb79c64049cb002b4e9fcb30edb7979bf4706dfc
|
||||||
TimesafariDailyNotificationPlugin: 3c12e8c39fc27f689f56cf4e57230a8c28611fcc
|
TimesafariDailyNotificationPlugin: 69277c884380a9a620f671b68e0327eaa4b3d27d
|
||||||
ZIPFoundation: dfd3d681c4053ff7e2f7350bc4e53b5dba3f5351
|
ZIPFoundation: dfd3d681c4053ff7e2f7350bc4e53b5dba3f5351
|
||||||
|
|
||||||
PODFILE CHECKSUM: 87c07d03f36ef38ab0c873802aee1ce9b5d34448
|
PODFILE CHECKSUM: 87c07d03f36ef38ab0c873802aee1ce9b5d34448
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
"name": "giftopia",
|
"name": "giftopia",
|
||||||
"version": "1.4.2",
|
"version": "1.4.4",
|
||||||
"description": "Giftopia App",
|
"description": "Giftopia App",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Gift Economies Team"
|
"name": "Gift Economies Team"
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
"prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node sw_combine.js && node scripts/copy-wasm.js",
|
"prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node sw_combine.js && node scripts/copy-wasm.js",
|
||||||
"test:prerequisites": "node scripts/check-prerequisites.js",
|
"test:prerequisites": "node scripts/check-prerequisites.js",
|
||||||
"check:dependencies": "./scripts/check-dependencies.sh",
|
"check:dependencies": "./scripts/check-dependencies.sh",
|
||||||
|
"deps:update-daily-notification-plugin": "npm install @timesafari/daily-notification-plugin@git+https://gitea.anomalistdesign.com/trent_larson/daily-notification-plugin.git#master",
|
||||||
"test:all": "npm run lint && tsc && npm run test:web && npm run test:mobile && ./scripts/test-safety-check.sh && echo '\n\n\nGotta add the performance tests'",
|
"test:all": "npm run lint && tsc && npm run test:web && npm run test:mobile && ./scripts/test-safety-check.sh && echo '\n\n\nGotta add the performance tests'",
|
||||||
"test:web": "npx playwright test -c playwright.config-local.ts --trace on",
|
"test:web": "npx playwright test -c playwright.config-local.ts --trace on",
|
||||||
"test:mobile": "./scripts/test-mobile.sh",
|
"test:mobile": "./scripts/test-mobile.sh",
|
||||||
|
|||||||
@@ -56,7 +56,10 @@ npx capacitor-assets generate --web
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Asset generation is configured in `capacitor-assets.config.json` at the project root.
|
`resources/` is this project's canonical asset source. `@capacitor/assets`
|
||||||
|
prioritizes a top-level `assets/` directory over `resources/`, so a legacy
|
||||||
|
`assets/` directory can prevent these assets from being discovered. Remove that
|
||||||
|
directory when it is empty or obsolete.
|
||||||
|
|
||||||
## Version Control
|
## Version Control
|
||||||
|
|
||||||
|
|||||||
|
After Width: | Height: | Size: 602 KiB |
|
After Width: | Height: | Size: 223 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 279 KiB After Width: | Height: | Size: 624 KiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 3.3 MiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 279 KiB After Width: | Height: | Size: 624 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 279 KiB After Width: | Height: | Size: 624 KiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 3.3 MiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 3.3 MiB |
|
Before Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 279 KiB After Width: | Height: | Size: 624 KiB |
@@ -92,7 +92,7 @@ function generateAssetConfig(): AssetConfig {
|
|||||||
},
|
},
|
||||||
splash: {
|
splash: {
|
||||||
source: "resources/splash.png",
|
source: "resources/splash.png",
|
||||||
darkSource: "resources/splash_dark.png",
|
darkSource: "resources/splash-dark.png",
|
||||||
android: {
|
android: {
|
||||||
scale: "cover",
|
scale: "cover",
|
||||||
target: "android/app/src/main/res"
|
target: "android/app/src/main/res"
|
||||||
@@ -138,7 +138,7 @@ function validateSourceFiles(): void {
|
|||||||
const requiredFiles = [
|
const requiredFiles = [
|
||||||
'resources/icon.png',
|
'resources/icon.png',
|
||||||
'resources/splash.png',
|
'resources/splash.png',
|
||||||
'resources/splash_dark.png'
|
'resources/splash-dark.png'
|
||||||
];
|
];
|
||||||
|
|
||||||
const missingFiles = requiredFiles.filter(file => {
|
const missingFiles = requiredFiles.filter(file => {
|
||||||
|
|||||||
@@ -230,8 +230,8 @@ validate_android_assets() {
|
|||||||
missing_assets+=("resources/splash.png")
|
missing_assets+=("resources/splash.png")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -f "resources/splash_dark.png" ]; then
|
if [ ! -f "resources/splash-dark.png" ]; then
|
||||||
missing_assets+=("resources/splash_dark.png")
|
missing_assets+=("resources/splash-dark.png")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ${#missing_assets[@]} -gt 0 ]; then
|
if [ ${#missing_assets[@]} -gt 0 ]; then
|
||||||
@@ -278,14 +278,14 @@ validate_android_assets() {
|
|||||||
# Copy source assets to assets directory for capacitor-assets
|
# Copy source assets to assets directory for capacitor-assets
|
||||||
cp resources/icon.png assets/ 2>/dev/null || log_warn "Could not copy icon.png"
|
cp resources/icon.png assets/ 2>/dev/null || log_warn "Could not copy icon.png"
|
||||||
cp resources/splash.png assets/ 2>/dev/null || log_warn "Could not copy splash.png"
|
cp resources/splash.png assets/ 2>/dev/null || log_warn "Could not copy splash.png"
|
||||||
cp resources/splash_dark.png assets/ 2>/dev/null || log_warn "Could not copy splash_dark.png"
|
cp resources/splash-dark.png assets/ 2>/dev/null || log_warn "Could not copy splash-dark.png"
|
||||||
|
|
||||||
# Generate assets
|
# Generate assets
|
||||||
if npx @capacitor/assets generate >/dev/null 2>&1; then
|
if npx @capacitor/assets generate >/dev/null 2>&1; then
|
||||||
log_success "Android assets regenerated successfully"
|
log_success "Android assets regenerated successfully"
|
||||||
|
|
||||||
# Clean up temporary assets
|
# Clean up temporary assets
|
||||||
rm -f assets/icon.png assets/splash.png assets/splash_dark.png
|
rm -f assets/icon.png assets/splash.png assets/splash-dark.png
|
||||||
|
|
||||||
# Verify the resources were created
|
# Verify the resources were created
|
||||||
local verification_failed=false
|
local verification_failed=false
|
||||||
|
|||||||
@@ -172,12 +172,12 @@ check_ios_resources() {
|
|||||||
log_info "Checking iOS resources..."
|
log_info "Checking iOS resources..."
|
||||||
|
|
||||||
# Check for required assets
|
# Check for required assets
|
||||||
if [ ! -f "assets/icon.png" ]; then
|
if [ ! -f "resources/icon.png" ]; then
|
||||||
log_warn "App icon not found at assets/icon.png"
|
log_warn "App icon not found at resources/icon.png"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -f "assets/splash.png" ]; then
|
if [ ! -f "resources/splash.png" ]; then
|
||||||
log_warn "Splash screen not found at assets/splash.png"
|
log_warn "Splash screen not found at resources/splash.png"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check for iOS-specific files
|
# Check for iOS-specific files
|
||||||
@@ -192,6 +192,118 @@ check_ios_resources() {
|
|||||||
log_success "iOS resource check completed"
|
log_success "iOS resource check completed"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# iOS app icon appearance variants (Dark, Tinted, …).
|
||||||
|
# Luminosity appearance values match Apple's asset catalog format (iOS 18+).
|
||||||
|
readonly _IOS_APP_ICON_APPEARANCE_LUMINOSITY="luminosity"
|
||||||
|
readonly _IOS_APP_ICON_APPEARANCE_DARK="dark"
|
||||||
|
readonly _IOS_APP_ICON_APPEARANCE_TINTED="tinted"
|
||||||
|
|
||||||
|
# Each row: source_file|luminosity_value|dest_filename
|
||||||
|
# To add a variant: append one row and place the source PNG under resources/ios/.
|
||||||
|
readonly _IOS_APP_ICON_APPEARANCE_VARIANTS=(
|
||||||
|
"resources/ios/icon/icon-dark.png|${_IOS_APP_ICON_APPEARANCE_DARK}|AppIcon-Dark.png"
|
||||||
|
"resources/ios/icon/icon-tinted.png|${_IOS_APP_ICON_APPEARANCE_TINTED}|AppIcon-Tinted.png"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update AppIcon.appiconset/Contents.json with one 1024×1024 appearance entry.
|
||||||
|
# Preserves all other images (including capacitor-assets output); replaces any
|
||||||
|
# existing entry for the same luminosity appearance.
|
||||||
|
_update_appicon_contents_for_appearance() {
|
||||||
|
local contents_json="$1"
|
||||||
|
local dest_filename="$2"
|
||||||
|
local luminosity_value="$3"
|
||||||
|
|
||||||
|
local tmp_file
|
||||||
|
tmp_file="$(mktemp)"
|
||||||
|
|
||||||
|
if ! jq --arg filename "$dest_filename" \
|
||||||
|
--arg appearance "$_IOS_APP_ICON_APPEARANCE_LUMINOSITY" \
|
||||||
|
--arg value "$luminosity_value" \
|
||||||
|
'
|
||||||
|
.images |= map(select((.appearances[0].value // "") != $value))
|
||||||
|
| .images += [{
|
||||||
|
"appearances": [{
|
||||||
|
"appearance": $appearance,
|
||||||
|
"value": $value
|
||||||
|
}],
|
||||||
|
"filename": $filename,
|
||||||
|
"idiom": "universal",
|
||||||
|
"platform": "ios",
|
||||||
|
"size": "1024x1024"
|
||||||
|
}]
|
||||||
|
' "$contents_json" > "$tmp_file"; then
|
||||||
|
rm -f "$tmp_file"
|
||||||
|
log_error "Failed to update AppIcon Contents.json for appearance: $luminosity_value"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv "$tmp_file" "$contents_json"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install one appearance variant when its source PNG exists.
|
||||||
|
_apply_ios_app_icon_appearance_variant() {
|
||||||
|
local appiconset_dir="$1"
|
||||||
|
local contents_json="$2"
|
||||||
|
local source_file="$3"
|
||||||
|
local luminosity_value="$4"
|
||||||
|
local dest_filename="$5"
|
||||||
|
|
||||||
|
if [ ! -f "$source_file" ]; then
|
||||||
|
log_info "iOS app icon appearance variant not found ($source_file) — skipping"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "$appiconset_dir" ]; then
|
||||||
|
log_warn "AppIcon.appiconset not found — skipping appearance variant ($luminosity_value)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! cp "$source_file" "$appiconset_dir/$dest_filename"; then
|
||||||
|
log_error "Failed to copy $source_file to $appiconset_dir/$dest_filename"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Installed iOS app icon appearance variant: $luminosity_value ($dest_filename)"
|
||||||
|
_update_appicon_contents_for_appearance "$contents_json" "$dest_filename" "$luminosity_value"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Post-process capacitor-assets iOS icons: copy optional Dark/Tinted sources and
|
||||||
|
# register them in AppIcon.appiconset/Contents.json.
|
||||||
|
apply_ios_app_icon_appearances() {
|
||||||
|
local appiconset_dir="ios/App/App/Assets.xcassets/AppIcon.appiconset"
|
||||||
|
local contents_json="$appiconset_dir/Contents.json"
|
||||||
|
|
||||||
|
if [ ! -f "$contents_json" ]; then
|
||||||
|
log_info "AppIcon Contents.json not found — skipping appearance variants"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v jq &> /dev/null; then
|
||||||
|
log_error "jq is required to install iOS app icon appearance variants (install with: brew install jq)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local variant source_file luminosity_value dest_filename
|
||||||
|
for variant in "${_IOS_APP_ICON_APPEARANCE_VARIANTS[@]}"; do
|
||||||
|
IFS='|' read -r source_file luminosity_value dest_filename <<< "$variant"
|
||||||
|
_apply_ios_app_icon_appearance_variant \
|
||||||
|
"$appiconset_dir" "$contents_json" \
|
||||||
|
"$source_file" "$luminosity_value" "$dest_filename"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate iOS assets (capacitor-assets), then apply optional appearance variants.
|
||||||
|
generate_ios_assets() {
|
||||||
|
ensure_ios_capacitor_asset_directories
|
||||||
|
if [ -d "assets" ]; then
|
||||||
|
log_warn "@capacitor/assets prioritizes the top-level assets/ directory over resources/."
|
||||||
|
log_warn "This project intentionally uses resources/ as the canonical asset source."
|
||||||
|
log_warn "Remove the legacy assets/ directory if it is empty or obsolete."
|
||||||
|
fi
|
||||||
|
npx capacitor-assets generate --ios
|
||||||
|
apply_ios_app_icon_appearances
|
||||||
|
}
|
||||||
|
|
||||||
# Function to clean iOS build
|
# Function to clean iOS build
|
||||||
clean_ios_build() {
|
clean_ios_build() {
|
||||||
log_info "Cleaning iOS build artifacts..."
|
log_info "Cleaning iOS build artifacts..."
|
||||||
@@ -424,8 +536,7 @@ fi
|
|||||||
# Handle assets-only mode
|
# Handle assets-only mode
|
||||||
if [ "$ASSETS_ONLY" = true ]; then
|
if [ "$ASSETS_ONLY" = true ]; then
|
||||||
log_info "Assets-only mode: generating assets"
|
log_info "Assets-only mode: generating assets"
|
||||||
ensure_ios_capacitor_asset_directories
|
safe_execute "Generating assets" "generate_ios_assets" || exit 7
|
||||||
safe_execute "Generating assets" "npx capacitor-assets generate --ios" || exit 7
|
|
||||||
log_success "Assets generation completed successfully!"
|
log_success "Assets generation completed successfully!"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
@@ -574,8 +685,7 @@ safe_execute "Installing CocoaPods dependencies" "run_pod_install_with_workaroun
|
|||||||
safe_execute "Syncing with Capacitor" "run_cap_sync_with_workaround" || exit 6
|
safe_execute "Syncing with Capacitor" "run_cap_sync_with_workaround" || exit 6
|
||||||
|
|
||||||
# Step 7: Generate assets
|
# Step 7: Generate assets
|
||||||
ensure_ios_capacitor_asset_directories
|
safe_execute "Generating assets" "generate_ios_assets" || exit 7
|
||||||
safe_execute "Generating assets" "npx capacitor-assets generate --ios" || exit 7
|
|
||||||
|
|
||||||
# Step 8: Build iOS app
|
# Step 8: Build iOS app
|
||||||
safe_execute "Building iOS app" "build_ios_app" || exit 5
|
safe_execute "Building iOS app" "build_ios_app" || exit 5
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ notifications for conflicted entities * - Template streamlined with computed CSS
|
|||||||
properties * * @author Matthew Raymer */
|
properties * * @author Matthew Raymer */
|
||||||
<template>
|
<template>
|
||||||
<div id="sectionGiftedGiver">
|
<div id="sectionGiftedGiver">
|
||||||
<label class="block font-bold mb-1">
|
<label class="block font-semibold text-lg capitalize text-center">
|
||||||
{{ stepLabel }}
|
{{ stepLabel }}
|
||||||
</label>
|
</label>
|
||||||
<!-- Toggle link for entity type selection -->
|
<!-- Toggle link for entity type selection -->
|
||||||
<div class="text-right mb-4">
|
<div class="text-center mb-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="text-sm text-blue-600 hover:text-blue-800 underline font-medium"
|
class="text-xs text-blue-600 hover:underline uppercase"
|
||||||
@click="handleToggleEntityType"
|
@click="handleToggleEntityType"
|
||||||
>
|
>
|
||||||
{{ toggleLinkText }}
|
{{ toggleLinkText }}
|
||||||
|
|||||||
@@ -62,10 +62,6 @@ export class NativeNotificationService implements NotificationServiceInterface {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Request notification permissions from the OS
|
|
||||||
* Shows native permission dialog on first call
|
|
||||||
*/
|
|
||||||
/**
|
/**
|
||||||
* Request notification permissions from the OS
|
* Request notification permissions from the OS
|
||||||
* Shows native permission dialog on first call
|
* Shows native permission dialog on first call
|
||||||
|
|||||||
@@ -1402,54 +1402,6 @@ export default class AccountViewView extends Vue {
|
|||||||
}, 150);
|
}, 150);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle dev-only 10-minute rollover for daily reminder. Saves the setting and,
|
|
||||||
* if reminder is already on, reschedules so the plugin uses the new interval.
|
|
||||||
*/
|
|
||||||
async toggleReminderFastRollover(): Promise<void> {
|
|
||||||
const next = !this.reminderFastRolloverForTesting;
|
|
||||||
await this.$saveSettings({
|
|
||||||
reminderFastRolloverForTesting: next,
|
|
||||||
});
|
|
||||||
this.reminderFastRolloverForTesting = next;
|
|
||||||
|
|
||||||
if (this.notifyingReminder) {
|
|
||||||
try {
|
|
||||||
const service = NotificationService.getInstance();
|
|
||||||
if (Capacitor.getPlatform() !== "android") {
|
|
||||||
await service.cancelDailyNotification();
|
|
||||||
}
|
|
||||||
const time24h = this.parseTimeTo24Hour(this.notifyingReminderTime);
|
|
||||||
const title = "Daily Reminder";
|
|
||||||
const body =
|
|
||||||
this.notifyingReminderMessage ||
|
|
||||||
"Click to share some gratitude with the world -- even if they're unnamed.";
|
|
||||||
await service.scheduleDailyNotification({
|
|
||||||
time: time24h,
|
|
||||||
title,
|
|
||||||
body,
|
|
||||||
priority: "normal",
|
|
||||||
...(next ? { rolloverIntervalMinutes: 10 } : {}),
|
|
||||||
});
|
|
||||||
this.notify.success(
|
|
||||||
next
|
|
||||||
? "Reminder will repeat every 10 minutes (testing)."
|
|
||||||
: "Reminder will repeat daily (24h).",
|
|
||||||
TIMEOUTS.STANDARD,
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(
|
|
||||||
"[AccountViewView] Reschedule after fast-rollover toggle failed:",
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
this.notify.error(
|
|
||||||
"Failed to update reminder interval. Please try again.",
|
|
||||||
TIMEOUTS.STANDARD,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse time string (e.g., "5:22 PM") to 24-hour format (e.g., "17:22")
|
* Parse time string (e.g., "5:22 PM") to 24-hour format (e.g., "17:22")
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -109,12 +109,14 @@ Raymer * @version 1.0.0 */
|
|||||||
|
|
||||||
<div v-if="isUserRegistered" id="sectionRecordSomethingGiven">
|
<div v-if="isUserRegistered" id="sectionRecordSomethingGiven">
|
||||||
<!-- Record Quick-Action -->
|
<!-- Record Quick-Action -->
|
||||||
<div class="mb-6">
|
<div
|
||||||
<div class="flex gap-2 items-center mb-2">
|
class="bg-slate-100 border border-slate-300 rounded-md px-4 py-2.5 mb-4"
|
||||||
|
>
|
||||||
|
<div class="flex gap-2 justify-between items-center ps-8">
|
||||||
<!-- Thank button - always visible and unchanged -->
|
<!-- Thank button - always visible and unchanged -->
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="text-center text-base uppercase bg-gradient-to-b from-green-400 to-green-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white flex items-center justify-center gap-2 px-4 py-3 rounded-full"
|
class="text-center text-xl uppercase font-bold bg-gradient-to-b from-green-400 to-green-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white flex items-center justify-center gap-2 mx-auto px-6 py-2 rounded-lg"
|
||||||
@click="openPersonDialog()"
|
@click="openPersonDialog()"
|
||||||
>
|
>
|
||||||
<font-awesome icon="plus" />
|
<font-awesome icon="plus" />
|
||||||
@@ -122,25 +124,29 @@ Raymer * @version 1.0.0 */
|
|||||||
</button>
|
</button>
|
||||||
<!-- Plus button - appears when scrolled, positioned over house-chimney icon -->
|
<!-- Plus button - appears when scrolled, positioned over house-chimney icon -->
|
||||||
<transition
|
<transition
|
||||||
enter-active-class="transition-all duration-1000 ease-out"
|
enter-active-class="transition-all duration-500 ease-out"
|
||||||
leave-active-class="transition-all duration-1000 ease-in"
|
leave-active-class="transition-all duration-500 ease-in"
|
||||||
enter-from-class="scale-0"
|
enter-from-class="opacity-0"
|
||||||
enter-to-class="scale-100"
|
enter-to-class="opacity-100"
|
||||||
leave-from-class="scale-100"
|
leave-from-class="opacity-100"
|
||||||
leave-to-class="scale-0"
|
leave-to-class="opacity-0"
|
||||||
>
|
>
|
||||||
<button
|
<div
|
||||||
v-if="isScrolled"
|
v-if="isScrolled"
|
||||||
type="button"
|
class="bg-gradient-to-t from-white to-transparent fixed inset-x-0 bottom-[calc(4.75rem+max(env(safe-area-inset-bottom),var(--safe-area-inset-bottom,0px)))] px-4 pb-3 w-full z-[49]"
|
||||||
class="fixed bottom-10 p-4 w-14 h-14 z-50 text-center bg-gradient-to-b from-green-400 to-green-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white rounded-full flex items-center justify-center"
|
|
||||||
:style="getButtonPosition()"
|
|
||||||
@click="openPersonDialog()"
|
|
||||||
>
|
>
|
||||||
<font-awesome icon="plus" />
|
<button
|
||||||
</button>
|
type="button"
|
||||||
|
class="text-center text-xl uppercase font-bold bg-gradient-to-b from-green-400 to-green-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white flex items-center justify-center gap-2 mx-auto px-6 py-2 rounded-lg drop-shadow-[0_0_10px_rgba(255,255,255,1)]"
|
||||||
|
@click="openPersonDialog()"
|
||||||
|
>
|
||||||
|
<font-awesome icon="plus" />
|
||||||
|
<span>Thank</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
<button
|
<button
|
||||||
class="block ms-auto text-sm text-center text-white bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] p-1.5 rounded-full"
|
class="block text-sm text-center text-white bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] p-1.5 rounded-full"
|
||||||
@click="openGiftedPrompts()"
|
@click="openGiftedPrompts()"
|
||||||
>
|
>
|
||||||
<font-awesome
|
<font-awesome
|
||||||
@@ -559,7 +565,7 @@ export default class HomeView extends Vue {
|
|||||||
const scrollTop = appElement
|
const scrollTop = appElement
|
||||||
? appElement.scrollTop
|
? appElement.scrollTop
|
||||||
: window.pageYOffset || document.documentElement.scrollTop || 0;
|
: window.pageYOffset || document.documentElement.scrollTop || 0;
|
||||||
const shouldBeScrolled = scrollTop > 100;
|
const shouldBeScrolled = scrollTop > 120;
|
||||||
if (this.isScrolled !== shouldBeScrolled) {
|
if (this.isScrolled !== shouldBeScrolled) {
|
||||||
this.isScrolled = shouldBeScrolled;
|
this.isScrolled = shouldBeScrolled;
|
||||||
}
|
}
|
||||||
|
|||||||