Compare commits
67 Commits
deep_linki
...
registrati
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3dcbb3229 | ||
| 7b0ee2e44e | |||
| ac018997e8 | |||
| 6f449e9c1f | |||
| 543599a6a1 | |||
| 634395ff38 | |||
| da1f08ebaa | |||
| 4ee3ce0061 | |||
| 654c67af72 | |||
| b244f609b3 | |||
| 9c84302c2e | |||
| ca37c30180 | |||
| 130139e2af | |||
| 9802deb17c | |||
| 76c983ea3e | |||
| 114ef440b8 | |||
| b58d510f24 | |||
|
|
da6a5ee83e | ||
|
|
7af39d322f | ||
|
|
bab802160f | ||
|
|
01d7bc9e27 | ||
|
|
fa20360d87 | ||
|
|
770c0fa77c | ||
|
|
0709d0c726 | ||
|
|
d943983bf8 | ||
| be9465e9f8 | |||
| 5606f2a18a | |||
|
|
06e9950e53 | ||
|
|
5143c65337 | ||
|
|
09ee94d5a3 | ||
| 071792b97c | |||
| bf2f23021f | |||
| 829870b16c | |||
|
|
44ffeebabe | ||
|
|
bed3bfa387 | ||
| b1056fc8dd | |||
| 189bfabcf8 | |||
|
|
aed1a9fea8 | ||
|
|
f71c76fcd3 | ||
|
|
d024db2258 | ||
|
|
c760385dcf | ||
|
|
8be8de5f1f | ||
|
|
b40604f8a6 | ||
|
|
2660b91995 | ||
|
|
474999dc9c | ||
| e825950e6e | |||
| a73d0a85e2 | |||
| fc01e81af7 | |||
|
|
436f40813c | ||
|
|
77b296b606 | ||
|
|
683e85f5be | ||
| e3ac5fe9fe | |||
|
|
5dbd66e51b | ||
|
|
312b4aaaa3 | ||
|
|
3a6a24d923 | ||
|
|
d7afb80a07 | ||
|
|
751df09fe5 | ||
|
|
8858495f73 | ||
|
|
ecb088bee2 | ||
|
|
8f7d794962 | ||
|
|
fa7d6317b9 | ||
|
|
4a75cdf20e | ||
|
|
79fdb9e570 | ||
|
|
aa09827317 | ||
|
|
cc1780bd01 | ||
|
|
e5d9c25ad4 | ||
|
|
ef8c2e6093 |
16
.gitignore
vendored
@@ -38,30 +38,18 @@ pnpm-debug.log*
|
||||
/dist-capacitor/
|
||||
/test-playwright-results/
|
||||
playwright-tests
|
||||
test-playwright
|
||||
dist-electron-packages
|
||||
ios
|
||||
.ruby-version
|
||||
+.env
|
||||
|
||||
# Generated test files
|
||||
.generated/
|
||||
|
||||
# Fastlane
|
||||
ios/fastlane/report.xml
|
||||
ios/fastlane/Preview.html
|
||||
ios/fastlane/screenshots
|
||||
ios/fastlane/test_output
|
||||
android/fastlane/report.xml
|
||||
android/fastlane/Preview.html
|
||||
android/fastlane/screenshots
|
||||
android/fastlane/test_output
|
||||
.env.default
|
||||
vendor/
|
||||
|
||||
# Build logs
|
||||
build_logs/
|
||||
|
||||
# Android generated assets
|
||||
android/app/src/main/assets/public/assets/
|
||||
|
||||
# PWA icon files generated by capacitor-assets
|
||||
icons
|
||||
|
||||
176
BUILDING.md
@@ -4,31 +4,52 @@ This guide explains how to build TimeSafari for different platforms.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
For a quick dev environment setup, use [pkgx](https://pkgx.dev).
|
||||
|
||||
- Node.js (LTS version recommended)
|
||||
- npm (comes with Node.js)
|
||||
- Git
|
||||
- For iOS builds: macOS with Xcode installed
|
||||
- For Android builds: Android Studio with SDK installed
|
||||
- For iOS builds: macOS with Xcode and ruby gems & bundle
|
||||
- pkgx +rubygems.org sh
|
||||
|
||||
- ... and you may have to fix these, especially with pkgx
|
||||
|
||||
```bash
|
||||
gem_path=$(which gem)
|
||||
shortened_path="${gem_path:h:h}"
|
||||
export GEM_HOME=$shortened_path
|
||||
export GEM_PATH=$shortened_path
|
||||
```
|
||||
|
||||
- For desktop builds: Additional build tools based on your OS
|
||||
|
||||
## Forks
|
||||
|
||||
If you have forked this to make your own app, you'll want to customize the iOS & Android files. You can either edit existing ones, or you can remove the `ios` and `android` directories and regenerate them before the `npx cap sync` step in each setup.
|
||||
|
||||
```bash
|
||||
npx cap add android
|
||||
npx cap add ios
|
||||
```
|
||||
|
||||
You'll also want to edit the deep link configuration (see below).
|
||||
|
||||
## Initial Setup
|
||||
|
||||
1. Clone the repository:
|
||||
|
||||
```bash
|
||||
git clone [repository-url]
|
||||
cd TimeSafari
|
||||
```
|
||||
|
||||
2. Install dependencies:
|
||||
Install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
## Web Build
|
||||
## Web Dev Locally
|
||||
|
||||
To build for web deployment:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Web Build for Server
|
||||
|
||||
1. Run the production build:
|
||||
|
||||
@@ -36,17 +57,66 @@ To build for web deployment:
|
||||
npm run build
|
||||
```
|
||||
|
||||
2. The built files will be in the `dist` directory.
|
||||
The built files will be in the `dist` directory.
|
||||
|
||||
3. To test the production build locally:
|
||||
2. To test the production build locally:
|
||||
|
||||
You'll likely want to use test locations for the Endorser & image & partner servers; see "DEFAULT_ENDORSER_API_SERVER" & "DEFAULT_IMAGE_API_SERVER" & "DEFAULT_PARTNER_API_SERVER" below.
|
||||
|
||||
```bash
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compile and minify for test & production
|
||||
|
||||
* If there are DB changes: before updating the test server, open browser(s) with current version to test DB migrations.
|
||||
|
||||
* `npx prettier --write ./sw_scripts/`
|
||||
|
||||
* Update the ClickUp tasks & CHANGELOG.md & the version in package.json, run `npm install`.
|
||||
|
||||
* Commit everything (since the commit hash is used the app).
|
||||
|
||||
* Put the commit hash in the changelog (which will help you remember to bump the version later).
|
||||
|
||||
* Tag with the new version, [online](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/releases) or `git tag 0.3.55 && git push origin 0.3.55`.
|
||||
|
||||
* For test, build the app (because test server is not yet set up to build):
|
||||
|
||||
```bash
|
||||
TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_APP_SERVER=https://test.timesafari.app VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app VITE_DEFAULT_PARTNER_API_SERVER=https://test-partner-api.endorser.ch VITE_PASSKEYS_ENABLED=true npm run build
|
||||
```
|
||||
|
||||
... and transfer to the test server:
|
||||
|
||||
```bash
|
||||
rsync -azvu -e "ssh -i ~/.ssh/..." dist ubuntutest@test.timesafari.app:time-safari
|
||||
```
|
||||
|
||||
(Let's replace that with a .env.development or .env.staging file.)
|
||||
|
||||
(Note: The test BVC_MEETUPS_PROJECT_CLAIM_ID does not resolve as a URL because it's only in the test DB and the prod redirect won't redirect there.)
|
||||
|
||||
* For prod, get on the server and run the correct build:
|
||||
|
||||
... and log onto the server:
|
||||
|
||||
* `pkgx +npm sh`
|
||||
|
||||
* `cd crowd-funder-for-time-pwa && git checkout master && git pull && git checkout 0.3.55 && npm install && npm run build && cd -`
|
||||
|
||||
(The plain `npm run build` uses the .env.production file.)
|
||||
|
||||
* Back up the time-safari/dist folder & deploy: `mv time-safari/dist time-safari-dist-prev.0 && mv crowd-funder-for-time-pwa/dist time-safari/`
|
||||
|
||||
* Record the new hash in the changelog. Edit package.json to increment version & add "-beta", `npm install`, and commit. Also record what version is on production.
|
||||
|
||||
|
||||
|
||||
|
||||
## Desktop Build (Electron)
|
||||
|
||||
### Building for Linux
|
||||
### Linux Build
|
||||
|
||||
1. Build the electron app in production mode:
|
||||
|
||||
@@ -114,6 +184,17 @@ Prerequisites: macOS with Xcode installed
|
||||
npx cap sync ios
|
||||
```
|
||||
|
||||
3. Copy the assets:
|
||||
|
||||
```bash
|
||||
# It makes no sense why capacitor-assets will not run without these but it actually changes the contents.
|
||||
mkdir -p ios/App/App/Assets.xcassets/AppIcon.appiconset
|
||||
echo '{"images":[]}' > ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json
|
||||
mkdir -p ios/App/App/Assets.xcassets/Splash.imageset
|
||||
echo '{"images":[]}' > ios/App/App/Assets.xcassets/Splash.imageset/Contents.json
|
||||
npx capacitor-assets generate --ios
|
||||
```
|
||||
|
||||
3. Open the project in Xcode:
|
||||
|
||||
```bash
|
||||
@@ -122,15 +203,11 @@ Prerequisites: macOS with Xcode installed
|
||||
|
||||
4. Use Xcode to build and run on simulator or device.
|
||||
|
||||
If you have forked this to make your own app, you'll want to customize the ios files:
|
||||
#### First-time iOS Configuration
|
||||
|
||||
```bash
|
||||
rm -rf ios
|
||||
npx cap add ios
|
||||
```
|
||||
|
||||
... and then repeat the steps above.
|
||||
- Generate certificates inside XCode.
|
||||
|
||||
- Right-click on App and under Signing & Capabilities set the Team.
|
||||
|
||||
### Android Build
|
||||
|
||||
@@ -150,24 +227,21 @@ Prerequisites: Android Studio with SDK installed
|
||||
npx cap sync android
|
||||
```
|
||||
|
||||
3. Open the project in Android Studio:
|
||||
3. Copy the assets
|
||||
|
||||
```bash
|
||||
npx capacitor-assets generate --android
|
||||
```
|
||||
|
||||
4. Open the project in Android Studio:
|
||||
|
||||
```bash
|
||||
npx cap open android
|
||||
```
|
||||
|
||||
3. Use Android Studio to build and run on emulator or device.
|
||||
5. Use Android Studio to build and run on emulator or device.
|
||||
|
||||
If you have forked this to make your own app, you'll want to customize the android files:
|
||||
|
||||
```bash
|
||||
rm -rf android
|
||||
npx cap add android
|
||||
```
|
||||
|
||||
... and then: repeat the steps above, and look below for the deep link configuration.
|
||||
|
||||
## Building Android from the console
|
||||
## Android Build from the console
|
||||
|
||||
```bash
|
||||
cd android
|
||||
@@ -177,7 +251,21 @@ If you have forked this to make your own app, you'll want to customize the andro
|
||||
npx cap run android
|
||||
```
|
||||
|
||||
## Configuring Android for deep links
|
||||
... or, to create the `aab` file, `bundle` instead of `build`:
|
||||
|
||||
```bash
|
||||
./gradlew bundleDebug -Dlint.baselines.continue=true
|
||||
```
|
||||
|
||||
... or, to create a signed release, add the app/gradle.properties.secrets file (see properties at top of app/build.gradle) and the app/time-safari-upload-key-pkcs12.jks file, then `bundleRelease`:
|
||||
|
||||
```bash
|
||||
./gradlew bundleRelease -Dlint.baselines.continue=true
|
||||
```
|
||||
|
||||
|
||||
|
||||
## First-time Android Configuration for deep links
|
||||
|
||||
You must add the following intent filter to the `android/app/src/main/AndroidManifest.xml` file:
|
||||
|
||||
@@ -329,10 +417,10 @@ The packaged application will be in `dist/TimeSafari`
|
||||
|
||||
## Testing
|
||||
|
||||
Run local tests:
|
||||
Run all tests (requires XCode and Android Studio/device):
|
||||
|
||||
```bash
|
||||
npm run test-local
|
||||
npm run test:all
|
||||
```
|
||||
|
||||
See [TESTING.md](test-playwright/TESTING.md) for more details.
|
||||
@@ -422,3 +510,19 @@ mv time-safari/dist time-safari-dist-prev.0 && mv crowd-funder-for-time-pwa/dist
|
||||
- For iOS: Xcode command line tools must be installed
|
||||
- For Android: Correct SDK version must be installed
|
||||
- Check Capacitor configuration in capacitor.config.ts
|
||||
|
||||
|
||||
# List all installed packages
|
||||
adb shell pm list packages | grep timesafari
|
||||
|
||||
# Force stop the app (if it's running)
|
||||
adb shell am force-stop app.timesafari
|
||||
|
||||
# Clear app data (if you don't want to fully uninstall)
|
||||
adb shell pm clear app.timesafari
|
||||
|
||||
# Uninstall for all users
|
||||
adb shell pm uninstall -k --user 0 app.timesafari
|
||||
|
||||
# Check if app is installed
|
||||
adb shell pm path app.timesafari
|
||||
53
README.md
@@ -19,59 +19,6 @@ npm run dev
|
||||
|
||||
See [BUILDING.md](BUILDING.md) for more details.
|
||||
|
||||
See the test locations for "IMAGE_API_SERVER" or "PARTNER_API_SERVER" below, or use http://localhost:3000 for local endorser.ch
|
||||
|
||||
|
||||
### Run all UI tests
|
||||
|
||||
Look at [BUILDING.md](BUILDING.md) for the "test-all" instructions and [TESTING.md](test-playwright/TESTING.md) for more details.
|
||||
|
||||
|
||||
### Compile and minify for test & production
|
||||
|
||||
* If there are DB changes: before updating the test server, open browser(s) with current version to test DB migrations.
|
||||
|
||||
* `npx prettier --write ./sw_scripts/`
|
||||
|
||||
* Update the ClickUp tasks & CHANGELOG.md & the version in package.json, run `npm install`.
|
||||
|
||||
* Commit everything (since the commit hash is used the app).
|
||||
|
||||
* Put the commit hash in the changelog (which will help you remember to bump the version later).
|
||||
|
||||
* Tag with the new version, [online](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/releases) or `git tag 0.3.55 && git push origin 0.3.55`.
|
||||
|
||||
* For test, build the app (because test server is not yet set up to build):
|
||||
|
||||
```bash
|
||||
TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_APP_SERVER=https://test.timesafari.app VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app VITE_DEFAULT_PARTNER_API_SERVER=https://test-partner-api.endorser.ch VITE_PASSKEYS_ENABLED=true npm run build
|
||||
```
|
||||
|
||||
... and transfer to the test server:
|
||||
|
||||
```bash
|
||||
rsync -azvu -e "ssh -i ~/.ssh/..." dist ubuntutest@test.timesafari.app:time-safari
|
||||
```
|
||||
|
||||
(Let's replace that with a .env.development or .env.staging file.)
|
||||
|
||||
(Note: The test BVC_MEETUPS_PROJECT_CLAIM_ID does not resolve as a URL because it's only in the test DB and the prod redirect won't redirect there.)
|
||||
|
||||
* For prod, get on the server and run the correct build:
|
||||
|
||||
... and log onto the server:
|
||||
|
||||
* `pkgx +npm sh`
|
||||
|
||||
* `cd crowd-funder-for-time-pwa && git checkout master && git pull && git checkout 0.3.55 && npm install && npm run build && cd -`
|
||||
|
||||
(The plain `npm run build` uses the .env.production file.)
|
||||
|
||||
* Back up the time-safari/dist folder & deploy: `mv time-safari/dist time-safari-dist-prev.0 && mv crowd-funder-for-time-pwa/dist time-safari/`
|
||||
|
||||
* Record the new hash in the changelog. Edit package.json to increment version & add "-beta", `npm install`, and commit. Also record what version is on production.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
8
android/.gitignore
vendored
@@ -1,5 +1,8 @@
|
||||
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
|
||||
|
||||
app/gradle.properties.secrets
|
||||
app/time-safari-upload-key-pkcs12.jks
|
||||
|
||||
# Built application files
|
||||
*.apk
|
||||
*.aar
|
||||
@@ -99,3 +102,8 @@ app/src/main/assets/public
|
||||
app/src/main/assets/capacitor.config.json
|
||||
app/src/main/assets/capacitor.plugins.json
|
||||
app/src/main/res/xml/config.xml
|
||||
|
||||
# Generated Icons from capacitor-assets
|
||||
app/src/main/res/drawable/*.png
|
||||
app/src/main/res/drawable-*/*.png
|
||||
app/src/main/res/mipmap-*/*.png
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#Tue Mar 11 10:01:05 UTC 2025
|
||||
gradle.version=8.10.2
|
||||
#Fri Mar 21 07:27:50 UTC 2025
|
||||
gradle.version=8.2.1
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "fastlane"
|
||||
1
android/app/.gitignore
vendored
@@ -1,3 +1,2 @@
|
||||
/build/*
|
||||
!/build/.npmkeep
|
||||
src/main/assets/public/assets/
|
||||
@@ -1,14 +1,38 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
// These are sample values to set in gradle.properties.secrets
|
||||
// MY_KEYSTORE_FILE=time-safari-upload-key-pkcs12.jks
|
||||
// MY_KEYSTORE_PASSWORD=...
|
||||
// MY_KEY_ALIAS=time-safari-key-alias
|
||||
// MY_KEY_PASSWORD=...
|
||||
|
||||
// Try to load from environment variables first
|
||||
project.ext.MY_KEYSTORE_FILE = System.getenv('ANDROID_KEYSTORE_FILE') ?: ""
|
||||
project.ext.MY_KEYSTORE_PASSWORD = System.getenv('ANDROID_KEYSTORE_PASSWORD') ?: ""
|
||||
project.ext.MY_KEY_ALIAS = System.getenv('ANDROID_KEY_ALIAS') ?: ""
|
||||
project.ext.MY_KEY_PASSWORD = System.getenv('ANDROID_KEY_PASSWORD') ?: ""
|
||||
|
||||
// If no environment variables, try to load from secrets file
|
||||
if (!project.ext.MY_KEYSTORE_FILE) {
|
||||
def secretsPropertiesFile = rootProject.file("gradle.properties.secrets")
|
||||
if (secretsPropertiesFile.exists()) {
|
||||
Properties secretsProperties = new Properties()
|
||||
secretsProperties.load(new FileInputStream(secretsPropertiesFile))
|
||||
secretsProperties.each { name, value ->
|
||||
project.ext[name] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
namespace "app.timesafari"
|
||||
namespace 'app.timesafari'
|
||||
compileSdk rootProject.ext.compileSdkVersion
|
||||
defaultConfig {
|
||||
applicationId "app.timesafari"
|
||||
applicationId "app.timesafari.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
versionCode 9
|
||||
versionName "0.4.4"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
@@ -16,22 +40,42 @@ android {
|
||||
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
||||
}
|
||||
}
|
||||
signingConfigs {
|
||||
release {
|
||||
if (project.ext.MY_KEYSTORE_FILE &&
|
||||
project.ext.MY_KEYSTORE_PASSWORD &&
|
||||
project.ext.MY_KEY_ALIAS &&
|
||||
project.ext.MY_KEY_PASSWORD) {
|
||||
|
||||
storeFile file(project.ext.MY_KEYSTORE_FILE)
|
||||
storePassword project.ext.MY_KEYSTORE_PASSWORD
|
||||
keyAlias project.ext.MY_KEY_ALIAS
|
||||
keyPassword project.ext.MY_KEY_PASSWORD
|
||||
}
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
// Only sign if we have the signing config
|
||||
if (signingConfigs.release.storeFile != null) {
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
}
|
||||
lintOptions {
|
||||
disable 'UnsanitizedFilenameFromContentProvider'
|
||||
abortOnError false
|
||||
baseline file("lint-baseline.xml")
|
||||
|
||||
// Ignore Capacitor module issues
|
||||
ignore 'DefaultLocale'
|
||||
ignore 'UnsanitizedFilenameFromContentProvider'
|
||||
ignore 'LintBaseline'
|
||||
ignore 'LintBaselineFixed'
|
||||
|
||||
// Enable bundle builds (without which it doesn't work right for bundleDebug vs bundleRelease)
|
||||
bundle {
|
||||
language {
|
||||
enableSplit = true
|
||||
}
|
||||
density {
|
||||
enableSplit = true
|
||||
}
|
||||
abi {
|
||||
enableSplit = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,398 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<issues format="6" by="lint 8.1.0" type="baseline" client="gradle" dependencies="true" name="AGP (8.1.0)" variant="all" version="8.1.0">
|
||||
|
||||
<issue
|
||||
id="UnknownIssueId"
|
||||
message="Unknown issue id "UnsanitizedFilenameFromContentProvider""
|
||||
errorLine1=" disable 'UnsanitizedFilenameFromContentProvider'"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="build.gradle"
|
||||
line="26"
|
||||
column="18"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="UnknownIssueId"
|
||||
message="Unknown issue id "UnsanitizedFilenameFromContentProvider""
|
||||
errorLine1=" disable 'UnsanitizedFilenameFromContentProvider'"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="build.gradle"
|
||||
line="26"
|
||||
column="18"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="UnknownIssueId"
|
||||
message="Unknown issue id "LintBaselineFixed""
|
||||
errorLine1=" ignore 'LintBaselineFixed'"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="build.gradle"
|
||||
line="34"
|
||||
column="17"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="UnknownIssueId"
|
||||
message="Unknown issue id "LintBaselineFixed""
|
||||
errorLine1=" ignore 'LintBaselineFixed'"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="build.gradle"
|
||||
line="34"
|
||||
column="17"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="DefaultLocale"
|
||||
message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
|
||||
errorLine1=" String msg = String.format("
|
||||
errorLine2=" ^">
|
||||
<location
|
||||
file="src/main/java/com/getcapacitor/BridgeWebChromeClient.java"
|
||||
line="467"
|
||||
column="26"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="DefaultLocale"
|
||||
message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
|
||||
errorLine1=" return mask.toUpperCase().equals(string.toUpperCase());"
|
||||
errorLine2=" ~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/getcapacitor/util/HostMask.java"
|
||||
line="110"
|
||||
column="29"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="DefaultLocale"
|
||||
message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
|
||||
errorLine1=" return mask.toUpperCase().equals(string.toUpperCase());"
|
||||
errorLine2=" ~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/getcapacitor/util/HostMask.java"
|
||||
line="110"
|
||||
column="57"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="DefaultLocale"
|
||||
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
|
||||
errorLine1=" if (header.getKey().equalsIgnoreCase("Accept") && header.getValue().toLowerCase().contains("text/html")) {"
|
||||
errorLine2=" ~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/getcapacitor/WebViewLocalServer.java"
|
||||
line="474"
|
||||
column="93"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="SimpleDateFormat"
|
||||
message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
|
||||
errorLine1=" String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/getcapacitor/BridgeWebChromeClient.java"
|
||||
line="504"
|
||||
column="28"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="SimpleDateFormat"
|
||||
message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates."
|
||||
errorLine1=" DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'");"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/getcapacitor/PluginResult.java"
|
||||
line="44"
|
||||
column="25"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="UnusedAttribute"
|
||||
message="Attribute `usesCleartextTraffic` is only used in API level 23 and higher (current min is 22)"
|
||||
errorLine1="<application android:usesCleartextTraffic="true">"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/AndroidManifest.xml"
|
||||
line="4"
|
||||
column="15"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="UnusedAttribute"
|
||||
message="Attribute `autoVerify` is only used in API level 23 and higher (current min is 22)"
|
||||
errorLine1=" <intent-filter android:autoVerify="true">"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/AndroidManifest.xml"
|
||||
line="25"
|
||||
column="28"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ManifestOrder"
|
||||
message="`<uses-permission>` tag appears after `<application>` tag"
|
||||
errorLine1=" <uses-permission android:name="android.permission.INTERNET" />"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/AndroidManifest.xml"
|
||||
line="47"
|
||||
column="6"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="AndroidGradlePluginVersion"
|
||||
message="A newer version of com.android.tools.build:gradle than 8.2.1 is available: 8.9.0. (There is also a newer version of 8.2.𝑥 available, if upgrading to 8.9.0 is difficult: 8.2.2)"
|
||||
errorLine1=" classpath 'com.android.tools.build:gradle:8.2.1'"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="build.gradle"
|
||||
line="12"
|
||||
column="9"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="AndroidGradlePluginVersion"
|
||||
message="A newer version of com.android.tools.build:gradle than 8.2.1 is available: 8.9.0. (There is also a newer version of 8.2.𝑥 available, if upgrading to 8.9.0 is difficult: 8.2.2)"
|
||||
errorLine1=" classpath 'com.android.tools.build:gradle:8.2.1'"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="build.gradle"
|
||||
line="18"
|
||||
column="9"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="GradleDependency"
|
||||
message="A newer version of androidx.appcompat:appcompat than 1.6.1 is available: 1.7.0"
|
||||
errorLine1=" implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion""
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="build.gradle"
|
||||
line="46"
|
||||
column="20"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="GradleDependency"
|
||||
message="A newer version of androidx.appcompat:appcompat than 1.6.1 is available: 1.7.0"
|
||||
errorLine1=" implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion""
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="build.gradle"
|
||||
line="46"
|
||||
column="20"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="GradleDependency"
|
||||
message="A newer version of androidx.coordinatorlayout:coordinatorlayout than 1.2.0 is available: 1.3.0"
|
||||
errorLine1=" implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion""
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="build.gradle"
|
||||
line="47"
|
||||
column="20"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="GradleDependency"
|
||||
message="A newer version of androidx.test.ext:junit than 1.1.5 is available: 1.2.1"
|
||||
errorLine1=" androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion""
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="build.gradle"
|
||||
line="51"
|
||||
column="31"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="GradleDependency"
|
||||
message="A newer version of androidx.test.espresso:espresso-core than 3.5.1 is available: 3.6.1"
|
||||
errorLine1=" androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion""
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="build.gradle"
|
||||
line="52"
|
||||
column="31"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="GradleDependency"
|
||||
message="A newer version of androidx.appcompat:appcompat than 1.6.1 is available: 1.7.0"
|
||||
errorLine1=" implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion""
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="build.gradle"
|
||||
line="75"
|
||||
column="20"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="GradleDependency"
|
||||
message="A newer version of androidx.test.ext:junit than 1.1.5 is available: 1.2.1"
|
||||
errorLine1=" androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion""
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="build.gradle"
|
||||
line="77"
|
||||
column="31"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="GradleDependency"
|
||||
message="A newer version of androidx.test.espresso:espresso-core than 3.5.1 is available: 3.6.1"
|
||||
errorLine1=" androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion""
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="build.gradle"
|
||||
line="78"
|
||||
column="31"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="Recycle"
|
||||
message="This `TypedArray` should be recycled after use with `#recycle()`"
|
||||
errorLine1=" TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.bridge_fragment);"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/getcapacitor/BridgeFragment.java"
|
||||
line="99"
|
||||
column="32"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="Overdraw"
|
||||
message="Possible overdraw: Root element paints background `#F0FF1414` with a theme that also paints a background (inferred theme is `@android:style/Theme.Holo`)"
|
||||
errorLine1=" android:background="#F0FF1414""
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/layout/fragment_bridge.xml"
|
||||
line="5"
|
||||
column="5"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="UnusedResources"
|
||||
message="The resource `R.layout.activity_main` appears to be unused"
|
||||
errorLine1="<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android""
|
||||
errorLine2="^">
|
||||
<location
|
||||
file="src/main/res/layout/activity_main.xml"
|
||||
line="2"
|
||||
column="1"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="UnusedResources"
|
||||
message="The resource `R.xml.config` appears to be unused"
|
||||
errorLine1="<widget version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">"
|
||||
errorLine2="^">
|
||||
<location
|
||||
file="src/main/res/xml/config.xml"
|
||||
line="2"
|
||||
column="1"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="UnusedResources"
|
||||
message="The resource `R.drawable.ic_launcher_background` appears to be unused"
|
||||
errorLine1="<vector xmlns:android="http://schemas.android.com/apk/res/android""
|
||||
errorLine2="^">
|
||||
<location
|
||||
file="src/main/res/drawable/ic_launcher_background.xml"
|
||||
line="2"
|
||||
column="1"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="UnusedResources"
|
||||
message="The resource `R.drawable.ic_launcher_foreground` appears to be unused"
|
||||
errorLine1="<vector xmlns:android="http://schemas.android.com/apk/res/android""
|
||||
errorLine2="^">
|
||||
<location
|
||||
file="src/main/res/drawable-v24/ic_launcher_foreground.xml"
|
||||
line="1"
|
||||
column="1"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="UnusedResources"
|
||||
message="The resource `R.string.package_name` appears to be unused"
|
||||
errorLine1=" <string name="package_name">app.timesafari.app</string>"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/values/strings.xml"
|
||||
line="5"
|
||||
column="13"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="UnusedResources"
|
||||
message="The resource `R.string.custom_url_scheme` appears to be unused"
|
||||
errorLine1=" <string name="custom_url_scheme">app.timesafari.app</string>"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/values/strings.xml"
|
||||
line="6"
|
||||
column="13"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="MonochromeLauncherIcon"
|
||||
message="The application adaptive icon is missing a monochrome tag"
|
||||
errorLine1="<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">"
|
||||
errorLine2="^">
|
||||
<location
|
||||
file="src/main/res/mipmap-anydpi-v26/ic_launcher.xml"
|
||||
line="2"
|
||||
column="1"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="MonochromeLauncherIcon"
|
||||
message="The application adaptive roundIcon is missing a monochrome tag"
|
||||
errorLine1="<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">"
|
||||
errorLine2="^">
|
||||
<location
|
||||
file="src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml"
|
||||
line="2"
|
||||
column="1"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="IconDipSize"
|
||||
message="The image `splash.png` varies significantly in its density-independent (dip) size across the various density versions: drawable-land-hdpi/splash.png: 533x320 dp (800x480 px), drawable-land-mdpi/splash.png: 480x320 dp (480x320 px), drawable-land-xhdpi/splash.png: 640x360 dp (1280x720 px), drawable-land-xxhdpi/splash.png: 533x320 dp (1600x960 px), drawable-land-xxxhdpi/splash.png: 480x320 dp (1920x1280 px)">
|
||||
<location
|
||||
file="src/main/res/drawable-land-mdpi/splash.png"/>
|
||||
<location
|
||||
file="src/main/res/drawable-land-xxxhdpi/splash.png"/>
|
||||
<location
|
||||
file="src/main/res/drawable-land-hdpi/splash.png"/>
|
||||
<location
|
||||
file="src/main/res/drawable-land-xxhdpi/splash.png"/>
|
||||
<location
|
||||
file="src/main/res/drawable-land-xhdpi/splash.png"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="IconDuplicatesConfig"
|
||||
message="The `splash.png` icon has identical contents in the following configuration folders: drawable-land-mdpi, drawable">
|
||||
<location
|
||||
file="src/main/res/drawable/splash.png"/>
|
||||
<location
|
||||
file="src/main/res/drawable-land-mdpi/splash.png"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="IconLocation"
|
||||
message="Found bitmap drawable `res/drawable/splash.png` in densityless folder">
|
||||
<location
|
||||
file="src/main/res/drawable/splash.png"/>
|
||||
</issue>
|
||||
|
||||
</issues>
|
||||
@@ -1,20 +1,26 @@
|
||||
package app.timesafari.app;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
package com.getcapacitor.myapp;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
public void useAppContext() throws Exception {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
|
||||
assertEquals("app.timesafari", appContext.getPackageName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,19 +10,19 @@
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<activity
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
|
||||
android:exported="true"
|
||||
android:label="@string/title_activity_main"
|
||||
android:theme="@style/AppTheme.NoActionBarLaunch"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="true">
|
||||
android:theme="@style/AppTheme.NoActionBarLaunch">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:autoVerify="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"appId": "app.timesafari.app",
|
||||
"appId": "app.timesafari",
|
||||
"appName": "TimeSafari",
|
||||
"webDir": "dist",
|
||||
"bundledWebRuntime": false,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<title>TimeSafari</title>
|
||||
<script type="module" crossorigin src="/assets/index-CI0bMoT0.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-CZMUlUNO.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package app.timesafari;
|
||||
|
||||
import com.getcapacitor.BridgeActivity;
|
||||
|
||||
public class MainActivity extends BridgeActivity {
|
||||
// ... existing code ...
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package app.timesafari.app;
|
||||
package timesafari.app;
|
||||
|
||||
import com.getcapacitor.BridgeActivity;
|
||||
|
||||
|
Before Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
@@ -1,5 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<background>
|
||||
<inset android:drawable="@mipmap/ic_launcher_background" android:inset="16.7%" />
|
||||
</background>
|
||||
<foreground>
|
||||
<inset android:drawable="@mipmap/ic_launcher_foreground" android:inset="16.7%" />
|
||||
</foreground>
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<background>
|
||||
<inset android:drawable="@mipmap/ic_launcher_background" android:inset="16.7%" />
|
||||
</background>
|
||||
<foreground>
|
||||
<inset android:drawable="@mipmap/ic_launcher_foreground" android:inset="16.7%" />
|
||||
</foreground>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 16 KiB |
@@ -2,6 +2,6 @@
|
||||
<resources>
|
||||
<string name="app_name">TimeSafari</string>
|
||||
<string name="title_activity_main">TimeSafari</string>
|
||||
<string name="package_name">app.timesafari.app</string>
|
||||
<string name="custom_url_scheme">app.timesafari.app</string>
|
||||
<string name="package_name">timesafari.app</string>
|
||||
<string name="custom_url_scheme">timesafari.app</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,9 +7,8 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.1.0'
|
||||
classpath 'com.android.tools.build:gradle:8.2.1'
|
||||
classpath 'com.google.gms:google-services:4.4.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
@@ -28,10 +27,3 @@ allprojects {
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
force 'org.jetbrains.kotlin:kotlin-stdlib:1.8.0'
|
||||
force 'org.jetbrains.kotlin:kotlin-stdlib-common:1.8.0'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
android {
|
||||
lintOptions {
|
||||
disable 'UnsanitizedFilenameFromContentProvider'
|
||||
abortOnError false
|
||||
baseline file("lint-baseline.xml")
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
|
||||
package_name("app.timesafari.app") # e.g. com.krausefx.app
|
||||
@@ -1,38 +0,0 @@
|
||||
# This file contains the fastlane.tools configuration
|
||||
# You can find the documentation at https://docs.fastlane.tools
|
||||
#
|
||||
# For a list of all available actions, check out
|
||||
#
|
||||
# https://docs.fastlane.tools/actions
|
||||
#
|
||||
# For a list of all available plugins, check out
|
||||
#
|
||||
# https://docs.fastlane.tools/plugins/available-plugins
|
||||
#
|
||||
|
||||
# Uncomment the line if you want fastlane to automatically update itself
|
||||
# update_fastlane
|
||||
|
||||
default_platform(:android)
|
||||
|
||||
platform :android do
|
||||
desc "Build and deploy Android app"
|
||||
lane :beta do
|
||||
gradle(
|
||||
task: "clean assembleRelease"
|
||||
)
|
||||
upload_to_play_store(
|
||||
track: 'beta',
|
||||
aab: '../app/build/outputs/bundle/release/app-release.aab'
|
||||
)
|
||||
end
|
||||
|
||||
lane :release do
|
||||
gradle(
|
||||
task: "clean assembleRelease"
|
||||
)
|
||||
upload_to_play_store(
|
||||
aab: '../app/build/outputs/bundle/release/app-release.aab'
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -1,40 +0,0 @@
|
||||
fastlane documentation
|
||||
----
|
||||
|
||||
# Installation
|
||||
|
||||
Make sure you have the latest version of the Xcode command line tools installed:
|
||||
|
||||
```sh
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
|
||||
|
||||
# Available Actions
|
||||
|
||||
## Android
|
||||
|
||||
### android beta
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android beta
|
||||
```
|
||||
|
||||
Build and deploy Android app
|
||||
|
||||
### android release
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android release
|
||||
```
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
|
||||
|
||||
More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
|
||||
|
||||
The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
|
||||
@@ -21,5 +21,3 @@ org.gradle.jvmargs=-Xmx1536m
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
android.suppressUnsupportedCompileSdk=34
|
||||
android.suppressUnsupportedCompileSdk=34
|
||||
android.suppressUnsupportedCompileSdk=34
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
## This file must *NOT* be checked into Version Control Systems,
|
||||
# as it contains information specific to your local configuration.
|
||||
#
|
||||
# Location of the SDK. This is only used by Gradle.
|
||||
# For customization when using a Version Control System, please read the
|
||||
# header note.
|
||||
#Sun Mar 09 06:14:41 UTC 2025
|
||||
sdk.dir=/opt/android-sdk
|
||||
BIN
assets/icon.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
@@ -1,7 +1,7 @@
|
||||
import type { CapacitorConfig } from '@capacitor/cli';
|
||||
import { CapacitorConfig } from '@capacitor/cli';
|
||||
|
||||
const config: CapacitorConfig = {
|
||||
appId: 'app.timesafari.app',
|
||||
appId: 'app.timesafari',
|
||||
appName: 'TimeSafari',
|
||||
webDir: 'dist',
|
||||
bundledWebRuntime: false,
|
||||
|
||||
27
ios/.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
App/build
|
||||
App/Pods
|
||||
App/output
|
||||
App/App/public
|
||||
DerivedData
|
||||
xcuserdata
|
||||
*.xcuserstate
|
||||
|
||||
# Cordova plugins for Capacitor
|
||||
capacitor-cordova-ios-plugins
|
||||
|
||||
# Generated Config files
|
||||
App/App/capacitor.config.json
|
||||
App/App/config.xml
|
||||
|
||||
# User-specific Xcode files
|
||||
App/App.xcodeproj/xcuserdata/*.xcuserdatad/
|
||||
App/App.xcodeproj/*.xcuserstate
|
||||
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots
|
||||
fastlane/test_output
|
||||
|
||||
# Generated Icons from capacitor-assets (also Contents.json which is confusing; see BUILDING.md)
|
||||
App/App/Assets.xcassets/AppIcon.appiconset
|
||||
App/App/Assets.xcassets/Splash.imageset
|
||||
10
ios/App/App.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Time Safari.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
49
ios/App/App/AppDelegate.swift
Normal file
@@ -0,0 +1,49 @@
|
||||
import UIKit
|
||||
import Capacitor
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
return true
|
||||
}
|
||||
|
||||
func applicationWillResignActive(_ application: UIApplication) {
|
||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
|
||||
}
|
||||
|
||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
||||
}
|
||||
|
||||
func applicationWillEnterForeground(_ application: UIApplication) {
|
||||
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||
}
|
||||
|
||||
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
|
||||
// Called when the app was launched with a url. Feel free to add additional processing here,
|
||||
// but if you want the App API to support tracking app url opens, make sure to keep this call
|
||||
return ApplicationDelegateProxy.shared.application(app, open: url, options: options)
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
|
||||
// Called when the app was launched with an activity, including Universal Links.
|
||||
// Feel free to add additional processing here, but if you want the App API to support
|
||||
// tracking app url opens, make sure to keep this call
|
||||
return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
|
||||
}
|
||||
|
||||
}
|
||||
6
ios/App/App/Assets.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
32
ios/App/App/Base.lproj/LaunchScreen.storyboard
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17132" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17105"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<imageView key="view" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Splash" id="snD-IY-ifK">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
</imageView>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="Splash" width="1366" height="1366"/>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
19
ios/App/App/Base.lproj/Main.storyboard
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14111" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Bridge View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="CAPBridgeViewController" customModule="Capacitor" sceneMemberID="viewController"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
49
ios/App/App/Info.plist
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>TimeSafari</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
24
ios/App/Podfile
Normal file
@@ -0,0 +1,24 @@
|
||||
require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers'
|
||||
|
||||
platform :ios, '13.0'
|
||||
use_frameworks!
|
||||
|
||||
# workaround to avoid Xcode caching of Pods that requires
|
||||
# Product -> Clean Build Folder after new Cordova plugins installed
|
||||
# Requires CocoaPods 1.6 or newer
|
||||
install! 'cocoapods', :disable_input_output_paths => true
|
||||
|
||||
def capacitor_pods
|
||||
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
||||
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
||||
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
|
||||
end
|
||||
|
||||
target 'App' do
|
||||
capacitor_pods
|
||||
# Add your Pods here
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
assertDeploymentTarget(installer)
|
||||
end
|
||||
28
ios/App/Podfile.lock
Normal file
@@ -0,0 +1,28 @@
|
||||
PODS:
|
||||
- Capacitor (6.2.0):
|
||||
- CapacitorCordova
|
||||
- CapacitorApp (6.0.2):
|
||||
- Capacitor
|
||||
- CapacitorCordova (6.2.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- "Capacitor (from `../../node_modules/@capacitor/ios`)"
|
||||
- "CapacitorApp (from `../../node_modules/@capacitor/app`)"
|
||||
- "CapacitorCordova (from `../../node_modules/@capacitor/ios`)"
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
Capacitor:
|
||||
:path: "../../node_modules/@capacitor/ios"
|
||||
CapacitorApp:
|
||||
:path: "../../node_modules/@capacitor/app"
|
||||
CapacitorCordova:
|
||||
:path: "../../node_modules/@capacitor/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Capacitor: 05d35014f4425b0740fc8776481f6a369ad071bf
|
||||
CapacitorApp: e1e6b7d05e444d593ca16fd6d76f2b7c48b5aea7
|
||||
CapacitorCordova: b33e7f4aa4ed105dd43283acdd940964374a87d9
|
||||
|
||||
PODFILE CHECKSUM: 4233f5c5f414604460ff96d372542c311b0fb7a8
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
414
ios/App/Time Safari.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,414 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 48;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; };
|
||||
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */ = {isa = PBXBuildFile; fileRef = 50379B222058CBB4000EE86E /* capacitor.config.json */; };
|
||||
504EC3081FED79650016851F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504EC3071FED79650016851F /* AppDelegate.swift */; };
|
||||
504EC30D1FED79650016851F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30B1FED79650016851F /* Main.storyboard */; };
|
||||
504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; };
|
||||
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; };
|
||||
50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; };
|
||||
A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = "<group>"; };
|
||||
50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = "<group>"; };
|
||||
504EC3041FED79650016851F /* Time Safari.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Time Safari.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
504EC3071FED79650016851F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
504EC30C1FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
504EC30E1FED79650016851F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = "<group>"; };
|
||||
AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = "<group>"; };
|
||||
FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
504EC3011FED79650016851F /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
27E2DDA53C4D2A4D1A88CE4A /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
504EC2FB1FED79650016851F = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
504EC3061FED79650016851F /* App */,
|
||||
504EC3051FED79650016851F /* Products */,
|
||||
7F8756D8B27F46E3366F6CEA /* Pods */,
|
||||
27E2DDA53C4D2A4D1A88CE4A /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
504EC3051FED79650016851F /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
504EC3041FED79650016851F /* Time Safari.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
504EC3061FED79650016851F /* App */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
50379B222058CBB4000EE86E /* capacitor.config.json */,
|
||||
504EC3071FED79650016851F /* AppDelegate.swift */,
|
||||
504EC30B1FED79650016851F /* Main.storyboard */,
|
||||
504EC30E1FED79650016851F /* Assets.xcassets */,
|
||||
504EC3101FED79650016851F /* LaunchScreen.storyboard */,
|
||||
504EC3131FED79650016851F /* Info.plist */,
|
||||
2FAD9762203C412B000D30F8 /* config.xml */,
|
||||
50B271D01FEDC1A000F3C39B /* public */,
|
||||
);
|
||||
path = App;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7F8756D8B27F46E3366F6CEA /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */,
|
||||
AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
504EC3031FED79650016851F /* Time Safari */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "Time Safari" */;
|
||||
buildPhases = (
|
||||
6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */,
|
||||
504EC3001FED79650016851F /* Sources */,
|
||||
504EC3011FED79650016851F /* Frameworks */,
|
||||
504EC3021FED79650016851F /* Resources */,
|
||||
9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "Time Safari";
|
||||
productName = App;
|
||||
productReference = 504EC3041FED79650016851F /* Time Safari.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
504EC2FC1FED79650016851F /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0920;
|
||||
LastUpgradeCheck = 0920;
|
||||
TargetAttributes = {
|
||||
504EC3031FED79650016851F = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
LastSwiftMigration = 1100;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 504EC2FF1FED79650016851F /* Build configuration list for PBXProject "Time Safari" */;
|
||||
compatibilityVersion = "Xcode 8.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 504EC2FB1FED79650016851F;
|
||||
packageReferences = (
|
||||
);
|
||||
productRefGroup = 504EC3051FED79650016851F /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
504EC3031FED79650016851F /* Time Safari */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
504EC3021FED79650016851F /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */,
|
||||
50B271D11FEDC1A000F3C39B /* public in Resources */,
|
||||
504EC30F1FED79650016851F /* Assets.xcassets in Resources */,
|
||||
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */,
|
||||
504EC30D1FED79650016851F /* Main.storyboard in Resources */,
|
||||
2FAD9763203C412B000D30F8 /* config.xml in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-App-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-App/Pods-App-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
504EC3001FED79650016851F /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
504EC30B1FED79650016851F /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
504EC30C1FED79650016851F /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
504EC3101FED79650016851F /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
504EC3111FED79650016851F /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
504EC3141FED79650016851F /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
504EC3151FED79650016851F /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
504EC3171FED79650016851F /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Time Safari";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 1.0;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
504EC3181FED79650016851F /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Time Safari";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
504EC2FF1FED79650016851F /* Build configuration list for PBXProject "Time Safari" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
504EC3141FED79650016851F /* Debug */,
|
||||
504EC3151FED79650016851F /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "Time Safari" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
504EC3171FED79650016851F /* Debug */,
|
||||
504EC3181FED79650016851F /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 504EC2FC1FED79650016851F /* Project object */;
|
||||
}
|
||||
4022
package-lock.json
generated
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "timesafari",
|
||||
"version": "0.4.4",
|
||||
"description": "TimeSafari Desktop Application",
|
||||
"description": "Time Safari Application",
|
||||
"author": {
|
||||
"name": "TimeSafari Team"
|
||||
"name": "Time Safari Team"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite --config vite.config.dev.mts",
|
||||
@@ -12,8 +12,7 @@
|
||||
"lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src",
|
||||
"lint-fix": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src",
|
||||
"prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node sw_combine.js",
|
||||
"test-local": "npx playwright test -c playwright.config-local.ts --trace on",
|
||||
"test-all": "npm run test:prerequisites && npm run build && npm run test:web && npm run test:mobile",
|
||||
"test:all": "npm run test:prerequisites && npm run build && npm run test:web && npm run test:mobile",
|
||||
"test:prerequisites": "node scripts/check-prerequisites.js",
|
||||
"test:web": "npx playwright test -c playwright.config-local.ts --trace on",
|
||||
"test:mobile": "npm run build:capacitor && npm run test:android && npm run test:ios",
|
||||
@@ -118,6 +117,7 @@
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/assets": "^3.0.5",
|
||||
"@playwright/test": "^1.45.2",
|
||||
"@types/dom-webcodecs": "^0.1.7",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
|
||||
@@ -46,21 +46,21 @@ export default defineConfig({
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium-serial',
|
||||
testMatch: /.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts/,
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
permissions: ["clipboard-read"],
|
||||
},
|
||||
workers: 1, // Force serial execution for problematic tests
|
||||
},
|
||||
{
|
||||
name: 'firefox-serial',
|
||||
testMatch: /.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts/,
|
||||
use: { ...devices['Desktop Firefox'] },
|
||||
workers: 1,
|
||||
},
|
||||
// {
|
||||
// name: 'chromium-serial',
|
||||
// testMatch: /.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts/,
|
||||
// use: {
|
||||
// ...devices['Desktop Chrome'],
|
||||
// permissions: ["clipboard-read"],
|
||||
// },
|
||||
// workers: 1, // Force serial execution for problematic tests
|
||||
// },
|
||||
// {
|
||||
// name: 'firefox-serial',
|
||||
// testMatch: /.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts/,
|
||||
// use: { ...devices['Desktop Firefox'] },
|
||||
// workers: 1,
|
||||
// },
|
||||
{
|
||||
name: 'chromium',
|
||||
testMatch: /^(?!.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts).+\.spec\.ts$/,
|
||||
@@ -76,32 +76,26 @@ export default defineConfig({
|
||||
},
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
|
||||
{
|
||||
name: "Mobile Chrome",
|
||||
use: { ...devices["Pixel 5"] },
|
||||
},
|
||||
{
|
||||
name: "Mobile Safari",
|
||||
use: { ...devices["iPhone 12"] },
|
||||
},
|
||||
// {
|
||||
// name: "Mobile Chrome",
|
||||
// use: { ...devices["Pixel 5"] },
|
||||
// },
|
||||
// {
|
||||
// name: "Mobile Safari",
|
||||
// use: { ...devices["iPhone 12"] },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
// },
|
||||
{
|
||||
name: "Google Chrome",
|
||||
use: { ...devices["Desktop Chrome"], channel: "chrome" },
|
||||
},
|
||||
],
|
||||
|
||||
/* Configure global timeout; default is 30000 milliseconds */
|
||||
// the image upload will often not succeed in 5 seconds
|
||||
// 33-record-gift-x10.spec.ts:90:5 > Record 9 new gifts will often not succeed in 30 seconds
|
||||
timeout: 35000, // various tests fail at various times with 25000
|
||||
timeout: 45000, // various tests fail at various times with 25000
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
/**
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
*
|
||||
* @requires child_process
|
||||
* @requires path
|
||||
* @requires readline
|
||||
*
|
||||
* @author TimeSafari Team
|
||||
* @license MIT
|
||||
@@ -37,7 +38,14 @@
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const { join } = require('path');
|
||||
const { existsSync, mkdirSync, appendFileSync, readFileSync } = require('fs');
|
||||
const { existsSync, mkdirSync, appendFileSync, readFileSync, writeFileSync } = require('fs');
|
||||
const readline = require('readline');
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
const question = (prompt) => new Promise((resolve) => rl.question(prompt, resolve));
|
||||
|
||||
// Format date as YYYY-MM-DD-HHMMSS
|
||||
const getLogFileName = () => {
|
||||
@@ -88,11 +96,57 @@ const verifyJavaInstallation = (log) => {
|
||||
// Generate test data using generate_data.ts
|
||||
const generateTestData = async (log) => {
|
||||
log('🔄 Generating test data...');
|
||||
|
||||
// Create .generated directory if it doesn't exist
|
||||
if (!existsSync('.generated')) {
|
||||
mkdirSync('.generated', { recursive: true });
|
||||
}
|
||||
|
||||
try {
|
||||
execSync('npx ts-node test-scripts/generate_data.ts', { stdio: 'inherit' });
|
||||
// Generate test data
|
||||
const testData = {
|
||||
CONTACT1_DID: "did:ethr:0x1943754837A09684Fd6380C1D80aa53E3F20E338",
|
||||
CLAIM_ID: "01JPVVX7FH0EKQWTQY9HTXZQDZ"
|
||||
};
|
||||
|
||||
const claimDetails = {
|
||||
claim_id: "01JPVVX7FH0EKQWTQY9HTXZQDZ",
|
||||
issuedAt: "2025-03-21T08:07:57ZZ",
|
||||
issuer: "did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F"
|
||||
};
|
||||
|
||||
const contacts = [
|
||||
{
|
||||
did: "did:ethr:0x1943754837A09684Fd6380C1D80aa53E3F20E338",
|
||||
name: "Test Contact"
|
||||
}
|
||||
];
|
||||
|
||||
// Write files
|
||||
log('📝 Writing test data files...');
|
||||
writeFileSync('.generated/test-env.json', JSON.stringify(testData, null, 2));
|
||||
writeFileSync('.generated/claim_details.json', JSON.stringify(claimDetails, null, 2));
|
||||
writeFileSync('.generated/contacts.json', JSON.stringify(contacts, null, 2));
|
||||
|
||||
// Verify files were written
|
||||
log('✅ Verifying test data files...');
|
||||
const files = [
|
||||
'.generated/test-env.json',
|
||||
'.generated/claim_details.json',
|
||||
'.generated/contacts.json'
|
||||
];
|
||||
|
||||
for (const file of files) {
|
||||
if (!existsSync(file)) {
|
||||
throw new Error(`Failed to create ${file}`);
|
||||
}
|
||||
log(`✅ Created ${file}`);
|
||||
}
|
||||
|
||||
log('✅ Test data generated successfully');
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to generate test data: ${error.message}`);
|
||||
log(`❌ Failed to generate test data: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -116,14 +170,24 @@ const executeDeeplink = async (url, description, log) => {
|
||||
|
||||
try {
|
||||
// Stop the app before executing the deep link
|
||||
execSync('adb shell am force-stop app.timesafari.app');
|
||||
execSync('adb shell am force-stop app.timesafari');
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1s
|
||||
|
||||
execSync(`adb shell am start -W -a android.intent.action.VIEW -d "${url}" -c android.intent.category.BROWSABLE`);
|
||||
log(`✅ Successfully executed: ${description}`);
|
||||
|
||||
// Wait between deeplink tests
|
||||
await new Promise(resolve => setTimeout(resolve, 5000)); // Wait 5s
|
||||
// Wait for app to load content
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
|
||||
// Wait for user confirmation before continuing
|
||||
await question('\n⏎ Press Enter to continue to next test (or Ctrl+C to quit)...');
|
||||
|
||||
// Press Back button to ensure app is in consistent state
|
||||
log(`📱 Sending keystroke (BACK) to device...`);
|
||||
execSync('adb shell input keyevent KEYCODE_BACK');
|
||||
|
||||
// Small delay after keystroke
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
} catch (error) {
|
||||
log(`❌ Failed to execute deeplink: ${description}`);
|
||||
log(`Error: ${error.message}`);
|
||||
@@ -137,11 +201,11 @@ const runDeeplinkTests = async (log) => {
|
||||
|
||||
try {
|
||||
// Load test data
|
||||
const testEnv = parseEnvFile('.generated/test-env.sh');
|
||||
const testEnv = JSON.parse(readFileSync('.generated/test-env.json', 'utf8'));
|
||||
const claimDetails = JSON.parse(readFileSync('.generated/claim_details.json', 'utf8'));
|
||||
const contacts = JSON.parse(readFileSync('.generated/contacts.json', 'utf8'));
|
||||
|
||||
// Test each deeplink
|
||||
// Test URLs
|
||||
const deeplinkTests = [
|
||||
{
|
||||
url: `timesafari://claim/${claimDetails.claim_id}`,
|
||||
@@ -173,26 +237,44 @@ const runDeeplinkTests = async (log) => {
|
||||
}
|
||||
];
|
||||
|
||||
// Execute each test
|
||||
for (const test of deeplinkTests) {
|
||||
await executeDeeplink(test.url, test.description, log);
|
||||
}
|
||||
// Show test plan
|
||||
log('\n📋 Test Plan:');
|
||||
deeplinkTests.forEach((test, i) => {
|
||||
log(`${i + 1}. ${test.description}`);
|
||||
});
|
||||
|
||||
let succeeded = true;
|
||||
try {
|
||||
await executeDeeplink('timesafari://contactJunk', 'Non-existent deeplink', log);
|
||||
} catch (error) {
|
||||
log('✅ Non-existent deeplink failed as expected');
|
||||
succeeded = false;
|
||||
} finally {
|
||||
if (succeeded) {
|
||||
throw new Error('Non-existent deeplink should have failed');
|
||||
// Execute each test
|
||||
let testsCompleted = 0;
|
||||
for (const test of deeplinkTests) {
|
||||
// Show progress
|
||||
log(`\n📊 Progress: ${testsCompleted}/${deeplinkTests.length} tests completed`);
|
||||
|
||||
// Show upcoming test info
|
||||
log('\n📱 NEXT TEST:');
|
||||
log('------------------------');
|
||||
log(`Description: ${test.description}`);
|
||||
log(`URL: ${test.url}`);
|
||||
log('------------------------');
|
||||
|
||||
await executeDeeplink(test.url, test.description, log);
|
||||
testsCompleted++;
|
||||
|
||||
// If there are more tests, show the next one
|
||||
if (testsCompleted < deeplinkTests.length) {
|
||||
const nextTest = deeplinkTests[testsCompleted];
|
||||
log('\n⏭️ NEXT UP:');
|
||||
log('------------------------');
|
||||
log(`Next test will be: ${nextTest.description}`);
|
||||
log(`URL: ${nextTest.url}`);
|
||||
log('------------------------');
|
||||
}
|
||||
}
|
||||
|
||||
log('✅ All deeplink tests completed successfully');
|
||||
log('\n🎉 All deeplink tests completed successfully!');
|
||||
rl.close(); // Close readline interface when done
|
||||
} catch (error) {
|
||||
log('❌ Deeplink tests failed');
|
||||
rl.close(); // Close readline interface on error
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -214,24 +296,88 @@ const configureAndroidProject = async (log) => {
|
||||
|
||||
log('⚙️ Configuring Gradle properties...');
|
||||
const gradleProps = 'android/gradle.properties';
|
||||
if (!existsSync(gradleProps) || !execSync(`grep -q "android.suppressUnsupportedCompileSdk=34" ${gradleProps}`)) {
|
||||
|
||||
// Create file if it doesn't exist
|
||||
if (!existsSync(gradleProps)) {
|
||||
execSync('touch android/gradle.properties');
|
||||
}
|
||||
|
||||
// Check if line exists without using grep
|
||||
const gradleContent = readFileSync(gradleProps, 'utf8');
|
||||
if (!gradleContent.includes('android.suppressUnsupportedCompileSdk=34')) {
|
||||
execSync('echo "android.suppressUnsupportedCompileSdk=34" >> android/gradle.properties');
|
||||
log('✅ Added SDK suppression to gradle.properties');
|
||||
} else {
|
||||
log('✅ SDK suppression already configured in gradle.properties');
|
||||
}
|
||||
};
|
||||
|
||||
// Build and test Android project
|
||||
const buildAndTestAndroid = async (log, env) => {
|
||||
log('🏗️ Building Android project...');
|
||||
|
||||
// Kill and restart ADB server first
|
||||
try {
|
||||
log('🔄 Restarting ADB server...');
|
||||
execSync('adb kill-server', { stdio: 'inherit' });
|
||||
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2s
|
||||
execSync('adb start-server', { stdio: 'inherit' });
|
||||
await new Promise(resolve => setTimeout(resolve, 3000)); // Wait 3s
|
||||
|
||||
// Verify device connection
|
||||
const devices = execSync('adb devices').toString();
|
||||
if (!devices.includes('\tdevice')) {
|
||||
throw new Error('No devices connected after ADB restart');
|
||||
}
|
||||
log('✅ ADB server restarted successfully');
|
||||
} catch (error) {
|
||||
log(`⚠️ ADB restart failed: ${error.message}`);
|
||||
log('Continuing with build process...');
|
||||
}
|
||||
|
||||
// Clean build
|
||||
log('🧹 Cleaning project...');
|
||||
execSync('cd android && ./gradlew clean', { stdio: 'inherit', env });
|
||||
log('✅ Gradle clean completed');
|
||||
|
||||
// Build
|
||||
log('🏗️ Building project...');
|
||||
execSync('cd android && ./gradlew build', { stdio: 'inherit', env });
|
||||
log('✅ Gradle build completed');
|
||||
|
||||
// Run tests with retry
|
||||
log('🧪 Running Android tests...');
|
||||
execSync('cd android && ./gradlew connectedAndroidTest', { stdio: 'inherit', env });
|
||||
log('✅ Android tests completed');
|
||||
let retryCount = 0;
|
||||
const maxRetries = 3;
|
||||
|
||||
while (retryCount < maxRetries) {
|
||||
try {
|
||||
// Verify ADB connection before tests
|
||||
execSync('adb devices', { stdio: 'inherit' });
|
||||
|
||||
// Run the tests
|
||||
execSync('cd android && ./gradlew connectedAndroidTest', {
|
||||
stdio: 'inherit',
|
||||
env,
|
||||
timeout: 60000 // 1 minute timeout
|
||||
});
|
||||
log('✅ Android tests completed');
|
||||
return;
|
||||
} catch (error) {
|
||||
retryCount++;
|
||||
log(`⚠️ Test attempt ${retryCount} failed: ${error.message}`);
|
||||
|
||||
if (retryCount < maxRetries) {
|
||||
log('🔄 Restarting ADB and retrying...');
|
||||
execSync('adb kill-server', { stdio: 'inherit' });
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
execSync('adb start-server', { stdio: 'inherit' });
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
} else {
|
||||
throw new Error(`Android tests failed after ${maxRetries} attempts`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Run the app
|
||||
@@ -296,4 +442,10 @@ async function runAndroidTests() {
|
||||
}
|
||||
|
||||
// Execute the test suite
|
||||
runAndroidTests();
|
||||
runAndroidTests();
|
||||
|
||||
// Add cleanup handler for SIGINT
|
||||
process.on('SIGINT', () => {
|
||||
rl.close();
|
||||
process.exit();
|
||||
});
|
||||
@@ -6,9 +6,11 @@
|
||||
* web build and runs the test suite on a specified iOS simulator.
|
||||
*
|
||||
* Process flow:
|
||||
* 1. Sync Capacitor project with latest web build
|
||||
* 2. Build app for iOS simulator
|
||||
* 3. Run XCTest suite
|
||||
* 1. Clean and reset iOS platform (if needed)
|
||||
* 2. Check prerequisites (Xcode, CocoaPods, Capacitor setup)
|
||||
* 3. Sync Capacitor project with latest web build
|
||||
* 4. Build app for iOS simulator
|
||||
* 5. Run XCTest suite
|
||||
*
|
||||
* Prerequisites:
|
||||
* - macOS operating system
|
||||
@@ -38,7 +40,21 @@
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const { join } = require('path');
|
||||
const { existsSync, mkdirSync, appendFileSync, readFileSync } = require('fs');
|
||||
const { existsSync, mkdirSync, appendFileSync, readFileSync, writeFileSync, readdirSync, statSync, accessSync } = require('fs');
|
||||
const readline = require('readline');
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
const { constants } = require('fs');
|
||||
|
||||
const question = (prompt) => new Promise((resolve) => rl.question(prompt, resolve));
|
||||
|
||||
// Make sure to close readline at the end
|
||||
process.on('SIGINT', () => {
|
||||
rl.close();
|
||||
process.exit();
|
||||
});
|
||||
|
||||
// Format date as YYYY-MM-DD-HHMMSS
|
||||
const getLogFileName = () => {
|
||||
@@ -58,6 +74,169 @@ const createLogger = (logFile) => {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Clean up and reset iOS platform
|
||||
* This function completely removes and recreates the iOS platform to ensure a fresh setup
|
||||
* @param {function} log - Logging function
|
||||
* @returns {boolean} - Success status
|
||||
*/
|
||||
const cleanIosPlatform = async (log) => {
|
||||
log('🧹 Cleaning iOS platform (complete reset)...');
|
||||
|
||||
// Check for package.json and capacitor.config.ts/js
|
||||
if (!existsSync('package.json')) {
|
||||
log('⚠️ package.json not found. Are you in the correct directory?');
|
||||
throw new Error('package.json not found. Cannot continue without project configuration.');
|
||||
}
|
||||
log('✅ package.json exists');
|
||||
|
||||
const capacitorConfigExists =
|
||||
existsSync('capacitor.config.ts') ||
|
||||
existsSync('capacitor.config.js') ||
|
||||
existsSync('capacitor.config.json');
|
||||
|
||||
if (!capacitorConfigExists) {
|
||||
log('⚠️ Capacitor config file not found');
|
||||
log('Creating minimal capacitor.config.ts...');
|
||||
|
||||
try {
|
||||
// Get app name from package.json
|
||||
const packageJson = JSON.parse(readFileSync('package.json', 'utf8'));
|
||||
const appName = packageJson.name || 'App';
|
||||
const appId = packageJson.capacitor?.appId || 'io.ionic.starter';
|
||||
|
||||
// Create a minimal capacitor config
|
||||
const capacitorConfig = `
|
||||
import { CapacitorConfig } from '@capacitor/cli';
|
||||
|
||||
const config: CapacitorConfig = {
|
||||
appId: '${appId}',
|
||||
appName: '${appName}',
|
||||
webDir: 'dist',
|
||||
bundledWebRuntime: false
|
||||
};
|
||||
|
||||
export default config;
|
||||
`.trim();
|
||||
|
||||
writeFileSync('capacitor.config.ts', capacitorConfig);
|
||||
log('✅ Created capacitor.config.ts');
|
||||
} catch (configError) {
|
||||
log('⚠️ Failed to create Capacitor config file');
|
||||
log('Please create a capacitor.config.ts file manually');
|
||||
throw new Error('Capacitor configuration missing. Please configure manually.');
|
||||
}
|
||||
} else {
|
||||
log('✅ Capacitor config exists');
|
||||
}
|
||||
|
||||
// Check if the platform exists first
|
||||
if (existsSync('ios')) {
|
||||
log('🗑️ Removing existing iOS platform directory...');
|
||||
try {
|
||||
execSync('rm -rf ios', { stdio: 'inherit' });
|
||||
log('✅ Existing iOS platform removed');
|
||||
} catch (error) {
|
||||
log(`⚠️ Error removing iOS platform: ${error.message}`);
|
||||
log('⚠️ You may need to manually remove the ios directory');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild web assets first to ensure they're available
|
||||
log('🔄 Building web assets before adding iOS platform...');
|
||||
try {
|
||||
execSync('rm -rf dist', { stdio: 'inherit' });
|
||||
execSync('npm run build:web', { stdio: 'inherit' });
|
||||
execSync('npm run build:capacitor', { stdio: 'inherit' });
|
||||
log('✅ Web assets built successfully');
|
||||
} catch (error) {
|
||||
log(`⚠️ Error building web assets: ${error.message}`);
|
||||
log('⚠️ Continuing with platform addition, but it may fail if web assets are required');
|
||||
}
|
||||
|
||||
// Add the platform back
|
||||
log('➕ Adding iOS platform...');
|
||||
try {
|
||||
execSync('npx cap add ios', { stdio: 'inherit' });
|
||||
log('✅ iOS platform added successfully');
|
||||
|
||||
// Verify critical files were created
|
||||
if (!existsSync('ios/App/Podfile')) {
|
||||
log('⚠️ Podfile was not created - something is wrong with the Capacitor setup');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!existsSync('ios/App/App/Info.plist')) {
|
||||
log('⚠️ Info.plist was not created - something is wrong with the Capacitor setup');
|
||||
return false;
|
||||
}
|
||||
|
||||
log('✅ iOS platform setup verified - critical files exist');
|
||||
return true;
|
||||
} catch (error) {
|
||||
log(`⚠️ Error adding iOS platform: ${error.message}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check all prerequisites for iOS testing
|
||||
* Verifies and attempts to install/initialize all required components
|
||||
*/
|
||||
const checkPrerequisites = async (log) => {
|
||||
log('🔍 Checking prerequisites for iOS testing...');
|
||||
|
||||
// Check for macOS
|
||||
if (process.platform !== 'darwin') {
|
||||
throw new Error('iOS testing is only supported on macOS');
|
||||
}
|
||||
log('✅ Running on macOS');
|
||||
|
||||
// Verify Xcode installation
|
||||
try {
|
||||
const xcodeOutput = execSync('xcode-select -p').toString().trim();
|
||||
log(`✅ Xcode command line tools found at: ${xcodeOutput}`);
|
||||
} catch (error) {
|
||||
log('⚠️ Xcode command line tools not found');
|
||||
log('Please install Xcode from the App Store and run:');
|
||||
log('xcode-select --install');
|
||||
throw new Error('Xcode command line tools not found. Please install Xcode first.');
|
||||
}
|
||||
|
||||
// Check Xcode version
|
||||
try {
|
||||
const xcodeVersionOutput = execSync('xcodebuild -version').toString().trim();
|
||||
log(`✅ Xcode version: ${xcodeVersionOutput.split('\n')[0]}`);
|
||||
} catch (error) {
|
||||
log('⚠️ Unable to determine Xcode version');
|
||||
}
|
||||
|
||||
// Check for CocoaPods
|
||||
try {
|
||||
const podVersionOutput = execSync('pod --version').toString().trim();
|
||||
log(`✅ CocoaPods version: ${podVersionOutput}`);
|
||||
} catch (error) {
|
||||
log('⚠️ CocoaPods not found');
|
||||
log('Attempting to install CocoaPods...');
|
||||
|
||||
try {
|
||||
log('🔄 Installing CocoaPods via gem...');
|
||||
execSync('gem install cocoapods', { stdio: 'inherit' });
|
||||
log('✅ CocoaPods installed successfully');
|
||||
} catch (gemError) {
|
||||
log('⚠️ Failed to install CocoaPods via gem');
|
||||
log('Please install CocoaPods manually:');
|
||||
log('1. sudo gem install cocoapods');
|
||||
log('2. brew install cocoapods');
|
||||
throw new Error('CocoaPods installation failed. Please install manually.');
|
||||
}
|
||||
}
|
||||
|
||||
log('✅ All prerequisites for iOS testing are met');
|
||||
return true;
|
||||
};
|
||||
|
||||
// Check for iOS simulator
|
||||
const checkSimulator = async (log) => {
|
||||
log('🔍 Checking for iOS simulator...');
|
||||
@@ -151,122 +330,84 @@ const verifyXcodeInstallation = (log) => {
|
||||
|
||||
// Generate test data using generate_data.ts
|
||||
const generateTestData = async (log) => {
|
||||
log('🔄 Generating test data...');
|
||||
log('\n🔍 DEBUG: Starting test data generation...');
|
||||
|
||||
// Check if test-scripts directory exists
|
||||
if (!existsSync('test-scripts')) {
|
||||
log('⚠️ test-scripts directory not found');
|
||||
log('⚠️ Current directory: ' + process.cwd());
|
||||
|
||||
// List directories to help debug
|
||||
const { readdirSync } = require('fs');
|
||||
log('📂 Directories in current path:');
|
||||
try {
|
||||
const files = readdirSync('.');
|
||||
files.forEach(file => {
|
||||
const isDir = existsSync(file) && require('fs').statSync(file).isDirectory();
|
||||
log(`${isDir ? '📁' : '📄'} ${file}`);
|
||||
});
|
||||
} catch (err) {
|
||||
log(`⚠️ Error listing directory: ${err.message}`);
|
||||
}
|
||||
} else {
|
||||
log('✅ Found test-scripts directory');
|
||||
|
||||
// Check if generate_data.ts exists
|
||||
if (existsSync('test-scripts/generate_data.ts')) {
|
||||
log('✅ Found generate_data.ts');
|
||||
} else {
|
||||
log('⚠️ generate_data.ts not found in test-scripts directory');
|
||||
|
||||
// List files in test-scripts to help debug
|
||||
const { readdirSync } = require('fs');
|
||||
log('📂 Files in test-scripts:');
|
||||
try {
|
||||
const files = readdirSync('test-scripts');
|
||||
files.forEach(file => {
|
||||
log(`📄 ${file}`);
|
||||
});
|
||||
} catch (err) {
|
||||
log(`⚠️ Error listing test-scripts: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check directory structure
|
||||
log('📁 Current directory:', process.cwd());
|
||||
log('📁 Directory contents:', require('fs').readdirSync('.'));
|
||||
|
||||
// Create .generated directory if it doesn't exist
|
||||
if (!existsSync('.generated')) {
|
||||
log('📁 Creating .generated directory');
|
||||
mkdirSync('.generated', { recursive: true });
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// Try to generate test data using the script
|
||||
log('🔄 Running test data generation script...');
|
||||
log('🔄 Attempting to run generate_data.ts...');
|
||||
execSync('npx ts-node test-scripts/generate_data.ts', { stdio: 'inherit' });
|
||||
log('✅ Test data generation script completed');
|
||||
log('✅ Test data generation completed');
|
||||
|
||||
// Verify the generated files exist
|
||||
// Verify and log generated files content
|
||||
const requiredFiles = [
|
||||
'.generated/test-env.json',
|
||||
'.generated/claim_details.json',
|
||||
'.generated/contacts.json'
|
||||
];
|
||||
|
||||
log('🔍 Verifying generated files:');
|
||||
log('\n📝 Verifying generated files:');
|
||||
for (const file of requiredFiles) {
|
||||
if (!existsSync(file)) {
|
||||
log(`⚠️ Required file ${file} was not generated`);
|
||||
throw new Error(`Required file ${file} was not generated`);
|
||||
log(`❌ Missing file: ${file}`);
|
||||
} else {
|
||||
log(`✅ ${file} exists`);
|
||||
const content = readFileSync(file, 'utf8');
|
||||
log(`\n📄 Content of ${file}:`);
|
||||
log(content);
|
||||
try {
|
||||
const parsed = JSON.parse(content);
|
||||
if (file.includes('test-env.json')) {
|
||||
log('🔑 CONTACT1_DID in test-env:', parsed.CONTACT1_DID);
|
||||
}
|
||||
if (file.includes('contacts.json')) {
|
||||
log('👥 First contact DID:', parsed[0]?.did);
|
||||
}
|
||||
} catch (e) {
|
||||
log(`❌ Error parsing ${file}:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log(`⚠️ Failed to generate test data: ${error.message}`);
|
||||
log(`\n⚠️ Test data generation failed: ${error.message}`);
|
||||
log('⚠️ Creating fallback test data...');
|
||||
|
||||
// Create minimal fallback test data
|
||||
// Create fallback data with detailed logging
|
||||
const fallbackTestEnv = {
|
||||
"CONTACT1_DID": "did:example:123456789",
|
||||
"CONTACT1_DID": "did:ethr:0x35A71Ac3fA0A4D5a4903f10F0f7A3ac4034FaB5B",
|
||||
"APP_URL": "https://app.timesafari.example"
|
||||
};
|
||||
|
||||
const fallbackClaimDetails = {
|
||||
"claim_id": "claim_12345",
|
||||
"title": "Test Claim",
|
||||
"description": "This is a test claim"
|
||||
};
|
||||
|
||||
const fallbackContacts = [
|
||||
{
|
||||
"id": "contact1",
|
||||
"name": "Test Contact",
|
||||
"did": "did:example:123456789"
|
||||
"did": "did:ethr:0x35A71Ac3fA0A4D5a4903f10F0f7A3ac4034FaB5B"
|
||||
}
|
||||
];
|
||||
|
||||
// Use writeFileSync to overwrite any existing files
|
||||
const { writeFileSync } = require('fs');
|
||||
log('\n📝 Writing fallback data:');
|
||||
log('TestEnv:', JSON.stringify(fallbackTestEnv, null, 2));
|
||||
log('Contacts:', JSON.stringify(fallbackContacts, null, 2));
|
||||
|
||||
writeFileSync('.generated/test-env.json', JSON.stringify(fallbackTestEnv, null, 2));
|
||||
writeFileSync('.generated/claim_details.json', JSON.stringify(fallbackClaimDetails, null, 2));
|
||||
writeFileSync('.generated/contacts.json', JSON.stringify(fallbackContacts, null, 2));
|
||||
|
||||
log('✅ Fallback test data created');
|
||||
|
||||
// Verify files were created
|
||||
const requiredFiles = [
|
||||
'.generated/test-env.json',
|
||||
'.generated/claim_details.json',
|
||||
'.generated/contacts.json'
|
||||
];
|
||||
|
||||
log('🔍 Verifying fallback files:');
|
||||
for (const file of requiredFiles) {
|
||||
if (!existsSync(file)) {
|
||||
log(`⚠️ Failed to create ${file}`);
|
||||
} else {
|
||||
log(`✅ Created ${file}`);
|
||||
}
|
||||
// Verify fallback data was written
|
||||
log('\n🔍 Verifying fallback data:');
|
||||
try {
|
||||
const writtenTestEnv = JSON.parse(readFileSync('.generated/test-env.json', 'utf8'));
|
||||
const writtenContacts = JSON.parse(readFileSync('.generated/contacts.json', 'utf8'));
|
||||
log('Written TestEnv:', writtenTestEnv);
|
||||
log('Written Contacts:', writtenContacts);
|
||||
} catch (e) {
|
||||
log('❌ Error verifying fallback data:', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -282,40 +423,45 @@ const buildWebAssets = async (log) => {
|
||||
|
||||
// Configure iOS project
|
||||
const configureIosProject = async (log) => {
|
||||
log('📱 Syncing Capacitor project...');
|
||||
try {
|
||||
execSync('npx cap sync ios', { stdio: 'inherit' });
|
||||
log('✅ Capacitor sync completed');
|
||||
} catch (error) {
|
||||
log('⚠️ Capacitor sync encountered issues. Attempting to continue...');
|
||||
}
|
||||
log('📱 Configuring iOS project...');
|
||||
|
||||
// Skip cap sync since we just did a clean platform add
|
||||
log('✅ Using freshly created iOS platform');
|
||||
|
||||
// Register URL scheme for deeplink tests
|
||||
log('🔗 Configuring URL scheme for deeplink tests...');
|
||||
if (checkAndRegisterUrlScheme(log)) {
|
||||
log('✅ URL scheme configuration completed');
|
||||
} else {
|
||||
log('⚠️ URL scheme could not be registered automatically');
|
||||
log('⚠️ Deeplink tests may not work correctly');
|
||||
}
|
||||
|
||||
log('⚙️ Installing CocoaPods dependencies...');
|
||||
try {
|
||||
// Try to run pod install normally first
|
||||
log('🔄 Running "pod install" in ios/App directory...');
|
||||
execSync('cd ios/App && pod install', { stdio: 'inherit' });
|
||||
log('✅ CocoaPods installation completed');
|
||||
} catch (error) {
|
||||
// If that fails, try using sudo (requires password)
|
||||
log('⚠️ CocoaPods installation failed. Trying with sudo...');
|
||||
try {
|
||||
execSync('cd ios/App && sudo pod install', { stdio: 'inherit' });
|
||||
} catch (sudoError) {
|
||||
// If both methods fail, alert the user
|
||||
log('❌ CocoaPods installation failed.');
|
||||
log('Please run one of the following commands manually:');
|
||||
log('1. cd ios/App && pod install');
|
||||
log('2. cd ios/App && sudo pod install');
|
||||
log('3. Install CocoaPods through Homebrew: brew install cocoapods');
|
||||
throw new Error('CocoaPods installation failed. See log for details.');
|
||||
}
|
||||
// If that fails, provide detailed instructions
|
||||
log(`⚠️ CocoaPods installation failed: ${error.message}`);
|
||||
log('⚠️ Please ensure CocoaPods is installed correctly:');
|
||||
log('1. If using system Ruby: "sudo gem install cocoapods"');
|
||||
log('2. If using Homebrew Ruby: "brew install cocoapods"');
|
||||
log('3. Then run: "cd ios/App && pod install"');
|
||||
|
||||
// Try to continue despite the error
|
||||
log('⚠️ Attempting to continue with the build process...');
|
||||
}
|
||||
log('✅ CocoaPods installation completed');
|
||||
|
||||
// Add information about iOS security dialogs
|
||||
log('\n📱 iOS Security Dialog Information:');
|
||||
log('⚠️ iOS will display security confirmation dialogs when testing deeplinks');
|
||||
log('⚠️ This is a security feature of iOS and cannot be bypassed in normal testing');
|
||||
log('⚠️ You will need to manually approve each deeplink test by clicking "Open" in the dialog');
|
||||
log('⚠️ The app must be running in the foreground for deeplinks to work properly');
|
||||
log('⚠️ If tests appear to hang, check if a security dialog is waiting for your confirmation');
|
||||
};
|
||||
|
||||
// Build and test iOS project
|
||||
@@ -365,6 +511,96 @@ const runIosApp = async (log, simulator) => {
|
||||
log('✅ App launched successfully');
|
||||
};
|
||||
|
||||
const validateTestData = (log) => {
|
||||
log('\n=== VALIDATING TEST DATA ===');
|
||||
|
||||
const generateFreshTestData = () => {
|
||||
log('\n🔄 Generating fresh test data...');
|
||||
try {
|
||||
// Ensure .generated directory exists
|
||||
if (!existsSync('.generated')) {
|
||||
mkdirSync('.generated', { recursive: true });
|
||||
}
|
||||
|
||||
// Execute the generate_data.ts script synchronously
|
||||
log('Running generate_data.ts...');
|
||||
execSync('npx ts-node test-scripts/generate_data.ts', {
|
||||
stdio: 'inherit',
|
||||
encoding: 'utf8'
|
||||
});
|
||||
|
||||
// Read and validate the generated files
|
||||
const testEnvPath = '.generated/test-env.json';
|
||||
const contactsPath = '.generated/contacts.json';
|
||||
|
||||
if (!existsSync(testEnvPath) || !existsSync(contactsPath)) {
|
||||
throw new Error('Generated files not found after running generate_data.ts');
|
||||
}
|
||||
|
||||
const testEnv = JSON.parse(readFileSync(testEnvPath, 'utf8'));
|
||||
const contacts = JSON.parse(readFileSync(contactsPath, 'utf8'));
|
||||
|
||||
// Validate required fields
|
||||
if (!testEnv.CONTACT1_DID) {
|
||||
throw new Error('CONTACT1_DID missing from generated test data');
|
||||
}
|
||||
|
||||
log('Generated test data:', {
|
||||
testEnv: testEnv,
|
||||
contacts: contacts
|
||||
});
|
||||
|
||||
return { testEnv, contacts };
|
||||
} catch (error) {
|
||||
log('❌ Test data generation failed:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
// Try to read existing data or generate fresh data
|
||||
const testEnvPath = '.generated/test-env.json';
|
||||
const contactsPath = '.generated/contacts.json';
|
||||
|
||||
let testData;
|
||||
|
||||
// If either file is missing or invalid, generate fresh data
|
||||
if (!existsSync(testEnvPath) || !existsSync(contactsPath)) {
|
||||
testData = generateFreshTestData();
|
||||
} else {
|
||||
try {
|
||||
const testEnv = JSON.parse(readFileSync(testEnvPath, 'utf8'));
|
||||
const contacts = JSON.parse(readFileSync(contactsPath, 'utf8'));
|
||||
|
||||
// Validate required fields
|
||||
if (!testEnv.CLAIM_ID || !testEnv.CONTACT1_DID) {
|
||||
log('⚠️ Existing test data missing required fields, regenerating...');
|
||||
testData = generateFreshTestData();
|
||||
} else {
|
||||
testData = { testEnv, contacts };
|
||||
}
|
||||
} catch (error) {
|
||||
log('⚠️ Error reading existing test data, regenerating...');
|
||||
testData = generateFreshTestData();
|
||||
}
|
||||
}
|
||||
|
||||
// Final validation of data
|
||||
if (!testData.testEnv.CLAIM_ID || !testData.testEnv.CONTACT1_DID) {
|
||||
throw new Error('Test data validation failed even after generation');
|
||||
}
|
||||
|
||||
log('✅ Test data validated successfully');
|
||||
log('📄 Test Environment:', JSON.stringify(testData.testEnv, null, 2));
|
||||
|
||||
return testData;
|
||||
|
||||
} catch (error) {
|
||||
log(`❌ Test data validation failed: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Run deeplink tests
|
||||
* Optionally tests deeplinks if the test data is available
|
||||
@@ -373,122 +609,115 @@ const runIosApp = async (log, simulator) => {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const runDeeplinkTests = async (log) => {
|
||||
log('🔗 Starting deeplink tests...');
|
||||
log('\n=== Starting Deeplink Tests ===');
|
||||
|
||||
// Register URL scheme if needed
|
||||
checkAndRegisterUrlScheme(log);
|
||||
|
||||
// Check if test data files exist first
|
||||
const requiredFiles = [
|
||||
'.generated/test-env.json',
|
||||
'.generated/claim_details.json',
|
||||
'.generated/contacts.json'
|
||||
];
|
||||
|
||||
for (const file of requiredFiles) {
|
||||
if (!existsSync(file)) {
|
||||
log(`⚠️ Required file ${file} does not exist`);
|
||||
log('⚠️ Skipping deeplink tests');
|
||||
return;
|
||||
}
|
||||
// Validate test data before proceeding
|
||||
let testEnv, contacts;
|
||||
try {
|
||||
({ testEnv, contacts } = validateTestData(log));
|
||||
} catch (error) {
|
||||
log('❌ Cannot proceed with tests due to invalid test data');
|
||||
log(`Error: ${error.message}`);
|
||||
log('Please ensure test data is properly generated before running tests');
|
||||
process.exit(1); // Exit with error code
|
||||
}
|
||||
|
||||
// Now we can safely create the deeplink tests knowing we have valid data
|
||||
const deeplinkTests = [
|
||||
{
|
||||
url: `timesafari://claim/${testEnv.CLAIM_ID}`,
|
||||
description: 'Claim view'
|
||||
},
|
||||
{
|
||||
url: `timesafari://claim-cert/${testEnv.CERT_ID || testEnv.CLAIM_ID}`,
|
||||
description: 'Claim certificate view'
|
||||
},
|
||||
{
|
||||
url: `timesafari://claim-add-raw/${testEnv.RAW_CLAIM_ID || testEnv.CLAIM_ID}`,
|
||||
description: 'Raw claim addition'
|
||||
},
|
||||
{
|
||||
url: 'timesafari://did/test',
|
||||
description: 'DID view with test identifier'
|
||||
},
|
||||
{
|
||||
url: `timesafari://did/${testEnv.CONTACT1_DID}`,
|
||||
description: 'DID view with contact DID'
|
||||
},
|
||||
{
|
||||
url: (() => {
|
||||
if (!testEnv?.CONTACT1_DID) {
|
||||
throw new Error('Cannot construct contact-edit URL: CONTACT1_DID is missing');
|
||||
}
|
||||
const url = `timesafari://contact-edit/${testEnv.CONTACT1_DID}`;
|
||||
log('Created contact-edit URL:', url);
|
||||
return url;
|
||||
})(),
|
||||
description: 'Contact editing'
|
||||
},
|
||||
{
|
||||
url: `timesafari://contacts/import?contacts=${encodeURIComponent(JSON.stringify(contacts))}`,
|
||||
description: 'Contacts import'
|
||||
}
|
||||
];
|
||||
|
||||
// Log the final test configuration
|
||||
log('\n5. Final Test Configuration:');
|
||||
deeplinkTests.forEach((test, i) => {
|
||||
log(`\nTest ${i + 1}:`);
|
||||
log(`Description: ${test.description}`);
|
||||
log(`URL: ${test.url}`);
|
||||
});
|
||||
|
||||
// Show instructions for iOS security dialogs
|
||||
log('\n📱 IMPORTANT: iOS Security Dialog Instructions:');
|
||||
log('1. Each deeplink test will trigger a security confirmation dialog');
|
||||
log('2. You MUST click "Open" on each dialog to continue testing');
|
||||
log('3. The app must be running in the FOREGROUND');
|
||||
log('4. You will need to press Enter in this terminal after handling each dialog');
|
||||
log('5. You can abort the testing process by pressing Ctrl+C\n');
|
||||
|
||||
// Ensure app is in foreground
|
||||
log('⚠️ IMPORTANT: Please make sure the app is in the FOREGROUND now');
|
||||
await question('Press Enter when the app is visible and in the foreground...');
|
||||
|
||||
try {
|
||||
// Load test data
|
||||
log('📂 Loading test data from .generated directory');
|
||||
let testEnv, claimDetails, contacts;
|
||||
|
||||
try {
|
||||
const testEnvContent = readFileSync('.generated/test-env.json', 'utf8');
|
||||
testEnv = JSON.parse(testEnvContent);
|
||||
log('✅ Loaded test-env.json');
|
||||
} catch (error) {
|
||||
log(`⚠️ Failed to load test-env.json: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const claimDetailsContent = readFileSync('.generated/claim_details.json', 'utf8');
|
||||
claimDetails = JSON.parse(claimDetailsContent);
|
||||
log('✅ Loaded claim_details.json');
|
||||
} catch (error) {
|
||||
log(`⚠️ Failed to load claim_details.json: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const contactsContent = readFileSync('.generated/contacts.json', 'utf8');
|
||||
contacts = JSON.parse(contactsContent);
|
||||
log('✅ Loaded contacts.json');
|
||||
} catch (error) {
|
||||
log(`⚠️ Failed to load contacts.json: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the app URL scheme is registered in the simulator
|
||||
log('🔍 Checking if URL scheme is registered in simulator...');
|
||||
try {
|
||||
// Attempt to open a simple URL with the scheme
|
||||
execSync(`xcrun simctl openurl booted "timesafari://test"`, { stdio: 'pipe' });
|
||||
log('✅ URL scheme is registered and working');
|
||||
} catch (error) {
|
||||
const errorMessage = error.message || '';
|
||||
|
||||
// Check for the specific error code that indicates an unregistered URL scheme
|
||||
if (errorMessage.includes('OSStatus error -10814') || errorMessage.includes('NSOSStatusErrorDomain, code=-10814')) {
|
||||
log('⚠️ URL scheme "timesafari://" is not registered in the app or app is not running');
|
||||
log('⚠️ The scheme was added to Info.plist but the app may need to be rebuilt');
|
||||
log('⚠️ Trying to continue with tests, but they may fail');
|
||||
}
|
||||
}
|
||||
|
||||
// Test URLs
|
||||
const deeplinkTests = [
|
||||
{
|
||||
url: `timesafari://claim/${claimDetails.claim_id}`,
|
||||
description: 'Claim view'
|
||||
},
|
||||
{
|
||||
url: `timesafari://claim-cert/${claimDetails.claim_id}`,
|
||||
description: 'Claim certificate view'
|
||||
},
|
||||
{
|
||||
url: `timesafari://claim-add-raw/${claimDetails.claim_id}`,
|
||||
description: 'Raw claim addition'
|
||||
},
|
||||
{
|
||||
url: 'timesafari://did/test',
|
||||
description: 'DID view with test identifier'
|
||||
},
|
||||
{
|
||||
url: `timesafari://did/${testEnv.CONTACT1_DID}`,
|
||||
description: 'DID view with contact DID'
|
||||
},
|
||||
{
|
||||
url: `timesafari://contact-edit/${testEnv.CONTACT1_DID}`,
|
||||
description: 'Contact editing'
|
||||
},
|
||||
{
|
||||
url: `timesafari://contacts/import?contacts=${encodeURIComponent(JSON.stringify(contacts))}`,
|
||||
description: 'Contacts import'
|
||||
}
|
||||
];
|
||||
|
||||
// Execute each test
|
||||
let testsCompleted = 0;
|
||||
let testsSkipped = 0;
|
||||
|
||||
for (const test of deeplinkTests) {
|
||||
// Show upcoming test info before execution
|
||||
log('\n📱 NEXT TEST:');
|
||||
log('------------------------');
|
||||
log(`Description: ${test.description}`);
|
||||
log(`URL to test: ${test.url}`);
|
||||
log('------------------------');
|
||||
|
||||
// Clear prompt for user action
|
||||
await question('\n⏎ Press Enter to execute this test (or Ctrl+C to quit)...');
|
||||
|
||||
try {
|
||||
log(`\n🔗 Testing deeplink: ${test.description}`);
|
||||
log(`URL: ${test.url}`);
|
||||
log('🚀 Executing deeplink test...');
|
||||
log('⚠️ iOS SECURITY DIALOG WILL APPEAR - Click "Open" to continue');
|
||||
|
||||
execSync(`xcrun simctl openurl booted "${test.url}"`, { stdio: 'pipe' });
|
||||
log(`✅ Successfully executed: ${test.description}`);
|
||||
testsCompleted++;
|
||||
|
||||
// Wait between tests
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
// Show progress
|
||||
log(`\n📊 Progress: ${testsCompleted}/${deeplinkTests.length} tests completed`);
|
||||
|
||||
// If there are more tests, show the next one
|
||||
if (testsCompleted < deeplinkTests.length) {
|
||||
const nextTest = deeplinkTests[testsCompleted];
|
||||
log('\n⏭️ NEXT UP:');
|
||||
log('------------------------');
|
||||
log(`Next test will be: ${nextTest.description}`);
|
||||
log(`URL: ${nextTest.url}`);
|
||||
log('------------------------');
|
||||
await question('\n⏎ Press Enter when ready for the next test...');
|
||||
}
|
||||
} catch (deeplinkError) {
|
||||
const errorMessage = deeplinkError.message || '';
|
||||
|
||||
@@ -501,22 +730,35 @@ const runDeeplinkTests = async (log) => {
|
||||
log(`⚠️ Error: ${errorMessage}`);
|
||||
}
|
||||
log('⚠️ Continuing with next test...');
|
||||
|
||||
// Show next test info after error handling
|
||||
if (testsCompleted + testsSkipped < deeplinkTests.length) {
|
||||
const nextTest = deeplinkTests[testsCompleted + testsSkipped];
|
||||
log('\n⏭️ NEXT UP:');
|
||||
log('------------------------');
|
||||
log(`Next test will be: ${nextTest.description}`);
|
||||
log(`URL: ${nextTest.url}`);
|
||||
log('------------------------');
|
||||
await question('\n⏎ Press Enter when ready for the next test...');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log(`✅ Deeplink tests completed: ${testsCompleted} successful, ${testsSkipped} skipped`);
|
||||
log('\n🎉 All deeplink tests completed!');
|
||||
log(`✅ Successful: ${testsCompleted}`);
|
||||
log(`⚠️ Skipped: ${testsSkipped}`);
|
||||
|
||||
if (testsSkipped > 0) {
|
||||
log('\n📝 Note about skipped tests:');
|
||||
log('1. The app needs to have the URL scheme registered in Info.plist');
|
||||
log('2. The app needs to be rebuilt after registering the URL scheme');
|
||||
log('3. The app must be running in the foreground for deeplink tests to work');
|
||||
log('4. If these conditions are met and tests still fail, check URL handling in the app code');
|
||||
log('4. iOS security dialogs must be manually approved for each deeplink test');
|
||||
log('5. If these conditions are met and tests still fail, check URL handling in the app code');
|
||||
}
|
||||
} catch (error) {
|
||||
log(`❌ Deeplink tests setup failed: ${error.message}`);
|
||||
log('⚠️ Deeplink tests might be unavailable or test data is missing');
|
||||
// Don't rethrow the error to prevent halting the process
|
||||
}
|
||||
};
|
||||
|
||||
@@ -558,7 +800,7 @@ const checkAndRegisterUrlScheme = (log) => {
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>app.timesafari.app</string>
|
||||
<string>app.timesafari</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>timesafari</string>
|
||||
@@ -585,14 +827,51 @@ const checkAndRegisterUrlScheme = (log) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to get the app identifier from package.json or capacitor config
|
||||
const getAppIdentifier = () => {
|
||||
try {
|
||||
// Try to read from capacitor.config.ts/js/json
|
||||
if (existsSync('capacitor.config.json')) {
|
||||
const config = JSON.parse(readFileSync('capacitor.config.json', 'utf8'));
|
||||
return config.appId;
|
||||
}
|
||||
if (existsSync('capacitor.config.js')) {
|
||||
// We can't directly require the file, but we can try to extract the appId
|
||||
const content = readFileSync('capacitor.config.js', 'utf8');
|
||||
const match = content.match(/appId:\s*['"]([^'"]+)['"]/);
|
||||
if (match && match[1]) return match[1];
|
||||
}
|
||||
if (existsSync('capacitor.config.ts')) {
|
||||
// Similar approach for TypeScript
|
||||
const content = readFileSync('capacitor.config.ts', 'utf8');
|
||||
const match = content.match(/appId:\s*['"]([^'"]+)['"]/);
|
||||
if (match && match[1]) return match[1];
|
||||
}
|
||||
|
||||
// Fall back to package.json
|
||||
const packageJson = JSON.parse(readFileSync('package.json', 'utf8'));
|
||||
if (packageJson.capacitor && packageJson.capacitor.appId) {
|
||||
return packageJson.capacitor.appId;
|
||||
}
|
||||
|
||||
// Default fallback
|
||||
return 'app.timesafari';
|
||||
} catch (error) {
|
||||
console.error('Error getting app identifier:', error);
|
||||
return 'app.timesafari'; // Default fallback
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Runs the complete iOS test suite including build and testing
|
||||
*
|
||||
* The function performs the following steps:
|
||||
* 1. Syncs the Capacitor project with latest web build
|
||||
* 2. Builds the app using xcodebuild
|
||||
* 3. Optionally runs tests if configured
|
||||
* 4. Launches the app in the simulator
|
||||
* 1. Cleans and resets the iOS platform
|
||||
* 2. Verifies prerequisites and project setup
|
||||
* 3. Syncs the Capacitor project with latest web build
|
||||
* 4. Builds the app using xcodebuild
|
||||
* 5. Optionally runs tests if configured
|
||||
* 6. Launches the app in the simulator
|
||||
*
|
||||
* If no simulator is running, it automatically selects and boots one.
|
||||
*
|
||||
@@ -617,7 +896,16 @@ async function runIosTests() {
|
||||
try {
|
||||
log('🚀 Starting iOS build and test process...');
|
||||
|
||||
// Generate test data first
|
||||
// Clean and reset iOS platform first
|
||||
const cleanSuccess = await cleanIosPlatform(log);
|
||||
if (!cleanSuccess) {
|
||||
throw new Error('Failed to clean and reset iOS platform. Please check the logs for details.');
|
||||
}
|
||||
|
||||
// Check prerequisites
|
||||
await checkPrerequisites(log);
|
||||
|
||||
// Generate test data
|
||||
await generateTestData(log);
|
||||
|
||||
// Verify Xcode installation
|
||||
@@ -626,8 +914,7 @@ async function runIosTests() {
|
||||
// Check for simulator or boot one if needed
|
||||
const simulator = await checkSimulator(log);
|
||||
|
||||
// Build web assets and configure iOS project
|
||||
await buildWebAssets(log);
|
||||
// Configure iOS project
|
||||
await configureIosProject(log);
|
||||
|
||||
// Build and test using the selected simulator
|
||||
|
||||
265
src/components/ActivityListItem.vue
Normal file
@@ -0,0 +1,265 @@
|
||||
<template>
|
||||
<li>
|
||||
<!-- Last viewed separator -->
|
||||
<div
|
||||
v-if="record.jwtId == lastViewedClaimId"
|
||||
class="border-b border-dashed border-slate-300 text-orange-400 mt-4 mb-6 font-bold text-sm"
|
||||
>
|
||||
<span class="block w-fit mx-auto -mb-2.5 bg-white px-2">
|
||||
You've already seen all the following
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center justify-between gap-2 text-lg bg-slate-200 border border-slate-300 border-b-0 rounded-t-md px-3 sm:px-4 py-1 sm:py-2"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<div v-if="record.issuerDid">
|
||||
<EntityIcon
|
||||
:entity-id="record.issuerDid"
|
||||
class="rounded-full bg-white overflow-hidden !size-[2rem] object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<font-awesome
|
||||
icon="person-circle-question"
|
||||
class="text-slate-300 text-[2rem]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-semibold">
|
||||
{{ record.issuer.known ? record.issuer.displayName : "" }}
|
||||
</h3>
|
||||
<p class="ms-auto text-xs text-slate-500 italic">
|
||||
{{ friendlyDate }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a class="cursor-pointer" @click="$emit('loadClaim', record.jwtId)">
|
||||
<font-awesome icon="circle-info" class="fa-fw text-slate-500" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-100 rounded-b-md border border-slate-300 p-3 sm:p-4">
|
||||
<!-- Record Image -->
|
||||
<div
|
||||
v-if="record.image"
|
||||
class="bg-cover mb-6 -mt-3 sm:-mt-4 -mx-3 sm:-mx-4"
|
||||
:style="`background-image: url(${record.image});`"
|
||||
>
|
||||
<a
|
||||
class="block bg-slate-100/50 backdrop-blur-md px-6 py-4 cursor-pointer"
|
||||
@click="$emit('viewImage', record.image)"
|
||||
>
|
||||
<img
|
||||
class="w-full h-auto max-w-lg max-h-96 object-contain mx-auto drop-shadow-md"
|
||||
:src="record.image"
|
||||
alt="Activity image"
|
||||
@load="$emit('cacheImage', record.image)"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="relative flex justify-between gap-4 max-w-[40rem] mx-auto mb-5"
|
||||
>
|
||||
<!-- Source -->
|
||||
<div
|
||||
class="w-[8rem] sm:w-[12rem] text-center bg-white border border-slate-200 rounded p-2 sm:p-3"
|
||||
>
|
||||
<div class="relative w-fit mx-auto">
|
||||
<div>
|
||||
<!-- Project Icon -->
|
||||
<div v-if="record.providerPlanName">
|
||||
<ProjectIcon
|
||||
:entity-id="record.providerPlanName"
|
||||
:icon-size="48"
|
||||
class="rounded size-[3rem] sm:size-[4rem] *:w-full *:h-full"
|
||||
/>
|
||||
</div>
|
||||
<!-- Identicon for DIDs -->
|
||||
<div v-else-if="record.agentDid">
|
||||
<EntityIcon
|
||||
:entity-id="record.agentDid"
|
||||
:profile-image-url="record.issuer.profileImageUrl"
|
||||
class="rounded-full bg-slate-100 overflow-hidden !size-[3rem] sm:!size-[4rem]"
|
||||
/>
|
||||
</div>
|
||||
<!-- Unknown Person -->
|
||||
<div v-else>
|
||||
<font-awesome
|
||||
icon="person-circle-question"
|
||||
class="text-slate-300 text-[3rem] sm:text-[4rem]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="record.providerPlanName || record.giver.known"
|
||||
class="text-xs mt-2 truncate"
|
||||
>
|
||||
<font-awesome
|
||||
:icon="record.providerPlanName ? 'users' : 'user'"
|
||||
class="fa-fw text-slate-400"
|
||||
/>
|
||||
{{ record.providerPlanName || record.giver.displayName }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Arrow -->
|
||||
<div
|
||||
class="absolute inset-x-[8rem] sm:inset-x-[12rem] mx-2 top-1/2 -translate-y-1/2"
|
||||
>
|
||||
<div class="text-sm text-center leading-none font-semibold pe-[15px]">
|
||||
{{ fetchAmount }}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<hr
|
||||
class="grow border-t-[18px] sm:border-t-[24px] border-slate-300"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="shrink-0 w-0 h-0 border border-slate-300 border-t-[20px] sm:border-t-[25px] border-t-transparent border-b-[20px] sm:border-b-[25px] border-b-transparent border-s-[27px] sm:border-s-[34px] border-e-0"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Destination -->
|
||||
<div
|
||||
class="w-[8rem] sm:w-[12rem] text-center bg-white border border-slate-200 rounded p-2 sm:p-3"
|
||||
>
|
||||
<div class="relative w-fit mx-auto">
|
||||
<div>
|
||||
<!-- Project Icon -->
|
||||
<div v-if="record.recipientProjectName">
|
||||
<ProjectIcon
|
||||
:entity-id="record.recipientProjectName"
|
||||
:icon-size="48"
|
||||
class="rounded size-[3rem] sm:size-[4rem] *:w-full *:h-full"
|
||||
/>
|
||||
</div>
|
||||
<!-- Identicon for DIDs -->
|
||||
<div v-else-if="record.recipientDid">
|
||||
<EntityIcon
|
||||
:entity-id="record.recipientDid"
|
||||
:profile-image-url="record.receiver.profileImageUrl"
|
||||
class="rounded-full bg-slate-100 overflow-hidden !size-[3rem] sm:!size-[4rem]"
|
||||
/>
|
||||
</div>
|
||||
<!-- Unknown Person -->
|
||||
<div v-else>
|
||||
<font-awesome
|
||||
icon="person-circle-question"
|
||||
class="text-slate-300 text-[3rem] sm:text-[4rem]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="record.recipientProjectName || record.receiver.known"
|
||||
class="text-xs mt-2 truncate"
|
||||
>
|
||||
<font-awesome
|
||||
:icon="record.recipientProjectName ? 'users' : 'user'"
|
||||
class="fa-fw text-slate-400"
|
||||
/>
|
||||
{{ record.recipientProjectName || record.receiver.displayName }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<p class="font-medium">
|
||||
<a class="cursor-pointer" @click="$emit('loadClaim', record.jwtId)">
|
||||
{{ description }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from "vue-facing-decorator";
|
||||
import { GiveRecordWithContactInfo } from "../types";
|
||||
import EntityIcon from "./EntityIcon.vue";
|
||||
import { isGiveClaimType, notifyWhyCannotConfirm } from "../libs/util";
|
||||
import { containsHiddenDid } from "../libs/endorserServer";
|
||||
import ProjectIcon from "./ProjectIcon.vue";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
EntityIcon,
|
||||
ProjectIcon,
|
||||
},
|
||||
})
|
||||
export default class ActivityListItem extends Vue {
|
||||
@Prop() record!: GiveRecordWithContactInfo;
|
||||
@Prop() lastViewedClaimId?: string;
|
||||
@Prop() isRegistered!: boolean;
|
||||
@Prop() activeDid!: string;
|
||||
@Prop() confirmerIdList?: string[];
|
||||
|
||||
get fetchAmount(): string {
|
||||
const claim =
|
||||
(this.record.fullClaim as unknown).claim || this.record.fullClaim;
|
||||
|
||||
const amount = claim.object?.amountOfThisGood
|
||||
? this.displayAmount(claim.object.unitCode, claim.object.amountOfThisGood)
|
||||
: "";
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
const claim =
|
||||
(this.record.fullClaim as unknown).claim || this.record.fullClaim;
|
||||
|
||||
return `${claim.description}`;
|
||||
}
|
||||
|
||||
private displayAmount(code: string, amt: number) {
|
||||
return `${amt} ${this.currencyShortWordForCode(code, amt === 1)}`;
|
||||
}
|
||||
|
||||
private currencyShortWordForCode(unitCode: string, single: boolean) {
|
||||
return unitCode === "HUR" ? (single ? "hour" : "hours") : unitCode;
|
||||
}
|
||||
|
||||
get canConfirm(): boolean {
|
||||
if (!this.isRegistered) return false;
|
||||
if (!isGiveClaimType(this.record.fullClaim?.["@type"])) return false;
|
||||
if (this.confirmerIdList?.includes(this.activeDid)) return false;
|
||||
if (this.record.issuerDid === this.activeDid) return false;
|
||||
if (containsHiddenDid(this.record.fullClaim)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
handleConfirmClick() {
|
||||
if (!this.canConfirm) {
|
||||
notifyWhyCannotConfirm(
|
||||
this.$notify,
|
||||
this.isRegistered,
|
||||
this.record.fullClaim?.["@type"],
|
||||
this.record,
|
||||
this.activeDid,
|
||||
this.confirmerIdList,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit("confirmClaim", this.record);
|
||||
}
|
||||
|
||||
get friendlyDate(): string {
|
||||
const date = new Date(this.record.issuedAt);
|
||||
return date.toLocaleDateString(undefined, {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -28,7 +28,7 @@
|
||||
:src="imageUrl"
|
||||
class="max-h-[calc(100vh-5rem)] w-full h-full object-contain"
|
||||
alt="expanded shared content"
|
||||
@click.stop
|
||||
@click="close"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/** * @file InfiniteScroll.vue * @description A Vue component that implements
|
||||
infinite scrolling functionality using the Intersection Observer API. * This
|
||||
component emits a 'reached-bottom' event when the user scrolls near the bottom
|
||||
of the content. * It includes debouncing to prevent multiple rapid triggers and
|
||||
loading state management. * * @author Matthew Raymer * @version 1.0.0 */
|
||||
|
||||
<template>
|
||||
<div ref="scrollContainer">
|
||||
<slot />
|
||||
@@ -8,13 +14,51 @@
|
||||
<script lang="ts">
|
||||
import { Component, Emit, Prop, Vue } from "vue-facing-decorator";
|
||||
|
||||
/**
|
||||
* InfiniteScroll Component
|
||||
*
|
||||
* This component implements infinite scrolling functionality by observing when a user
|
||||
* scrolls near the bottom of the content. It uses the Intersection Observer API for
|
||||
* efficient scroll detection and includes debouncing to prevent multiple rapid triggers.
|
||||
*
|
||||
* Usage in template:
|
||||
* ```vue
|
||||
* <InfiniteScroll @reached-bottom="loadMore">
|
||||
* <div>Content goes here</div>
|
||||
* </InfiniteScroll>
|
||||
* ```
|
||||
*
|
||||
* Props:
|
||||
* - distance: number (default: 200) - Distance in pixels from the bottom at which to trigger the event
|
||||
*
|
||||
* Events:
|
||||
* - reached-bottom: Emitted when the user scrolls near the bottom of the content
|
||||
*/
|
||||
@Component
|
||||
export default class InfiniteScroll extends Vue {
|
||||
/** Distance in pixels from the bottom at which to trigger the reached-bottom event */
|
||||
@Prop({ default: 200 })
|
||||
readonly distance!: number;
|
||||
|
||||
/** Intersection Observer instance for detecting scroll position */
|
||||
private observer!: IntersectionObserver;
|
||||
|
||||
/** Flag to track initial render state */
|
||||
private isInitialRender = true;
|
||||
|
||||
/** Flag to prevent multiple simultaneous loading states */
|
||||
private isLoading = false;
|
||||
|
||||
/** Timeout ID for debouncing scroll events */
|
||||
private debounceTimeout: number | null = null;
|
||||
|
||||
/**
|
||||
* Vue lifecycle hook that runs after component updates.
|
||||
* Initializes the Intersection Observer if not already set up.
|
||||
*
|
||||
* @internal
|
||||
* Used internally by Vue's lifecycle system
|
||||
*/
|
||||
updated() {
|
||||
if (!this.observer) {
|
||||
const options = {
|
||||
@@ -30,18 +74,50 @@ export default class InfiniteScroll extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
// 'beforeUnmount' hook runs before unmounting the component
|
||||
/**
|
||||
* Vue lifecycle hook that runs before component unmounting.
|
||||
* Cleans up the Intersection Observer and any pending timeouts.
|
||||
*
|
||||
* @internal
|
||||
* Used internally by Vue's lifecycle system
|
||||
*/
|
||||
beforeUnmount() {
|
||||
if (this.observer) {
|
||||
this.observer.disconnect();
|
||||
}
|
||||
if (this.debounceTimeout) {
|
||||
window.clearTimeout(this.debounceTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles intersection observer callbacks when the sentinel element becomes visible.
|
||||
* Implements debouncing to prevent multiple rapid triggers and manages loading state.
|
||||
*
|
||||
* @param entries - Array of IntersectionObserverEntry objects
|
||||
* @returns false (required by @Emit decorator)
|
||||
*
|
||||
* @internal
|
||||
* Used internally by the Intersection Observer
|
||||
* @emits reached-bottom - Emitted when the user scrolls near the bottom
|
||||
*/
|
||||
@Emit("reached-bottom")
|
||||
handleIntersection(entries: IntersectionObserverEntry[]) {
|
||||
const entry = entries[0];
|
||||
if (entry.isIntersecting) {
|
||||
return true;
|
||||
if (entry.isIntersecting && !this.isLoading) {
|
||||
// Debounce the intersection event
|
||||
if (this.debounceTimeout) {
|
||||
window.clearTimeout(this.debounceTimeout);
|
||||
}
|
||||
|
||||
this.debounceTimeout = window.setTimeout(() => {
|
||||
this.isLoading = true;
|
||||
this.$emit("reached-bottom", true);
|
||||
// Reset loading state after a short delay
|
||||
setTimeout(() => {
|
||||
this.isLoading = false;
|
||||
}, 1000);
|
||||
}, 300);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
</p>
|
||||
|
||||
<p class="mt-4">
|
||||
Search for a topic, or search around your neighborhod under "Nearby".
|
||||
Search for a topic, or search around your neighborhood under "Nearby".
|
||||
</p>
|
||||
|
||||
<p class="mt-4">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<!-- QUICK NAV -->
|
||||
<nav id="QuickNav" class="fixed bottom-0 left-0 right-0 bg-slate-200 z-50">
|
||||
<ul class="flex text-2xl p-2 gap-2 max-w-3xl mx-auto">
|
||||
<ul class="flex text-2xl px-6 py-2 gap-1 max-w-3xl mx-auto">
|
||||
<!-- Home Feed -->
|
||||
<li
|
||||
:class="{
|
||||
@@ -52,7 +52,7 @@
|
||||
>
|
||||
<div class="flex flex-col items-center">
|
||||
<font-awesome icon="hand" class="fa-fw" />
|
||||
<span class="text-xs mt-1">your work</span>
|
||||
<span class="text-xs mt-1">yours</span>
|
||||
</div>
|
||||
</router-link>
|
||||
</li>
|
||||
|
||||
101
src/components/RegistrationGate.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div v-if="visible" class="dialog-overlay">
|
||||
<div class="dialog">
|
||||
<h1 class="text-xl font-bold text-center mb-4">Registration Needed</h1>
|
||||
|
||||
Before you can perform certain actions in the app, you need to register an account. It's easy, and it's FREE!
|
||||
|
||||
<div class="mt-8">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="block w-full text-center text-lg font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md mb-2"
|
||||
@click="onClickSaveChanges()"
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md mb-2"
|
||||
@click="onClickCancel()"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Vue, Component } from "vue-facing-decorator";
|
||||
|
||||
import { NotificationIface } from "../constants/app";
|
||||
|
||||
@Component
|
||||
export default class RegistrationGate extends Vue {
|
||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||
|
||||
callback: (text: string, expiresAt: string) => void = () => {};
|
||||
inviteIdentifier = "";
|
||||
text = "";
|
||||
visible = false;
|
||||
expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24 * 7)
|
||||
.toISOString()
|
||||
.substring(0, 10);
|
||||
|
||||
async open(
|
||||
inviteIdentifier: string,
|
||||
aCallback: (text: string, expiresAt: string) => void,
|
||||
) {
|
||||
this.callback = aCallback;
|
||||
this.inviteIdentifier = inviteIdentifier;
|
||||
this.visible = true;
|
||||
}
|
||||
|
||||
async onClickSaveChanges() {
|
||||
if (!this.expiresAt) {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "warning",
|
||||
title: "Needs Expiration",
|
||||
text: "You must select an expiration date.",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
} else {
|
||||
this.callback(this.text, this.expiresAt);
|
||||
this.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
onClickCancel() {
|
||||
this.visible = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.dialog-overlay {
|
||||
z-index: 50;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
background-color: white;
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
}
|
||||
</style>
|
||||
@@ -112,7 +112,6 @@ db.on("populate", async () => {
|
||||
// ended up throwing lots of errors to the user... and they'd end up in a state
|
||||
// where they couldn't take action because they couldn't unlock that identity.)
|
||||
|
||||
// check for the secret in storage
|
||||
async function useSecretAndInitializeAccountsDB(
|
||||
secretDB: SecretDexie,
|
||||
accountsDB: SensitiveDexie,
|
||||
@@ -214,6 +213,22 @@ export async function updateAccountSettings(
|
||||
}
|
||||
}
|
||||
|
||||
export async function logToDb(message: string): Promise<void> {
|
||||
await db.open();
|
||||
const todayKey = new Date().toDateString();
|
||||
// only keep one day's worth of logs
|
||||
const previous = await db.logs.get(todayKey);
|
||||
if (!previous) {
|
||||
// when this is today's first log, clear out everything previous
|
||||
// to avoid the log table getting too large
|
||||
// (let's limit a different way someday)
|
||||
await db.logs.clear();
|
||||
}
|
||||
const prevMessages = (previous && previous.message) || "";
|
||||
const fullMessage = `${prevMessages}\n${new Date().toISOString()} ${message}`;
|
||||
await db.logs.update(todayKey, { message: fullMessage });
|
||||
}
|
||||
|
||||
// similar method is in the sw_scripts/additional-scripts.js file
|
||||
export async function logConsoleAndDb(
|
||||
message: string,
|
||||
@@ -224,16 +239,5 @@ export async function logConsoleAndDb(
|
||||
} else {
|
||||
logger.log(`${new Date().toISOString()} ${message}`);
|
||||
}
|
||||
|
||||
await db.open();
|
||||
const todayKey = new Date().toDateString();
|
||||
// only keep one day's worth of logs
|
||||
const previous = await db.logs.get(todayKey);
|
||||
if (!previous) {
|
||||
// when this is today's first log, clear out everything previous
|
||||
await db.logs.clear();
|
||||
}
|
||||
const prevMessages = (previous && previous.message) || "";
|
||||
const fullMessage = `${prevMessages}\n${new Date().toISOString()} ${message}`;
|
||||
await db.logs.update(todayKey, { message: fullMessage });
|
||||
await logToDb(message);
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@ export function numberOrZero(str: string): number {
|
||||
return isNumeric(str) ? +str : 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* from https://tools.ietf.org/html/rfc3986#section-3
|
||||
* also useful is https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Definition
|
||||
|
||||
164
src/main.ts
@@ -7,7 +7,169 @@ import axios from "axios";
|
||||
import VueAxios from "vue-axios";
|
||||
import Notifications from "notiwind";
|
||||
import "./assets/styles/tailwind.css";
|
||||
import { FontAwesomeIcon } from "./lib/fontawesome";
|
||||
|
||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
import {
|
||||
faArrowDown,
|
||||
faArrowLeft,
|
||||
faArrowRight,
|
||||
faArrowRotateBackward,
|
||||
faArrowUpRightFromSquare,
|
||||
faArrowUp,
|
||||
faBan,
|
||||
faBitcoinSign,
|
||||
faBurst,
|
||||
faCalendar,
|
||||
faCamera,
|
||||
faCaretDown,
|
||||
faChair,
|
||||
faCheck,
|
||||
faChevronDown,
|
||||
faChevronLeft,
|
||||
faChevronRight,
|
||||
faChevronUp,
|
||||
faCircle,
|
||||
faCircleCheck,
|
||||
faCircleInfo,
|
||||
faCircleQuestion,
|
||||
faCircleUser,
|
||||
faClock,
|
||||
faCoins,
|
||||
faComment,
|
||||
faCopy,
|
||||
faDollar,
|
||||
faEllipsis,
|
||||
faEllipsisVertical,
|
||||
faEnvelopeOpenText,
|
||||
faEraser,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
faFileContract,
|
||||
faFileLines,
|
||||
faFilter,
|
||||
faFloppyDisk,
|
||||
faFolderOpen,
|
||||
faForward,
|
||||
faGift,
|
||||
faGlobe,
|
||||
faHammer,
|
||||
faHand,
|
||||
faHandHoldingDollar,
|
||||
faHandHoldingHeart,
|
||||
faHouseChimney,
|
||||
faImage,
|
||||
faImagePortrait,
|
||||
faLeftRight,
|
||||
faLightbulb,
|
||||
faLink,
|
||||
faLocationDot,
|
||||
faLongArrowAltLeft,
|
||||
faLongArrowAltRight,
|
||||
faMagnifyingGlass,
|
||||
faMessage,
|
||||
faMinus,
|
||||
faPen,
|
||||
faPersonCircleCheck,
|
||||
faPersonCircleQuestion,
|
||||
faPlus,
|
||||
faQuestion,
|
||||
faQrcode,
|
||||
faRightFromBracket,
|
||||
faRotate,
|
||||
faShareNodes,
|
||||
faSpinner,
|
||||
faSquare,
|
||||
faSquareCaretDown,
|
||||
faSquareCaretUp,
|
||||
faSquarePlus,
|
||||
faTrashCan,
|
||||
faTriangleExclamation,
|
||||
faUser,
|
||||
faUsers,
|
||||
faXmark,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
library.add(
|
||||
faArrowDown,
|
||||
faArrowLeft,
|
||||
faArrowRight,
|
||||
faArrowRotateBackward,
|
||||
faArrowUpRightFromSquare,
|
||||
faArrowUp,
|
||||
faBan,
|
||||
faBitcoinSign,
|
||||
faBurst,
|
||||
faCalendar,
|
||||
faCamera,
|
||||
faCaretDown,
|
||||
faChair,
|
||||
faCheck,
|
||||
faChevronDown,
|
||||
faChevronLeft,
|
||||
faChevronRight,
|
||||
faChevronUp,
|
||||
faCircle,
|
||||
faCircleCheck,
|
||||
faCircleInfo,
|
||||
faCircleQuestion,
|
||||
faCircleUser,
|
||||
faClock,
|
||||
faCoins,
|
||||
faComment,
|
||||
faCopy,
|
||||
faDollar,
|
||||
faEllipsis,
|
||||
faEllipsisVertical,
|
||||
faEnvelopeOpenText,
|
||||
faEraser,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
faFileContract,
|
||||
faFileLines,
|
||||
faFilter,
|
||||
faFloppyDisk,
|
||||
faFolderOpen,
|
||||
faForward,
|
||||
faGift,
|
||||
faGlobe,
|
||||
faHammer,
|
||||
faHand,
|
||||
faHandHoldingDollar,
|
||||
faHandHoldingHeart,
|
||||
faHouseChimney,
|
||||
faImage,
|
||||
faImagePortrait,
|
||||
faLeftRight,
|
||||
faLightbulb,
|
||||
faLink,
|
||||
faLocationDot,
|
||||
faLongArrowAltLeft,
|
||||
faLongArrowAltRight,
|
||||
faMagnifyingGlass,
|
||||
faMessage,
|
||||
faMinus,
|
||||
faPen,
|
||||
faPersonCircleCheck,
|
||||
faPersonCircleQuestion,
|
||||
faPlus,
|
||||
faQrcode,
|
||||
faQuestion,
|
||||
faRotate,
|
||||
faRightFromBracket,
|
||||
faShareNodes,
|
||||
faSpinner,
|
||||
faSquare,
|
||||
faSquareCaretDown,
|
||||
faSquareCaretUp,
|
||||
faSquarePlus,
|
||||
faTrashCan,
|
||||
faTriangleExclamation,
|
||||
faUser,
|
||||
faUsers,
|
||||
faXmark,
|
||||
);
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import Camera from "simple-vue-camera";
|
||||
import { logger } from "./utils/logger";
|
||||
|
||||
|
||||
@@ -157,6 +157,11 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: "InviteOneAcceptView",
|
||||
component: () => import("../views/InviteOneAcceptView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/logs",
|
||||
name: "logs",
|
||||
component: () => import("../views/LogView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/new-activity",
|
||||
name: "new-activity",
|
||||
@@ -281,6 +286,15 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: "user-profile",
|
||||
component: () => import("../views/UserProfileView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/deep-link-error",
|
||||
name: "deep-link-error",
|
||||
component: () => import("../views/DeepLinkErrorView.vue"),
|
||||
meta: {
|
||||
title: "Invalid Deep Link",
|
||||
requiresAuth: false,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const isElectron = window.location.protocol === "file:";
|
||||
|
||||
@@ -29,7 +29,12 @@
|
||||
*/
|
||||
|
||||
import { Router } from "vue-router";
|
||||
import { deepLinkSchemas, baseUrlSchema } from "../types/deepLinks";
|
||||
import {
|
||||
deepLinkSchemas,
|
||||
baseUrlSchema,
|
||||
routeSchema,
|
||||
DeepLinkRoute,
|
||||
} from "../types/deepLinks";
|
||||
import { logConsoleAndDb } from "../db";
|
||||
import type { DeepLinkError } from "../interfaces/deepLinks";
|
||||
|
||||
@@ -111,7 +116,7 @@ export class DeepLinkHandler {
|
||||
): Promise<void> {
|
||||
const routeMap: Record<string, string> = {
|
||||
"user-profile": "user-profile",
|
||||
project: "project",
|
||||
"project-details": "project-details",
|
||||
"onboard-meeting-setup": "onboard-meeting-setup",
|
||||
"invite-one-accept": "invite-one-accept",
|
||||
"contact-import": "contact-import",
|
||||
@@ -124,25 +129,63 @@ export class DeepLinkHandler {
|
||||
did: "did",
|
||||
};
|
||||
|
||||
const routeName = routeMap[path];
|
||||
if (!routeName) {
|
||||
// First try to validate the route path
|
||||
let routeName: string;
|
||||
|
||||
try {
|
||||
// Validate route exists
|
||||
const validRoute = routeSchema.parse(path) as DeepLinkRoute;
|
||||
routeName = routeMap[validRoute];
|
||||
} catch (error) {
|
||||
// Log the invalid route attempt
|
||||
logConsoleAndDb(`[DeepLink] Invalid route path: ${path}`, true);
|
||||
|
||||
// Redirect to error page with information about the invalid link
|
||||
await this.router.replace({
|
||||
name: "deep-link-error",
|
||||
query: {
|
||||
originalPath: path,
|
||||
errorCode: "INVALID_ROUTE",
|
||||
message: `The link you followed (${path}) is not supported`,
|
||||
},
|
||||
});
|
||||
|
||||
throw {
|
||||
code: "INVALID_ROUTE",
|
||||
message: `Unsupported route: ${path}`,
|
||||
};
|
||||
}
|
||||
|
||||
// Validate parameters based on route type
|
||||
// Continue with parameter validation as before...
|
||||
const schema = deepLinkSchemas[path as keyof typeof deepLinkSchemas];
|
||||
const validatedParams = await schema.parseAsync({
|
||||
...params,
|
||||
...query,
|
||||
});
|
||||
|
||||
await this.router.replace({
|
||||
name: routeName,
|
||||
params: validatedParams,
|
||||
query,
|
||||
});
|
||||
try {
|
||||
const validatedParams = await schema.parseAsync({
|
||||
...params,
|
||||
...query,
|
||||
});
|
||||
|
||||
await this.router.replace({
|
||||
name: routeName,
|
||||
params: validatedParams,
|
||||
query,
|
||||
});
|
||||
} catch (error) {
|
||||
// For parameter validation errors, provide specific error feedback
|
||||
await this.router.replace({
|
||||
name: "deep-link-error",
|
||||
query: {
|
||||
originalPath: path,
|
||||
errorCode: "INVALID_PARAMETERS",
|
||||
message: `The link parameters are invalid: ${(error as Error).message}`,
|
||||
},
|
||||
});
|
||||
|
||||
throw {
|
||||
code: "INVALID_PARAMETERS",
|
||||
message: (error as Error).message,
|
||||
details: error,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,13 +27,35 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
// Base URL validation schema
|
||||
// Add a union type of all valid route paths
|
||||
export const VALID_DEEP_LINK_ROUTES = [
|
||||
"user-profile",
|
||||
"project-details",
|
||||
"onboard-meeting-setup",
|
||||
"invite-one-accept",
|
||||
"contact-import",
|
||||
"confirm-gift",
|
||||
"claim",
|
||||
"claim-cert",
|
||||
"claim-add-raw",
|
||||
"contact-edit",
|
||||
"contacts",
|
||||
"did",
|
||||
] as const;
|
||||
|
||||
// Create a type from the array
|
||||
export type DeepLinkRoute = (typeof VALID_DEEP_LINK_ROUTES)[number];
|
||||
|
||||
// Update your schema definitions to use this type
|
||||
export const baseUrlSchema = z.object({
|
||||
scheme: z.literal("timesafari"),
|
||||
path: z.string(),
|
||||
queryParams: z.record(z.string()).optional(),
|
||||
});
|
||||
|
||||
// Use the type to ensure route validation
|
||||
export const routeSchema = z.enum(VALID_DEEP_LINK_ROUTES);
|
||||
|
||||
// Parameter validation schemas for each route type
|
||||
export const deepLinkSchemas = {
|
||||
"user-profile": z.object({
|
||||
|
||||
25
src/types/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { GiveSummaryRecord, GiveVerifiableCredential } from "interfaces";
|
||||
|
||||
export interface GiveRecordWithContactInfo extends GiveSummaryRecord {
|
||||
jwtId: string;
|
||||
fullClaim: GiveVerifiableCredential;
|
||||
giver: {
|
||||
known: boolean;
|
||||
displayName: string;
|
||||
profileImageUrl?: string;
|
||||
};
|
||||
issuer: {
|
||||
known: boolean;
|
||||
displayName: string;
|
||||
profileImageUrl?: string;
|
||||
};
|
||||
receiver: {
|
||||
known: boolean;
|
||||
displayName: string;
|
||||
profileImageUrl?: string;
|
||||
};
|
||||
providerPlanName?: string;
|
||||
recipientProjectName?: string;
|
||||
description: string;
|
||||
image?: string;
|
||||
}
|
||||
@@ -1,18 +1,46 @@
|
||||
import { logToDb } from "../db";
|
||||
|
||||
function safeStringify(obj: unknown) {
|
||||
const seen = new WeakSet();
|
||||
|
||||
return JSON.stringify(obj, (key, value) => {
|
||||
if (typeof value === "object" && value !== null) {
|
||||
if (seen.has(value)) {
|
||||
return "[Circular]";
|
||||
}
|
||||
seen.add(value);
|
||||
}
|
||||
|
||||
if (typeof value === "function") {
|
||||
return `[Function: ${value.name || "anonymous"}]`;
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
export const logger = {
|
||||
log: (message: string, ...args: unknown[]) => {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(message, ...args);
|
||||
const argsString = args.length > 0 ? " - " + safeStringify(args) : "";
|
||||
logToDb(message + argsString);
|
||||
}
|
||||
},
|
||||
warn: (message: string, ...args: unknown[]) => {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(message, ...args);
|
||||
const argsString = args.length > 0 ? " - " + safeStringify(args) : "";
|
||||
logToDb(message + argsString);
|
||||
}
|
||||
},
|
||||
error: (message: string, ...args: unknown[]) => {
|
||||
// Errors will always be logged
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(message, ...args); // Errors should always be logged
|
||||
console.error(message, ...args);
|
||||
const argsString = args.length > 0 ? " - " + safeStringify(args) : "";
|
||||
logToDb(message + argsString);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -259,7 +259,7 @@
|
||||
<span class="mb-2 font-bold">Location for Searches</span>
|
||||
<router-link
|
||||
:to="{ name: 'search-area' }"
|
||||
class="text-m bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-2"
|
||||
class="text-m bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md mb-2"
|
||||
>
|
||||
{{ isSearchAreasSet ? "Change" : "Set" }} Search Area…
|
||||
</router-link>
|
||||
@@ -471,7 +471,7 @@
|
||||
<!-- id used by puppeteer test script -->
|
||||
<h3
|
||||
id="advanced"
|
||||
class="text-sm uppercase font-semibold mb-3"
|
||||
class="text-blue-500 text-sm font-semibold mb-3"
|
||||
@click="showAdvanced = !showAdvanced"
|
||||
>
|
||||
Advanced
|
||||
@@ -562,11 +562,22 @@
|
||||
<router-link
|
||||
id="switch-identity-link"
|
||||
:to="{ name: 'identity-switcher' }"
|
||||
class="block w-fit text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md mb-2"
|
||||
class="block w-fit text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md mb-2"
|
||||
>
|
||||
Switch Identifier
|
||||
</router-link>
|
||||
|
||||
<div class="flex mt-4">
|
||||
<button>
|
||||
<router-link
|
||||
:to="{ name: 'statistics' }"
|
||||
class="block w-fit text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md mb-2"
|
||||
>
|
||||
See Global Animated History of Giving
|
||||
</router-link>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="sectionImportContactsSettings" class="mt-4">
|
||||
<h2 class="text-slate-500 text-sm font-bold">
|
||||
Import Contacts & Settings Database
|
||||
@@ -856,17 +867,6 @@
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<div class="flex mt-4">
|
||||
<button>
|
||||
<router-link
|
||||
:to="{ name: 'statistics' }"
|
||||
class="block w-fit text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md mb-2"
|
||||
>
|
||||
See Global Animated History of Giving
|
||||
</router-link>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="sectionPasskeyExpiration" class="flex justify-between">
|
||||
<span>
|
||||
<span class="text-slate-500 text-sm font-bold mb-2">
|
||||
@@ -912,6 +912,13 @@
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<router-link
|
||||
:to="{ name: 'logs' }"
|
||||
class="block w-fit text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md mb-2"
|
||||
>
|
||||
View Logs
|
||||
</router-link>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -1071,7 +1078,7 @@ export default class AccountViewView extends Vue {
|
||||
try {
|
||||
const headers = await getHeaders(this.activeDid);
|
||||
const response = await this.axios.get(
|
||||
this.apiServer +
|
||||
this.partnerApiServer +
|
||||
"/api/partner/userProfileForIssuer/" +
|
||||
this.activeDid,
|
||||
{ headers },
|
||||
@@ -1889,7 +1896,7 @@ export default class AccountViewView extends Vue {
|
||||
);
|
||||
}
|
||||
const response = await this.axios.post(
|
||||
this.apiServer + "/api/partner/userProfile",
|
||||
this.partnerApiServer + "/api/partner/userProfile",
|
||||
payload,
|
||||
{ headers },
|
||||
);
|
||||
@@ -1977,7 +1984,7 @@ export default class AccountViewView extends Vue {
|
||||
try {
|
||||
const headers = await getHeaders(this.activeDid);
|
||||
const response = await this.axios.delete(
|
||||
this.apiServer + "/api/partner/userProfile",
|
||||
this.partnerApiServer + "/api/partner/userProfile",
|
||||
{ headers },
|
||||
);
|
||||
if (response.status === 204) {
|
||||
|
||||
@@ -829,7 +829,7 @@ export default class ConfirmGiftView extends Vue {
|
||||
3000,
|
||||
);
|
||||
} else {
|
||||
console.error("Got error submitting the confirmation:", result);
|
||||
logger.error("Got error submitting the confirmation:", result);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<!-- Breadcrumb -->
|
||||
<div class="mb-8">
|
||||
<!-- Back -->
|
||||
<div class="text-lg text-center font-light relative px-7">
|
||||
<div class="relative px-7">
|
||||
<h1
|
||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||
class="text-lg text-center font-light px-2 py-1 absolute -left-2 -top-1"
|
||||
@click="$router.back()"
|
||||
>
|
||||
<font-awesome icon="chevron-left" class="fa-fw" />
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
<button
|
||||
v-if="!showGiveNumbers"
|
||||
href=""
|
||||
class="text-md bg-gradient-to-b shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-2 px-1 py-1 rounded-md"
|
||||
class="text-md bg-gradient-to-b shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-3 px-3 py-1.5 rounded-md"
|
||||
:style="
|
||||
contactsSelected.length > 0
|
||||
? 'background-image: linear-gradient(to bottom, #3b82f6, #1e40af);'
|
||||
@@ -130,7 +130,7 @@
|
||||
<div class="w-full text-right">
|
||||
<button
|
||||
href=""
|
||||
class="text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1 py-1 rounded-md"
|
||||
class="text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 rounded-md"
|
||||
@click="toggleShowContactAmounts()"
|
||||
>
|
||||
{{
|
||||
@@ -180,14 +180,7 @@
|
||||
data-testId="contactListItem"
|
||||
>
|
||||
<div class="grow overflow-hidden">
|
||||
<div class="flex items-center">
|
||||
<EntityIcon
|
||||
:contact="contact"
|
||||
:icon-size="24"
|
||||
class="inline-block align-text-bottom border border-slate-300 rounded cursor-pointer"
|
||||
@click="showLargeIdenticon = contact"
|
||||
/>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<input
|
||||
v-if="!showGiveNumbers"
|
||||
type="checkbox"
|
||||
@@ -204,14 +197,19 @@
|
||||
"
|
||||
/>
|
||||
|
||||
<h2
|
||||
class="text-base font-semibold ml-2 w-1/3 truncate flex-shrink-0"
|
||||
>
|
||||
<EntityIcon
|
||||
:contact="contact"
|
||||
:icon-size="48"
|
||||
class="inline-block align-text-bottom border border-slate-300 rounded cursor-pointer overflow-hidden"
|
||||
@click="showLargeIdenticon = contact"
|
||||
/>
|
||||
|
||||
<h2 class="text-base font-semibold w-1/3 truncate flex-shrink-0">
|
||||
{{ contactNameNonBreakingSpace(contact.name) }}
|
||||
</h2>
|
||||
|
||||
<span>
|
||||
<div class="flex items-center">
|
||||
<div class="flex gap-2 items-center">
|
||||
<router-link
|
||||
:to="{
|
||||
path: '/did/' + encodeURIComponent(contact.did),
|
||||
@@ -220,81 +218,79 @@
|
||||
>
|
||||
<font-awesome
|
||||
icon="circle-info"
|
||||
class="text-xl text-blue-500 ml-4"
|
||||
class="text-xl text-blue-500"
|
||||
/>
|
||||
</router-link>
|
||||
|
||||
<span class="ml-4 text-sm overflow-hidden">{{
|
||||
<span class="text-sm overflow-hidden">{{
|
||||
libsUtil.shortDid(contact.did)
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="ml-4 text-sm">
|
||||
<div class="text-sm">
|
||||
{{ contact.notes }}
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div id="ContactActions" class="flex gap-1.5 mt-2">
|
||||
<div
|
||||
v-if="showGiveNumbers && contact.did != activeDid"
|
||||
class="ml-auto flex gap-1.5"
|
||||
<div
|
||||
v-if="showGiveNumbers && contact.did != activeDid"
|
||||
class="ml-auto flex gap-1.5 mt-2"
|
||||
>
|
||||
<button
|
||||
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-l-md"
|
||||
:title="givenToMeDescriptions[contact.did] || ''"
|
||||
@click="confirmShowGiftedDialog(contact.did, activeDid)"
|
||||
>
|
||||
<button
|
||||
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-l-md"
|
||||
:title="givenToMeDescriptions[contact.did] || ''"
|
||||
@click="confirmShowGiftedDialog(contact.did, activeDid)"
|
||||
>
|
||||
From:
|
||||
<br />
|
||||
{{
|
||||
/* eslint-disable prettier/prettier */
|
||||
showGiveTotals
|
||||
? ((givenToMeConfirmed[contact.did] || 0)
|
||||
+ (givenToMeUnconfirmed[contact.did] || 0))
|
||||
: showGiveConfirmed
|
||||
? (givenToMeConfirmed[contact.did] || 0)
|
||||
: (givenToMeUnconfirmed[contact.did] || 0)
|
||||
/* eslint-enable prettier/prettier */
|
||||
}}
|
||||
</button>
|
||||
From:
|
||||
<br />
|
||||
{{
|
||||
/* eslint-disable prettier/prettier */
|
||||
showGiveTotals
|
||||
? ((givenToMeConfirmed[contact.did] || 0)
|
||||
+ (givenToMeUnconfirmed[contact.did] || 0))
|
||||
: showGiveConfirmed
|
||||
? (givenToMeConfirmed[contact.did] || 0)
|
||||
: (givenToMeUnconfirmed[contact.did] || 0)
|
||||
/* eslint-enable prettier/prettier */
|
||||
}}
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white -ml-1.5 px-2 py-1.5 rounded-r-md border-l"
|
||||
:title="givenByMeDescriptions[contact.did] || ''"
|
||||
@click="confirmShowGiftedDialog(activeDid, contact.did)"
|
||||
>
|
||||
To:
|
||||
<br />
|
||||
{{
|
||||
/* eslint-disable prettier/prettier */
|
||||
showGiveTotals
|
||||
? ((givenByMeConfirmed[contact.did] || 0)
|
||||
+ (givenByMeUnconfirmed[contact.did] || 0))
|
||||
: showGiveConfirmed
|
||||
? (givenByMeConfirmed[contact.did] || 0)
|
||||
: (givenByMeUnconfirmed[contact.did] || 0)
|
||||
/* eslint-enable prettier/prettier */
|
||||
}}
|
||||
</button>
|
||||
<button
|
||||
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white -ml-1.5 px-2 py-1.5 rounded-r-md border-l"
|
||||
:title="givenByMeDescriptions[contact.did] || ''"
|
||||
@click="confirmShowGiftedDialog(activeDid, contact.did)"
|
||||
>
|
||||
To:
|
||||
<br />
|
||||
{{
|
||||
/* eslint-disable prettier/prettier */
|
||||
showGiveTotals
|
||||
? ((givenByMeConfirmed[contact.did] || 0)
|
||||
+ (givenByMeUnconfirmed[contact.did] || 0))
|
||||
: showGiveConfirmed
|
||||
? (givenByMeConfirmed[contact.did] || 0)
|
||||
: (givenByMeUnconfirmed[contact.did] || 0)
|
||||
/* eslint-enable prettier/prettier */
|
||||
}}
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-md border border-blue-400"
|
||||
data-testId="offerButton"
|
||||
@click="openOfferDialog(contact.did, contact.name)"
|
||||
>
|
||||
Offer
|
||||
</button>
|
||||
<button
|
||||
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-md border border-blue-400"
|
||||
data-testId="offerButton"
|
||||
@click="openOfferDialog(contact.did, contact.name)"
|
||||
>
|
||||
Offer
|
||||
</button>
|
||||
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'contact-amounts',
|
||||
query: { contactDid: contact.did },
|
||||
}"
|
||||
class="text-sm bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-md border border-slate-400"
|
||||
title="See more given activity"
|
||||
>
|
||||
<font-awesome icon="file-lines" class="fa-fw" />
|
||||
</router-link>
|
||||
</div>
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'contact-amounts',
|
||||
query: { contactDid: contact.did },
|
||||
}"
|
||||
class="text-sm bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-md border border-slate-400"
|
||||
title="See more given activity"
|
||||
>
|
||||
<fa icon="file-lines" class="fa-fw" />
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@@ -317,7 +313,7 @@
|
||||
<button
|
||||
v-if="!showGiveNumbers"
|
||||
href=""
|
||||
class="text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-2 px-1 py-1 rounded-md"
|
||||
class="text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-3 px-3 py-1.5 rounded-md"
|
||||
:style="
|
||||
contactsSelected.length > 0
|
||||
? 'background-image: linear-gradient(to bottom, #3b82f6, #1e40af);'
|
||||
|
||||