Compare commits
157 Commits
homeview-r
...
build-ios
| Author | SHA1 | Date | |
|---|---|---|---|
| ca455e9593 | |||
| 5ada70b05e | |||
|
|
4f9b146a66 | ||
|
|
2b638ce2a7 | ||
|
|
0b528af2a6 | ||
|
|
008211bc21 | ||
|
|
6955a36458 | ||
|
|
ba079ea983 | ||
|
|
d7b3c5ec9d | ||
|
|
d83a25f47e | ||
|
|
fb40dc0ff7 | ||
|
|
d03fa55001 | ||
|
|
c8eff4d39e | ||
|
|
b8a7771edf | ||
|
|
5d845fb112 | ||
|
|
660f2170de | ||
|
|
94bd649003 | ||
|
|
b2d628cfeb | ||
|
|
00e52f8dca | ||
|
|
073ce24f43 | ||
|
|
2c84bb50b3 | ||
|
|
abf18835f6 | ||
|
|
f72562804d | ||
|
|
bdc5ffafc1 | ||
| 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 | ||
|
|
c760385dcf | ||
|
|
8be8de5f1f | ||
|
|
2660b91995 | ||
|
|
474999dc9c | ||
| e825950e6e | |||
| a73d0a85e2 | |||
| fc01e81af7 | |||
|
|
683e85f5be | ||
| ac58804cb5 | |||
| 49b82e6c44 | |||
| 6f4fbc697f | |||
| 42413045c5 | |||
| 245959d783 | |||
| ae376f4c81 | |||
|
|
a67094218d | ||
| e3ac5fe9fe | |||
|
|
a215b1de72 | ||
|
|
d1acfb3c49 | ||
|
|
6773f512b9 | ||
|
|
5dbd66e51b | ||
|
|
df81bb6a95 | ||
|
|
312b4aaaa3 | ||
|
|
3a6a24d923 | ||
|
|
611d318a7a | ||
|
|
d7afb80a07 | ||
|
|
751df09fe5 | ||
|
|
2fbd42def5 | ||
|
|
9c8bf7997f | ||
|
|
6d4428668a | ||
|
|
eda4a6b25e | ||
|
|
87ef6f4186 | ||
|
|
e0aded04b4 | ||
|
|
8cae601148 | ||
| 562e82f176 | |||
| d53de5e79b | |||
|
|
b6213f5040 | ||
|
|
b590e41ec8 | ||
|
|
8858495f73 | ||
|
|
ecb088bee2 | ||
|
|
93219219ba | ||
|
|
8336b87bd0 | ||
|
|
a40420af16 | ||
|
|
21244efa73 | ||
|
|
02d6d220c7 | ||
|
|
5ed626b92f | ||
|
|
4562be3bac | ||
|
|
22de70a77d | ||
|
|
d6bf89ba57 | ||
|
|
b55b786738 | ||
|
|
879c00bd97 | ||
|
|
6cb1482b5f | ||
|
|
ad9b4836cd | ||
|
|
32f1f182d7 | ||
|
|
510f6a5faa | ||
|
|
1bb4e77714 | ||
|
|
cc10dab3a4 | ||
|
|
0a066dc99c | ||
|
|
eeddab506d | ||
|
|
bfd1aee27c | ||
|
|
2424d788d1 | ||
|
|
d14431161a | ||
| 8f993923a1 | |||
|
|
9edb3a255c | ||
|
|
b7b208407b | ||
|
|
a974ab4f51 | ||
|
|
bc971056e1 | ||
|
|
69b4b899c9 | ||
|
|
02747fb771 | ||
|
|
3dae8f7f7f | ||
|
|
9e6f0ab468 | ||
|
|
6685421ee8 | ||
|
|
4fcbb78450 | ||
| 9ffdb54c20 | |||
|
|
f4c5567471 | ||
|
|
86c1abb9be | ||
| e96617ca0f | |||
|
|
02bf0b3f1a | ||
|
|
d700be9e5b | ||
|
|
317fb2c644 | ||
| b91f2a5df7 | |||
| f6871e139d | |||
|
|
89d970da1d | ||
|
|
cb03df9240 | ||
|
|
20620c3aae | ||
|
|
9d04db4a71 | ||
|
|
1a9c97fe88 | ||
|
|
3b4f4dc125 | ||
|
|
f6802cd160 | ||
|
|
a2e19d7e9a | ||
|
|
42055a2d66 | ||
|
|
dc16cb393e | ||
|
|
c708716675 | ||
|
|
fbb9fba347 | ||
|
|
3b7a872ae1 | ||
|
|
a8e15804a6 | ||
|
|
cee7a6ded3 | ||
|
|
d2157a7d8c | ||
|
|
fbdf72557c | ||
|
|
74a412745a | ||
|
|
eaf0b76e9e |
6
.env.example
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Admin DID credentials
|
||||||
|
ADMIN_DID=did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F
|
||||||
|
ADMIN_PRIVATE_KEY=2b6472c026ec2aa2c4235c994a63868fc9212d18b58f6cbfe861b52e71330f5b
|
||||||
|
|
||||||
|
# API Configuration
|
||||||
|
ENDORSER_API_URL=https://test-api.endorser.ch/api/v2/claim
|
||||||
32
.eslintrc.js
@@ -5,30 +5,28 @@ module.exports = {
|
|||||||
es2022: true,
|
es2022: true,
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
"plugin:vue/vue3-essential",
|
"plugin:vue/vue3-recommended",
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"@vue/typescript/recommended",
|
"@vue/typescript/recommended",
|
||||||
"plugin:prettier/recommended",
|
"plugin:prettier/recommended"
|
||||||
],
|
],
|
||||||
// parserOptions: {
|
// parserOptions: {
|
||||||
// ecmaVersion: 2020,
|
// ecmaVersion: 2020,
|
||||||
// },
|
// },
|
||||||
rules: {
|
rules: {
|
||||||
"max-len": [
|
"max-len": ["warn", {
|
||||||
"warn",
|
code: 100,
|
||||||
{
|
ignoreComments: true,
|
||||||
code: 120,
|
ignorePattern: '^\\s*class="[^"]*"$',
|
||||||
ignoreComments: true, // why does this not make it allow comment of any length?
|
ignoreStrings: true,
|
||||||
ignorePattern: '^\\s*class="[^"]*"$',
|
ignoreTemplateLiterals: true,
|
||||||
ignoreStrings: true,
|
ignoreUrls: true,
|
||||||
ignoreTemplateLiterals: true,
|
}],
|
||||||
ignoreTrailingComments: true,
|
"no-console": process.env.NODE_ENV === "production" ? "error" : "warn",
|
||||||
ignoreUrls: true,
|
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "warn",
|
||||||
},
|
"@typescript-eslint/no-explicit-any": "warn",
|
||||||
],
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
|
||||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
|
||||||
// "prettier/prettier": ["warn", { printWidth: 120 }], // removes errors but adds thousands of warnings
|
|
||||||
"@typescript-eslint/no-unnecessary-type-constraint": "off",
|
"@typescript-eslint/no-unnecessary-type-constraint": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
20
.gitignore
vendored
@@ -36,4 +36,22 @@ pnpm-debug.log*
|
|||||||
/playwright/.cache/
|
/playwright/.cache/
|
||||||
/dist-electron-build/
|
/dist-electron-build/
|
||||||
/dist-capacitor/
|
/dist-capacitor/
|
||||||
/test-playwright-results/
|
/test-playwright-results/
|
||||||
|
playwright-tests
|
||||||
|
dist-electron-packages
|
||||||
|
.ruby-version
|
||||||
|
+.env
|
||||||
|
|
||||||
|
# Generated test files
|
||||||
|
.generated/
|
||||||
|
|
||||||
|
.env.default
|
||||||
|
vendor/
|
||||||
|
|
||||||
|
# Build logs
|
||||||
|
build_logs/
|
||||||
|
|
||||||
|
android/app/src/main/assets/public
|
||||||
|
android/app/src/main/res
|
||||||
|
android/.gradle/buildOutputCleanup/buildOutputCleanup.lock
|
||||||
|
android/.gradle/file-system.probe
|
||||||
300
BUILDING.md
@@ -4,52 +4,128 @@ This guide explains how to build TimeSafari for different platforms.
|
|||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
|
For a quick dev environment setup, use [pkgx](https://pkgx.dev).
|
||||||
|
|
||||||
- Node.js (LTS version recommended)
|
- Node.js (LTS version recommended)
|
||||||
- npm (comes with Node.js)
|
- npm (comes with Node.js)
|
||||||
- Git
|
- Git
|
||||||
- For iOS builds: macOS with Xcode installed
|
|
||||||
- For Android builds: Android Studio with SDK 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
|
- 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
|
## Initial Setup
|
||||||
|
|
||||||
1. Clone the repository:
|
Install dependencies:
|
||||||
```bash
|
|
||||||
git clone [repository-url]
|
|
||||||
cd TimeSafari
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Install dependencies:
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
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:
|
1. Run the production build:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
2. The built files will be in the `dist` directory.
|
The built files will be in the `dist` directory.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
3. To test the production build locally:
|
|
||||||
```bash
|
```bash
|
||||||
npm run serve
|
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)
|
## Desktop Build (Electron)
|
||||||
|
|
||||||
### Building for Linux
|
### Linux Build
|
||||||
|
|
||||||
1. Build the electron app in production mode:
|
1. Build the electron app in production mode:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build:electron-prod
|
npm run build:electron-prod
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Package the Electron app for Linux:
|
2. Package the Electron app for Linux:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# For AppImage (recommended)
|
# For AppImage (recommended)
|
||||||
npm run electron:build-linux
|
npm run electron:build-linux
|
||||||
@@ -65,12 +141,14 @@ To build for web deployment:
|
|||||||
### Running the Packaged App
|
### Running the Packaged App
|
||||||
|
|
||||||
- AppImage: Make executable and run
|
- AppImage: Make executable and run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
chmod +x dist-electron-packages/TimeSafari-*.AppImage
|
chmod +x dist-electron-packages/TimeSafari-*.AppImage
|
||||||
./dist-electron-packages/TimeSafari-*.AppImage
|
./dist-electron-packages/TimeSafari-*.AppImage
|
||||||
```
|
```
|
||||||
|
|
||||||
- DEB: Install and run
|
- DEB: Install and run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo dpkg -i dist-electron-packages/timesafari_*_amd64.deb
|
sudo dpkg -i dist-electron-packages/timesafari_*_amd64.deb
|
||||||
timesafari
|
timesafari
|
||||||
@@ -95,77 +173,181 @@ npm run build:electron-prod && npm run electron:start
|
|||||||
Prerequisites: macOS with Xcode installed
|
Prerequisites: macOS with Xcode installed
|
||||||
|
|
||||||
1. Build the web assets:
|
1. Build the web assets:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build -- --mode capacitor
|
npm run build:capacitor
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Add iOS platform if not already added:
|
2. Update iOS project with latest build:
|
||||||
```bash
|
|
||||||
npx cap add ios
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Update iOS project with latest build:
|
|
||||||
```bash
|
```bash
|
||||||
npx cap sync ios
|
npx cap sync ios
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Open the project in Xcode:
|
3. Copy the assets:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ios/App/App/Assets.xcassets/AppIcon.appiconset
|
||||||
|
npx capacitor-assets generate --ios
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Open the project in Xcode:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx cap open ios
|
npx cap open ios
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Use Xcode to build and run on simulator or device.
|
4. Use Xcode to build and run on simulator or device.
|
||||||
|
|
||||||
|
#### First-time iOS Configuration
|
||||||
|
|
||||||
|
- Generate certificates inside XCode.
|
||||||
|
|
||||||
|
- Right-click on App and under Signing & Capabilities set the Team.
|
||||||
|
|
||||||
### Android Build
|
### Android Build
|
||||||
|
|
||||||
Prerequisites: Android Studio with SDK installed
|
Prerequisites: Android Studio with SDK installed
|
||||||
|
|
||||||
1. Build the web assets:
|
1. Build the web assets:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build -- --mode capacitor
|
rm -rf dist
|
||||||
|
npm run build:web
|
||||||
|
npm run build:capacitor
|
||||||
|
cd android
|
||||||
|
./gradlew clean
|
||||||
|
./gradlew assembleDebug
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Add Android platform if not already added:
|
2. Update Android project with latest build:
|
||||||
```bash
|
|
||||||
npx cap add android
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Update Android project with latest build:
|
|
||||||
```bash
|
```bash
|
||||||
npx cap sync android
|
npx cap sync android
|
||||||
```
|
```
|
||||||
|
|
||||||
|
3. Copy the assets
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx capacitor-assets generate --android
|
||||||
|
```
|
||||||
|
|
||||||
4. Open the project in Android Studio:
|
4. Open the project in Android Studio:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx cap open android
|
npx cap open android
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Use Android Studio to build and run on emulator or device.
|
5. Use Android Studio to build and run on emulator or device.
|
||||||
|
|
||||||
## Development
|
## Android Build from the console
|
||||||
|
|
||||||
To run the application in development mode:
|
|
||||||
|
|
||||||
1. Start the development server:
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
cd android
|
||||||
|
./gradlew clean
|
||||||
|
./gradlew build -Dlint.baselines.continue=true
|
||||||
|
cd ..
|
||||||
|
npx cap run android
|
||||||
```
|
```
|
||||||
|
|
||||||
|
... 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:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<intent-filter android:autoVerify="true">
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="timesafari" />
|
||||||
|
</intent-filter>
|
||||||
|
```
|
||||||
|
|
||||||
|
You must also add the following to the `android/app/build.gradle` file:
|
||||||
|
|
||||||
|
```gradle
|
||||||
|
android {
|
||||||
|
// ... existing config ...
|
||||||
|
|
||||||
|
lintOptions {
|
||||||
|
disable 'UnsanitizedFilenameFromContentProvider'
|
||||||
|
abortOnError false
|
||||||
|
baseline file("lint-baseline.xml")
|
||||||
|
|
||||||
|
// Ignore Capacitor module issues
|
||||||
|
ignore 'DefaultLocale'
|
||||||
|
ignore 'UnsanitizedFilenameFromContentProvider'
|
||||||
|
ignore 'LintBaseline'
|
||||||
|
ignore 'LintBaselineFixed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Modify `/android/build.gradle` to use a stable version of AGP and make sure Kotlin version is compatible.
|
||||||
|
|
||||||
|
```gradle
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
// Use a stable version of AGP
|
||||||
|
classpath 'com.android.tools.build:gradle:8.1.0'
|
||||||
|
|
||||||
|
// Make sure Kotlin version is compatible
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this to handle version conflicts
|
||||||
|
configurations.all {
|
||||||
|
resolutionStrategy {
|
||||||
|
force 'org.jetbrains.kotlin:kotlin-stdlib:1.8.0'
|
||||||
|
force 'org.jetbrains.kotlin:kotlin-stdlib-common:1.8.0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## PyWebView Desktop Build
|
## PyWebView Desktop Build
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites for PyWebView
|
||||||
|
|
||||||
- Python 3.8 or higher
|
- Python 3.8 or higher
|
||||||
- pip (Python package manager)
|
- pip (Python package manager)
|
||||||
- virtualenv (recommended)
|
- virtualenv (recommended)
|
||||||
- System dependencies:
|
- System dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# For Ubuntu/Debian
|
# For Ubuntu/Debian
|
||||||
sudo apt-get install python3-webview
|
sudo apt-get install python3-webview
|
||||||
# or
|
# or
|
||||||
sudo apt-get install python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-webkit2-4.0
|
sudo apt-get install python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-webkit2-4.0
|
||||||
|
|
||||||
# For Arch Linux
|
# For Arch Linux
|
||||||
sudo pacman -S webkit2gtk python-gobject python-cairo
|
sudo pacman -S webkit2gtk python-gobject python-cairo
|
||||||
|
|
||||||
# For Fedora
|
# For Fedora
|
||||||
sudo dnf install python3-webview
|
sudo dnf install python3-webview
|
||||||
# or
|
# or
|
||||||
@@ -173,7 +355,9 @@ To run the application in development mode:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
|
|
||||||
1. Create and activate a virtual environment (recommended):
|
1. Create and activate a virtual environment (recommended):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m venv .venv
|
python -m venv .venv
|
||||||
source .venv/bin/activate # On Linux/macOS
|
source .venv/bin/activate # On Linux/macOS
|
||||||
@@ -182,6 +366,7 @@ To run the application in development mode:
|
|||||||
```
|
```
|
||||||
|
|
||||||
2. Install Python dependencies:
|
2. Install Python dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
@@ -189,13 +374,16 @@ To run the application in development mode:
|
|||||||
### Troubleshooting
|
### Troubleshooting
|
||||||
|
|
||||||
If encountering PyInstaller version errors:
|
If encountering PyInstaller version errors:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Try installing the latest stable version
|
# Try installing the latest stable version
|
||||||
pip install --upgrade pyinstaller
|
pip install --upgrade pyinstaller
|
||||||
```
|
```
|
||||||
|
|
||||||
### Development
|
### Development of PyWebView
|
||||||
|
|
||||||
1. Start the PyWebView development build:
|
1. Start the PyWebView development build:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run pywebview:dev
|
npm run pywebview:dev
|
||||||
```
|
```
|
||||||
@@ -203,43 +391,49 @@ pip install --upgrade pyinstaller
|
|||||||
### Building for Distribution
|
### Building for Distribution
|
||||||
|
|
||||||
#### Linux
|
#### Linux
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run pywebview:package-linux
|
npm run pywebview:package-linux
|
||||||
```
|
```
|
||||||
|
|
||||||
The packaged application will be in `dist/TimeSafari`
|
The packaged application will be in `dist/TimeSafari`
|
||||||
|
|
||||||
#### Windows
|
#### Windows
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run pywebview:package-win
|
npm run pywebview:package-win
|
||||||
```
|
```
|
||||||
|
|
||||||
The packaged application will be in `dist/TimeSafari`
|
The packaged application will be in `dist/TimeSafari`
|
||||||
|
|
||||||
#### macOS
|
#### macOS
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run pywebview:package-mac
|
npm run pywebview:package-mac
|
||||||
```
|
```
|
||||||
|
|
||||||
The packaged application will be in `dist/TimeSafari`
|
The packaged application will be in `dist/TimeSafari`
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
Run local tests:
|
Run all tests (requires XCode and Android Studio/device):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run test-local
|
npm run test:all
|
||||||
```
|
```
|
||||||
|
|
||||||
Run all tests (includes building):
|
See [TESTING.md](test-playwright/TESTING.md) for more details.
|
||||||
```bash
|
|
||||||
npm run test-all
|
|
||||||
```
|
|
||||||
|
|
||||||
## Linting
|
## Linting
|
||||||
|
|
||||||
Check code style:
|
Check code style:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run lint
|
npm run lint
|
||||||
```
|
```
|
||||||
|
|
||||||
Fix code style issues:
|
Fix code style issues:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run lint-fix
|
npm run lint-fix
|
||||||
```
|
```
|
||||||
@@ -260,16 +454,20 @@ See `.env.*` files for configuration.
|
|||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
### Version Management
|
### Version Management
|
||||||
|
|
||||||
1. Update CHANGELOG.md with new changes
|
1. Update CHANGELOG.md with new changes
|
||||||
2. Update version in package.json
|
2. Update version in package.json
|
||||||
3. Commit changes and tag release:
|
3. Commit changes and tag release:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git tag <VERSION_TAG>
|
git tag <VERSION_TAG>
|
||||||
git push origin <VERSION_TAG>
|
git push origin <VERSION_TAG>
|
||||||
```
|
```
|
||||||
|
|
||||||
4. After deployment, update package.json with next version + "-beta"
|
4. After deployment, update package.json with next version + "-beta"
|
||||||
|
|
||||||
### Test Server
|
### Test Server
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build using staging environment
|
# Build using staging environment
|
||||||
npm run build -- --mode staging
|
npm run build -- --mode staging
|
||||||
@@ -279,6 +477,7 @@ rsync -azvu -e "ssh -i ~/.ssh/<YOUR_KEY>" dist ubuntutest@test.timesafari.app:ti
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Production Server
|
### Production Server
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# On the production server:
|
# On the production server:
|
||||||
pkgx +npm sh
|
pkgx +npm sh
|
||||||
@@ -293,7 +492,7 @@ cd -
|
|||||||
mv time-safari/dist time-safari-dist-prev.0 && mv crowd-funder-for-time-pwa/dist time-safari/
|
mv time-safari/dist time-safari-dist-prev.0 && mv crowd-funder-for-time-pwa/dist time-safari/
|
||||||
```
|
```
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting Builds
|
||||||
|
|
||||||
### Common Build Issues
|
### Common Build Issues
|
||||||
|
|
||||||
@@ -311,3 +510,18 @@ mv time-safari/dist time-safari-dist-prev.0 && mv crowd-funder-for-time-pwa/dist
|
|||||||
- For Android: Correct SDK version must be installed
|
- For Android: Correct SDK version must be installed
|
||||||
- Check Capacitor configuration in capacitor.config.ts
|
- 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
|
||||||
587
CHANGELOG.md
@@ -15,269 +15,347 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
|
|
||||||
## [0.4.4] - 2025.02.17
|
## [0.4.4] - 2025.02.17
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.4.4
|
||||||
|
|
||||||
- On production (due to data?) the search results would disappear after scrolling down. Now we don't show any results when going to the people map with a shortcut.
|
- On production (due to data?) the search results would disappear after scrolling down. Now we don't show any results when going to the people map with a shortcut.
|
||||||
|
|
||||||
|
|
||||||
## [0.4.3] - 2025.02.17
|
## [0.4.3] - 2025.02.17
|
||||||
### Added
|
|
||||||
|
### Added in 0.4.3
|
||||||
|
|
||||||
- Discover query parameter searchPeople to go directly to the people map
|
- Discover query parameter searchPeople to go directly to the people map
|
||||||
|
|
||||||
|
|
||||||
## [0.4.2] - 2025.02.17
|
## [0.4.2] - 2025.02.17
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Capacitor build to Android
|
|
||||||
|
- Capacitor on iOS and Android
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Path issues
|
- Path issues
|
||||||
|
|
||||||
|
|
||||||
## [0.4.1] - 2025.02.16
|
## [0.4.1] - 2025.02.16
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.4.1
|
||||||
|
|
||||||
- nostr build issue
|
- nostr build issue
|
||||||
- Linting
|
- Linting
|
||||||
|
|
||||||
|
|
||||||
## [0.4.0] - 2025.02.14
|
## [0.4.0] - 2025.02.14
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Images in the home feed now take up the full width of the card.
|
- Images in the home feed now take up the full width of the card.
|
||||||
- Clicking the image previously, would open the image in a new tab. Now, clicking the image opens the image in a lightbox view.
|
- Clicking the image previously, would open the image in a new tab. Now, clicking the image opens the image in a lightbox view.
|
||||||
### Added
|
|
||||||
|
### Added in 0.4.0
|
||||||
|
|
||||||
- Clicking an image also now displays an in-app lightbox view of the image.
|
- Clicking an image also now displays an in-app lightbox view of the image.
|
||||||
- The lightbox view includes a download button for the image in mobile view.
|
- The lightbox view includes a download button for the image in mobile view.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.57] - 2025.02.11
|
## [0.3.57] - 2025.02.11
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.57
|
||||||
|
|
||||||
- Automatic user creation in onboarding meetings
|
- Automatic user creation in onboarding meetings
|
||||||
|
|
||||||
|
|
||||||
## [0.3.55] - 2025.02.07
|
## [0.3.55] - 2025.02.07
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.55
|
||||||
|
|
||||||
- End time for projects
|
- End time for projects
|
||||||
|
|
||||||
|
|
||||||
## [0.3.54] - 2025.02.06
|
## [0.3.54] - 2025.02.06
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.54
|
||||||
|
|
||||||
- Group onboarding meetings
|
- Group onboarding meetings
|
||||||
|
|
||||||
|
|
||||||
## [0.3.53] - 2025.01.30
|
## [0.3.53] - 2025.01.30
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.53
|
||||||
|
|
||||||
- Hints for contacting the creator of a project
|
- Hints for contacting the creator of a project
|
||||||
|
|
||||||
|
|
||||||
## [0.3.52] - 2025.01.22
|
## [0.3.52] - 2025.01.22
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.52
|
||||||
|
|
||||||
- User profile endpoint server for map was broken.
|
- User profile endpoint server for map was broken.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.51] - 2025.01.22
|
## [0.3.51] - 2025.01.22
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.51
|
||||||
|
|
||||||
- User profile map jumped on first zoom.
|
- User profile map jumped on first zoom.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.50] - 2025.01.20 - b9fedcd3fd3e34c3fb0fc79150d1a81a76eaeb40
|
## [0.3.50] - 2025.01.20 - b9fedcd3fd3e34c3fb0fc79150d1a81a76eaeb40
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.50
|
||||||
|
|
||||||
- User public profiles
|
- User public profiles
|
||||||
|
|
||||||
|
|
||||||
## [0.3.49] - 2025.01.09 - 36301ed238ff84df25bb11a8d44a295ee7eaf0f8
|
## [0.3.49] - 2025.01.09 - 36301ed238ff84df25bb11a8d44a295ee7eaf0f8
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.3.49
|
||||||
|
|
||||||
- Make all external contact links direct to the contact-import page.
|
- Make all external contact links direct to the contact-import page.
|
||||||
- Handle all new-single-contact JWTs in the contacts page, and multiple-contact JWTs in the contacts-import page.
|
- Handle all new-single-contact JWTs in the contacts page, and multiple-contact JWTs in the contacts-import page.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.48] - 2025.01.08 - 398f3e64a376789f7eb1c400cd886f5a2cacd588 (but app shows 07c4e58)
|
## [0.3.48] - 2025.01.08 - 398f3e64a376789f7eb1c400cd886f5a2cacd588 (but app shows 07c4e58)
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.48
|
||||||
|
|
||||||
- More sanity-checks on contact-import JWT
|
- More sanity-checks on contact-import JWT
|
||||||
|
|
||||||
|
|
||||||
## [0.3.47] - 2025.01.06 - 5bf6dd1ee32ca7cc46d39bd7afca58365b422f93
|
## [0.3.47] - 2025.01.06 - 5bf6dd1ee32ca7cc46d39bd7afca58365b422f93
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.47
|
||||||
|
|
||||||
- Notes on contacts page with new contact-edit page
|
- Notes on contacts page with new contact-edit page
|
||||||
- Contact methods (only on contact-edit page and under DID details)
|
- Contact methods (only on contact-edit page and under DID details)
|
||||||
- DID view with no DID shows user's info.
|
- DID view with no DID shows user's info.
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.3.47
|
||||||
|
|
||||||
- URL for user's contact info is now URL to this app (not endorser.ch).
|
- URL for user's contact info is now URL to this app (not endorser.ch).
|
||||||
- Extended details (eg. full claim) is beneath details link on claim page.
|
- Extended details (eg. full claim) is beneath details link on claim page.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.46] - 2025.01.03 - 9e7056616b5e5acc51e5a8cf7354d408029fefb3
|
## [0.3.46] - 2025.01.03 - 9e7056616b5e5acc51e5a8cf7354d408029fefb3
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.46
|
||||||
|
|
||||||
- More action-oriented questions for the gift prompts
|
- More action-oriented questions for the gift prompts
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.46
|
||||||
|
|
||||||
- Contact-list import set visibility for all, even if not chosen.
|
- Contact-list import set visibility for all, even if not chosen.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.45] - 2025.01.01 - 65402dc68ce69ccc6cb9aa8d2e7a9249bf4298e0
|
## [0.3.45] - 2025.01.01 - 65402dc68ce69ccc6cb9aa8d2e7a9249bf4298e0
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.45
|
||||||
|
|
||||||
- Previous project links stayed when following a link.
|
- Previous project links stayed when following a link.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.44] - 2024.12.31 - 694b22987b05482e4527c2478bbe15e6b6f3b532
|
## [0.3.44] - 2024.12.31 - 694b22987b05482e4527c2478bbe15e6b6f3b532
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.44
|
||||||
|
|
||||||
- Project counts on a map
|
- Project counts on a map
|
||||||
|
|
||||||
|
|
||||||
## [0.3.42] - 2024.12.27 - 9751934bc24a1040415a8cfeacbae59ed91f92a5
|
## [0.3.42] - 2024.12.27 - 9751934bc24a1040415a8cfeacbae59ed91f92a5
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.42
|
||||||
|
|
||||||
- Link from certificate page to the claim
|
- Link from certificate page to the claim
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.3.42
|
||||||
|
|
||||||
- Contact data sharing is now a verified JWT.
|
- Contact data sharing is now a verified JWT.
|
||||||
- Feed pictures are larger.
|
- Feed pictures are larger.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.41] - 2024.12.21 - ff6d14138f26daea6216b051562f0a04681f69fc
|
## [0.3.41] - 2024.12.21 - ff6d14138f26daea6216b051562f0a04681f69fc
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.41
|
||||||
|
|
||||||
- Link from certificate page to the claim
|
- Link from certificate page to the claim
|
||||||
|
|
||||||
|
|
||||||
## [0.3.40] - 2024.12.20 - 77290d9fed3c364243793dc3e9bfe2e994a016b8
|
## [0.3.40] - 2024.12.20 - 77290d9fed3c364243793dc3e9bfe2e994a016b8
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.40
|
||||||
|
|
||||||
- Only show issuer on certificate if it's not the agent.
|
- Only show issuer on certificate if it's not the agent.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.39] - 2024.12.20 - d8819155e2acd2b57fdab523168fa5d1d09e80cc
|
## [0.3.39] - 2024.12.20 - d8819155e2acd2b57fdab523168fa5d1d09e80cc
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.39
|
||||||
|
|
||||||
- Page for a framed claim certificate
|
- Page for a framed claim certificate
|
||||||
|
|
||||||
|
|
||||||
## [0.3.38] - 2024.12.14 - f8cae5ad4fee1f114320dcce052299eab12108b2
|
## [0.3.38] - 2024.12.14 - f8cae5ad4fee1f114320dcce052299eab12108b2
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.38
|
||||||
|
|
||||||
- Error on BVC confirmation screen (from IndexedDB refactor)
|
- Error on BVC confirmation screen (from IndexedDB refactor)
|
||||||
|
|
||||||
|
|
||||||
## [0.3.37] - 2024.12.13 - 4d805b43cd25eed73cdd6651f36ad1ec8c109555
|
## [0.3.37] - 2024.12.13 - 4d805b43cd25eed73cdd6651f36ad1ec8c109555
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.37
|
||||||
|
|
||||||
- Record a give from a project on the project page.
|
- Record a give from a project on the project page.
|
||||||
- New button on home page opens the gifted dialog.
|
- New button on home page opens the gifted dialog.
|
||||||
- On confirmation buttons on the project page gives, mark when unavailable and explain why.
|
- On confirmation buttons on the project page gives, mark when unavailable and explain why.
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.3.37
|
||||||
|
|
||||||
- Moved the secret into IndexedDB (and out of localStorage) for more reliability.
|
- Moved the secret into IndexedDB (and out of localStorage) for more reliability.
|
||||||
- New "invite" destination page helps troubleshoot when JWT link doesn't come through.
|
- New "invite" destination page helps troubleshoot when JWT link doesn't come through.
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.37
|
||||||
|
|
||||||
- Problem showing claim issuer name
|
- Problem showing claim issuer name
|
||||||
- Problem going "back" from a project page
|
- Problem going "back" from a project page
|
||||||
|
|
||||||
|
|
||||||
## [0.3.36] - 2024.11.24 - c8d23647d165016f8a8f575e13d32583242e53ac
|
## [0.3.36] - 2024.11.24 - c8d23647d165016f8a8f575e13d32583242e53ac
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.3.36
|
||||||
|
|
||||||
- More friendly default reminder message
|
- More friendly default reminder message
|
||||||
- Blue borders around people to indicate clickability
|
- Blue borders around people to indicate clickability
|
||||||
|
|
||||||
|
|
||||||
## [0.3.35] - 2024.11.24 - bff7d0a6320b70349185e26bfac72e3bb17f76df
|
## [0.3.35] - 2024.11.24 - bff7d0a6320b70349185e26bfac72e3bb17f76df
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.35
|
||||||
|
|
||||||
- Daily reliable, hard-coded notification message
|
- Daily reliable, hard-coded notification message
|
||||||
- Setting to change the partner API server
|
- Setting to change the partner API server
|
||||||
|
|
||||||
|
|
||||||
## [0.3.33] - 2024.11.07 - adb7b16ecf1343c39cba71a7d6bb0e7a973e1102
|
## [0.3.33] - 2024.11.07 - adb7b16ecf1343c39cba71a7d6bb0e7a973e1102
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.33
|
||||||
|
|
||||||
- Affirm Delivery button on offer claim page didn't work.
|
- Affirm Delivery button on offer claim page didn't work.
|
||||||
- Plans were not showing by default on project page.
|
- Plans were not showing by default on project page.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.32] - 2024.11.06 - 9a3fa38a3fd28f977e06f0265fc39e635c9c5ccd
|
## [0.3.32] - 2024.11.06 - 9a3fa38a3fd28f977e06f0265fc39e635c9c5ccd
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.32
|
||||||
|
|
||||||
- Highlight in green new offers to user & to user's projects on the front page.
|
- Highlight in green new offers to user & to user's projects on the front page.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.31] - 2024.10.25 - 07c02ab98a09d293dd90d9289a7872e7d681d296
|
## [0.3.31] - 2024.10.25 - 07c02ab98a09d293dd90d9289a7872e7d681d296
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.3.31
|
||||||
|
|
||||||
- Onboarding messages about offers
|
- Onboarding messages about offers
|
||||||
|
|
||||||
|
|
||||||
## [0.3.30]
|
## [0.3.30]
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.30
|
||||||
|
|
||||||
- Onboarding messages
|
- Onboarding messages
|
||||||
|
|
||||||
|
|
||||||
## [0.3.29] - 2024.10.09 - babd3832bdfe0c40eaa3869de1b41399a51713c1
|
## [0.3.29] - 2024.10.09 - babd3832bdfe0c40eaa3869de1b41399a51713c1
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.29
|
||||||
|
|
||||||
- Invite for a contact to join immediately
|
- Invite for a contact to join immediately
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.3.29
|
||||||
|
|
||||||
- Send signed data to nostr endpoints to verify public key ownership.
|
- Send signed data to nostr endpoints to verify public key ownership.
|
||||||
- Enhanced help & help onboarding.
|
- Enhanced help & help onboarding.
|
||||||
|
|
||||||
### Changed in DB or environment
|
### Changed in DB or environment
|
||||||
|
|
||||||
- Uses Endorser.ch version 4.1.1
|
- Uses Endorser.ch version 4.1.1
|
||||||
|
|
||||||
|
|
||||||
## [0.3.28] - 2024.09.30 - 84720b94049d29cc0ddd99c50cef2e7176130133
|
## [0.3.28] - 2024.09.30 - 84720b94049d29cc0ddd99c50cef2e7176130133
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.28
|
||||||
|
|
||||||
- Posting to nostr apps Trustroots & TripHopping
|
- Posting to nostr apps Trustroots & TripHopping
|
||||||
- Display of providers on claim view page
|
- Display of providers on claim view page
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.3.28
|
||||||
|
|
||||||
- Switched BVC-meeting-ending gift to be a gift from the group.
|
- Switched BVC-meeting-ending gift to be a gift from the group.
|
||||||
### Changed in DB or environment
|
|
||||||
|
### Changed in DB or environment in 0.3.28
|
||||||
|
|
||||||
- Requires Endorser.ch version 4.1.0
|
- Requires Endorser.ch version 4.1.0
|
||||||
|
|
||||||
|
|
||||||
## [0.3.27] - 2024.09.22 - ee23e6f005e47f5bd6f04d804599f6395371b0e4
|
## [0.3.27] - 2024.09.22 - ee23e6f005e47f5bd6f04d804599f6395371b0e4
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.27
|
||||||
|
|
||||||
- Error loading BVC claims to confirm
|
- Error loading BVC claims to confirm
|
||||||
- Really allow visibility of bulk-imported contacts
|
- Really allow visibility of bulk-imported contacts
|
||||||
|
|
||||||
|
|
||||||
## [0.3.26] - 2024.09.16 - 8263ed2b29947b3ccc6f3133bbc9454c222bce28
|
## [0.3.26] - 2024.09.16 - 8263ed2b29947b3ccc6f3133bbc9454c222bce28
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.26
|
||||||
|
|
||||||
- Separate 'isRegistered' flag for each account
|
- Separate 'isRegistered' flag for each account
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.26
|
||||||
|
|
||||||
- Failure to assign offers to their project
|
- Failure to assign offers to their project
|
||||||
- Alert when looking at one's own activity if not in contacts.
|
- Alert when looking at one's own activity if not in contacts.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.25] - 2024.08.30 - dcbe02d877aecb4cdef2643d90e6595d246a9f82
|
## [0.3.25] - 2024.08.30 - dcbe02d877aecb4cdef2643d90e6595d246a9f82
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.25
|
||||||
|
|
||||||
- "Ideas" now jumps directly to giving prompt or contact list.
|
- "Ideas" now jumps directly to giving prompt or contact list.
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.25
|
||||||
|
|
||||||
- Empty giver name on gifted-details view
|
- Empty giver name on gifted-details view
|
||||||
- Previously visited project would show up on the giving-details page.
|
- Previously visited project would show up on the giving-details page.
|
||||||
### Removed
|
|
||||||
|
### Removed in 0.3.25
|
||||||
|
|
||||||
- All unnecessary localStorage for project IDs
|
- All unnecessary localStorage for project IDs
|
||||||
|
|
||||||
|
|
||||||
## [0.3.23] - 2024.08.30
|
## [0.3.23] - 2024.08.30
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.23
|
||||||
|
|
||||||
- Sections in Help for different kinds of users
|
- Sections in Help for different kinds of users
|
||||||
- Discovery page parameters so that links with search text work
|
- Discovery page parameters so that links with search text work
|
||||||
- Message when no projects are found
|
- Message when no projects are found
|
||||||
|
|
||||||
|
|
||||||
## [0.3.21] - 2024.08.24 - a7b89f4bb6da928d56daeffaae7741fa74cc80bf
|
## [0.3.21] - 2024.08.24 - a7b89f4bb6da928d56daeffaae7741fa74cc80bf
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.21
|
||||||
|
|
||||||
- Send list of contacts to someone, and move individual contact actions to detail page.
|
- Send list of contacts to someone, and move individual contact actions to detail page.
|
||||||
- Prompt for name in pop-up, and send to different contact-sharing screens.
|
- Prompt for name in pop-up, and send to different contact-sharing screens.
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.3.21
|
||||||
|
|
||||||
- Moved contact actions from list onto detail page
|
- Moved contact actions from list onto detail page
|
||||||
|
|
||||||
|
|
||||||
## [0.3.20] - 2024.08.18 - 4064eb75a9743ca268bf00016fa0a5fc5dec4e30
|
## [0.3.20] - 2024.08.18 - 4064eb75a9743ca268bf00016fa0a5fc5dec4e30
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.20
|
||||||
|
|
||||||
- Bad "give" verbiage on offer page
|
- Bad "give" verbiage on offer page
|
||||||
- Failing offer test
|
- Failing offer test
|
||||||
|
|
||||||
|
|
||||||
## [0.3.19] - 2024.08.18 - ee9c14942ceba993bf21a11249601f205158ec71
|
## [0.3.19] - 2024.08.18 - ee9c14942ceba993bf21a11249601f205158ec71
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.19
|
||||||
|
|
||||||
- Update of an offer
|
- Update of an offer
|
||||||
- Recipient description in offer list
|
- Recipient description in offer list
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.19
|
||||||
|
|
||||||
- List of offers wasn't showing.
|
- List of offers wasn't showing.
|
||||||
- Destination page after sharing photo was wrong.
|
- Destination page after sharing photo was wrong.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.17] - 2024.07.11 - cefa384ff1a2d922848c370640c096c529920fab
|
## [0.3.17] - 2024.07.11 - cefa384ff1a2d922848c370640c096c529920fab
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.17
|
||||||
|
|
||||||
- Photos on more screens
|
- Photos on more screens
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.17
|
||||||
|
|
||||||
- Share of a photo, including sharing a photo from webkit/Safari which never worked
|
- Share of a photo, including sharing a photo from webkit/Safari which never worked
|
||||||
### Changed in DB or environment
|
|
||||||
|
### Changed in DB or environment in 0.3.17
|
||||||
|
|
||||||
- Nothing (though there's a new temp field in IndexedDB)
|
- Nothing (though there's a new temp field in IndexedDB)
|
||||||
|
|
||||||
|
|
||||||
## [0.3.15] - 2024.08.04 - c8f0f2c2b16b9f0b4b47d40f7bf29058c7baa68e
|
## [0.3.15] - 2024.08.04 - c8f0f2c2b16b9f0b4b47d40f7bf29058c7baa68e
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.15
|
||||||
|
|
||||||
- Edit gives
|
- Edit gives
|
||||||
- Page to edit claim JSON before submitting
|
- Page to edit claim JSON before submitting
|
||||||
- Update of imported contacts
|
- Update of imported contacts
|
||||||
@@ -288,263 +366,364 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Cache signatures for reports for passkey-signed requests
|
- Cache signatures for reports for passkey-signed requests
|
||||||
- Refactor: consolidate alternative signing, eg. for passkeys & did:peer
|
- Refactor: consolidate alternative signing, eg. for passkeys & did:peer
|
||||||
- Playwright tests
|
- Playwright tests
|
||||||
### Changed
|
|
||||||
- Linked projects display below description (instead of at bottom)
|
|
||||||
### Fixed
|
|
||||||
- Visibility toggle appearance
|
|
||||||
### Changed in DB or environment
|
|
||||||
- Nothing
|
|
||||||
|
|
||||||
|
### Changed in 0.3.15
|
||||||
|
|
||||||
|
- Linked projects display below description (instead of at bottom)
|
||||||
|
|
||||||
|
### Fixed in 0.3.15
|
||||||
|
|
||||||
|
- Visibility toggle appearance
|
||||||
|
|
||||||
|
### Changed in DB or environment in 0.3.15
|
||||||
|
|
||||||
|
- Nothing
|
||||||
|
|
||||||
## [0.3.14] - 2024.06.22 - 1611d22892f683f43856d2503eee7f391b6bbce8
|
## [0.3.14] - 2024.06.22 - 1611d22892f683f43856d2503eee7f391b6bbce8
|
||||||
### Added
|
|
||||||
- Clearer give-confirmation screen
|
|
||||||
- BX currency https://thebx.medium.com/
|
|
||||||
- Deselection of project on gifted details page
|
|
||||||
### Fixed
|
|
||||||
- Don't show registration pop-up for a new contact that is registered
|
|
||||||
### Changed in DB or environment
|
|
||||||
- Nothing
|
|
||||||
|
|
||||||
|
### Added in 0.3.14
|
||||||
|
|
||||||
|
- Clearer give-confirmation screen
|
||||||
|
- BX currency <https://thebx.medium.com/>
|
||||||
|
- Deselection of project on gifted details page
|
||||||
|
|
||||||
|
### Fixed in 0.3.14
|
||||||
|
|
||||||
|
- Don't show registration pop-up for a new contact that is registered
|
||||||
|
|
||||||
|
### Changed in DB or environment in 0.3.14
|
||||||
|
|
||||||
|
- Nothing
|
||||||
|
|
||||||
## [0.3.13] - 2024.05.24 - 08b67984e443c58d9178ad3776013b0bce7afddc
|
## [0.3.13] - 2024.05.24 - 08b67984e443c58d9178ad3776013b0bce7afddc
|
||||||
### Added
|
|
||||||
- Photos on projects
|
|
||||||
### Changed in DB or environment
|
|
||||||
- Nothing
|
|
||||||
|
|
||||||
|
### Added in 0.3.13
|
||||||
|
|
||||||
|
- Photos on projects
|
||||||
|
|
||||||
|
### Changed in DB or environment in 0.3.13
|
||||||
|
|
||||||
|
- Nothing
|
||||||
|
|
||||||
## [0.3.12] - 2024.05.19 - 141fb39ad19c44d82fe1a33bf85115beacf50870
|
## [0.3.12] - 2024.05.19 - 141fb39ad19c44d82fe1a33bf85115beacf50870
|
||||||
### Fixed
|
|
||||||
- Photo share (share_target) failed because requests were sent to server
|
|
||||||
### Changed in DB or environment
|
|
||||||
- Nothing
|
|
||||||
|
|
||||||
|
### Fixed in 0.3.12
|
||||||
|
|
||||||
|
- Photo share (share_target) failed because requests were sent to server
|
||||||
|
|
||||||
|
### Changed in DB or environment in 0.3.12
|
||||||
|
|
||||||
|
- Nothing
|
||||||
|
|
||||||
## [0.3.11] - 2024.05.19 - 567bcad88dfb7e9ac8fea72530d1163985e4a7cc
|
## [0.3.11] - 2024.05.19 - 567bcad88dfb7e9ac8fea72530d1163985e4a7cc
|
||||||
### Added
|
|
||||||
- Choose a file for gifts, and a URL for gifts & profiles
|
|
||||||
### Fixed
|
|
||||||
- Multiple button pushes were required to switch camera
|
|
||||||
### Changed in DB or environment
|
|
||||||
- Nothing
|
|
||||||
|
|
||||||
|
### Added in 0.3.11
|
||||||
|
|
||||||
|
- Choose a file for gifts, and a URL for gifts & profiles
|
||||||
|
|
||||||
|
### Fixed in 0.3.11
|
||||||
|
|
||||||
|
- Multiple button pushes were required to switch camera
|
||||||
|
|
||||||
|
### Changed in DB or environment in 0.3.11
|
||||||
|
|
||||||
|
- Nothing
|
||||||
|
|
||||||
## [0.3.10] - 2024.05.11 - 03ac31d98110f7828cf9acb366db8d01b185f64c
|
## [0.3.10] - 2024.05.11 - 03ac31d98110f7828cf9acb366db8d01b185f64c
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.10
|
||||||
|
|
||||||
- Share an image
|
- Share an image
|
||||||
- Choose a file on the device for a profile image
|
- Choose a file on the device for a profile image
|
||||||
### Changed in DB or environment
|
|
||||||
|
### Changed in DB or environment in 0.3.10
|
||||||
|
|
||||||
- Nothing
|
- Nothing
|
||||||
|
|
||||||
|
|
||||||
## [0.3.9] - 2024.04.28 - 874e717e698b93a1ace9f588e675b8a3dccd7617
|
## [0.3.9] - 2024.04.28 - 874e717e698b93a1ace9f588e675b8a3dccd7617
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.9
|
||||||
|
|
||||||
- Offers on contacts page
|
- Offers on contacts page
|
||||||
- Checks on front page until they show as registered
|
- Checks on front page until they show as registered
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.3.9
|
||||||
|
|
||||||
- Scanned contacts now add immediately and prompt for registration.
|
- Scanned contacts now add immediately and prompt for registration.
|
||||||
- Better UI for gives on contact page
|
- Better UI for gives on contact page
|
||||||
- Better UI for all confirmation messages
|
- Better UI for all confirmation messages
|
||||||
### Fixed
|
|
||||||
- Repeated elements at top of main feed
|
|
||||||
### Changed in DB or environment
|
|
||||||
- Nothing
|
|
||||||
|
|
||||||
|
### Fixed in 0.3.9
|
||||||
|
|
||||||
|
- Repeated elements at top of main feed
|
||||||
|
|
||||||
|
### Changed in DB or environment in 0.3.9
|
||||||
|
|
||||||
|
- Nothing
|
||||||
|
|
||||||
## [0.3.8] - 2024.04.20 - 15c026c80ce03a26cae3ff80b0888934c101c7e2
|
## [0.3.8] - 2024.04.20 - 15c026c80ce03a26cae3ff80b0888934c101c7e2
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.8
|
||||||
|
|
||||||
- Profile image for user
|
- Profile image for user
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.8
|
||||||
|
|
||||||
- Slow loading of home page feed
|
- Slow loading of home page feed
|
||||||
### Changed in DB or environment
|
|
||||||
|
### Changed in DB or environment in 0.3.8
|
||||||
|
|
||||||
- Nothing
|
- Nothing
|
||||||
|
|
||||||
|
|
||||||
## [0.3.7] - 2024.04.10 - cf18f1543a700d62a5f9e764905a4aafe1fb229b
|
## [0.3.7] - 2024.04.10 - cf18f1543a700d62a5f9e764905a4aafe1fb229b
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.7
|
||||||
|
|
||||||
- Filter on home page feed
|
- Filter on home page feed
|
||||||
- Ability to set time of daily notification
|
- Ability to set time of daily notification
|
||||||
- Jump to app on click of notification
|
- Jump to app on click of notification
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.3.7
|
||||||
|
|
||||||
- Built with vite
|
- Built with vite
|
||||||
- Descriptions on home page to include projects
|
- Descriptions on home page to include projects
|
||||||
### Changed in DB or environment
|
|
||||||
|
### Changed in DB or environment in 0.3.7
|
||||||
|
|
||||||
- Nothing
|
- Nothing
|
||||||
|
|
||||||
|
|
||||||
## [0.3.6] - 2024.03.24 - 3a07e31d6313ab95711265562d9023c42916e141
|
## [0.3.6] - 2024.03.24 - 3a07e31d6313ab95711265562d9023c42916e141
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.6
|
||||||
|
|
||||||
- Button to mirror photo during video
|
- Button to mirror photo during video
|
||||||
- More detailed onboarding help screen
|
- More detailed onboarding help screen
|
||||||
- Public-data blurb
|
- Public-data blurb
|
||||||
### Changed in DB or environment
|
|
||||||
|
### Changed in DB or environment in 0.3.6
|
||||||
|
|
||||||
- Nothing
|
- Nothing
|
||||||
|
|
||||||
|
|
||||||
## [0.3.5] - 2024.03.23 - 28754bdfb1e11aa221dd49a5dce4219b69cf6a9d
|
## [0.3.5] - 2024.03.23 - 28754bdfb1e11aa221dd49a5dce4219b69cf6a9d
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.5
|
||||||
|
|
||||||
- Photo on gift records
|
- Photo on gift records
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.5
|
||||||
|
|
||||||
- Environment variable for BVC meetings project
|
- Environment variable for BVC meetings project
|
||||||
- Environment variables and build enhancements for test vs prod
|
- Environment variables and build enhancements for test vs prod
|
||||||
### Changed in DB or environment
|
|
||||||
|
### Changed in DB or environment in 0.3.5
|
||||||
|
|
||||||
- New environment variable for image API server
|
- New environment variable for image API server
|
||||||
- Test that a new browser session will get the right default APIs.
|
- Test that a new browser session will get the right default APIs.
|
||||||
- Test that a new browser session will send the right BVC meetings project.
|
- Test that a new browser session will send the right BVC meetings project.
|
||||||
|
|
||||||
|
|
||||||
## [0.2.17] - 2024.03.01 - 3612ea42240c5e1b7d7eff29a39ff18f1b869b36
|
## [0.2.17] - 2024.03.01 - 3612ea42240c5e1b7d7eff29a39ff18f1b869b36
|
||||||
### Added
|
|
||||||
- Shortcut page for Bountiful Voluntaryist Community
|
|
||||||
### Changed
|
|
||||||
- More readable, targeted summaries in home-page feed items
|
|
||||||
### Changed in DB
|
|
||||||
- Nothing
|
|
||||||
|
|
||||||
|
### Added in 0.2.17
|
||||||
|
|
||||||
|
- Shortcut page for Bountiful Voluntaryist Community
|
||||||
|
|
||||||
|
### Changed in 0.2.17
|
||||||
|
|
||||||
|
- More readable, targeted summaries in home-page feed items
|
||||||
|
|
||||||
|
### Changed in DB
|
||||||
|
|
||||||
|
- Nothing
|
||||||
|
|
||||||
## [0.2.14] - 2024.02.14 - 5f9edea1167dbfb64e16648764eed8c09b24eaeb
|
## [0.2.14] - 2024.02.14 - 5f9edea1167dbfb64e16648764eed8c09b24eaeb
|
||||||
### Changed
|
|
||||||
- Combine all service worker scripts into a single file.
|
|
||||||
### Changed in DB
|
|
||||||
- Nothing
|
|
||||||
|
|
||||||
|
### Changed in 0.2.14
|
||||||
|
|
||||||
|
- Combine all service worker scripts into a single file.
|
||||||
|
|
||||||
|
### Changed in DB in 0.2.14
|
||||||
|
|
||||||
|
- Nothing
|
||||||
|
|
||||||
## [0.2.13] - 2024.02.07
|
## [0.2.13] - 2024.02.07
|
||||||
### Added
|
|
||||||
|
### Added in 0.2.13
|
||||||
|
|
||||||
- Display of user's offers
|
- Display of user's offers
|
||||||
- Check for valid DIDs
|
- Check for valid DIDs
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.2.13
|
||||||
|
|
||||||
- Name display on give prompt
|
- Name display on give prompt
|
||||||
- Non-numbers on number input & autocapitalize on URL input
|
- Non-numbers on number input & autocapitalize on URL input
|
||||||
### Changed in DB
|
|
||||||
|
### Changed in DB in 0.2.13
|
||||||
|
|
||||||
- Nothing
|
- Nothing
|
||||||
|
|
||||||
|
|
||||||
## [0.2.12] - 2024.02.01
|
## [0.2.12] - 2024.02.01
|
||||||
### Added
|
|
||||||
|
### Added in 0.2.12
|
||||||
|
|
||||||
- Prompts for gratitude
|
- Prompts for gratitude
|
||||||
|
|
||||||
|
|
||||||
## [0.2.11] - 2024.01.28
|
## [0.2.11] - 2024.01.28
|
||||||
### Added
|
|
||||||
|
### Added in 0.2.11
|
||||||
|
|
||||||
- Actions to share claim data with contacts
|
- Actions to share claim data with contacts
|
||||||
- Bulk CSV import from Endorser Mobile export
|
- Bulk CSV import from Endorser Mobile export
|
||||||
- Dates on give summaries
|
- Dates on give summaries
|
||||||
|
|
||||||
|
|
||||||
## [0.2.10] - 2024.01.18 - 667e1e8890b42de59cd939caca1a01c7a7a702be
|
## [0.2.10] - 2024.01.18 - 667e1e8890b42de59cd939caca1a01c7a7a702be
|
||||||
### Added
|
|
||||||
|
### Added in 0.2.10
|
||||||
|
|
||||||
- Person identicons for contacts
|
- Person identicons for contacts
|
||||||
- Confirmation & delivery directly from project page
|
- Confirmation & delivery directly from project page
|
||||||
- Offer dialog now allows units
|
- Offer dialog now allows units
|
||||||
- Links from claim detail page to the fulfilled project or offer
|
- Links from claim detail page to the fulfilled project or offer
|
||||||
- Link to project from home feed
|
- Link to project from home feed
|
||||||
- Copy to clipboard in more places
|
- Copy to clipboard in more places
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.2.10
|
||||||
|
|
||||||
- "More Contacts" for give on project page now links correctly.
|
- "More Contacts" for give on project page now links correctly.
|
||||||
|
|
||||||
|
|
||||||
## [0.2.9] - 2024.01.15 - e5e702f8a5a53a6efbed48d35f0bc3cee63024a0
|
## [0.2.9] - 2024.01.15 - e5e702f8a5a53a6efbed48d35f0bc3cee63024a0
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.2.9
|
||||||
|
|
||||||
- Set visibility for new contact.
|
- Set visibility for new contact.
|
||||||
|
|
||||||
|
|
||||||
## [0.2.8] - 2024.01.14
|
## [0.2.8] - 2024.01.14
|
||||||
### Added
|
|
||||||
|
### Added in 0.2.8
|
||||||
|
|
||||||
- Automatic ID creation from home page
|
- Automatic ID creation from home page
|
||||||
- Agent who can also edit a project
|
- Agent who can also edit a project
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.2.8
|
||||||
|
|
||||||
- Cannot declare anonymous gift
|
- Cannot declare anonymous gift
|
||||||
|
|
||||||
|
|
||||||
## [0.2.7] - 2024.01.12
|
## [0.2.7] - 2024.01.12
|
||||||
### Added
|
|
||||||
|
### Added in 0.2.7
|
||||||
|
|
||||||
- Give to fulfill a particular offer
|
- Give to fulfill a particular offer
|
||||||
- Give as part of a trade as opposed to a donation
|
- Give as part of a trade as opposed to a donation
|
||||||
- Error notifications on import
|
- Error notifications on import
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.2.7
|
||||||
|
|
||||||
- Library security updates
|
- Library security updates
|
||||||
- Visibility of actions & confirmations on claim page
|
- Visibility of actions & confirmations on claim page
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.2.7
|
||||||
|
|
||||||
- Name of offerer
|
- Name of offerer
|
||||||
|
|
||||||
|
|
||||||
## [0.2.2] - 2024.01.05
|
## [0.2.2] - 2024.01.05
|
||||||
### Added
|
|
||||||
|
### Added in 0.2.2
|
||||||
|
|
||||||
- Check for notification capability on front screen
|
- Check for notification capability on front screen
|
||||||
- Contact next-public-key-hash in manual textual input
|
- Contact next-public-key-hash in manual textual input
|
||||||
- Confirmation for contact visibility change
|
- Confirmation for contact visibility change
|
||||||
- YAML rendering of full claim details
|
- YAML rendering of full claim details
|
||||||
- Hints for onboarding on the contact screen
|
- Hints for onboarding on the contact screen
|
||||||
|
|
||||||
|
|
||||||
## [0.2.0] - 2024.01.04
|
## [0.2.0] - 2024.01.04
|
||||||
### Added
|
|
||||||
|
### Added in 0.2.0
|
||||||
|
|
||||||
- Contact next-public-key-hash
|
- Contact next-public-key-hash
|
||||||
- Icon for Android
|
- Icon for Android
|
||||||
- More thorough messaging and testing for notifications
|
- More thorough messaging and testing for notifications
|
||||||
|
|
||||||
|
|
||||||
## [0.1.9] - 2024.01.01
|
## [0.1.9] - 2024.01.01
|
||||||
### Added
|
|
||||||
|
### Added in 0.1.9
|
||||||
|
|
||||||
- Import for contacts and settings
|
- Import for contacts and settings
|
||||||
- Second download button for DuckDuckGo
|
- Second download button for DuckDuckGo
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.1.9
|
||||||
|
|
||||||
- Removed some keys from Dexie's IndexedDB declarations
|
- Removed some keys from Dexie's IndexedDB declarations
|
||||||
|
|
||||||
|
|
||||||
## [0.1.8] - 2023.12.27- d26d1d360152a7d0e559b68486e85b72b88bd9ff
|
## [0.1.8] - 2023.12.27- d26d1d360152a7d0e559b68486e85b72b88bd9ff
|
||||||
### Added
|
|
||||||
|
### Added in 0.1.8
|
||||||
|
|
||||||
- DB logging for service-worker events
|
- DB logging for service-worker events
|
||||||
- Help page for notifications
|
- Help page for notifications
|
||||||
- Test notification & web-push triggers inside app
|
- Test notification & web-push triggers inside app
|
||||||
- Check that the app is installed
|
- Check that the app is installed
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.1.8
|
||||||
|
|
||||||
- Project issuer display name
|
- Project issuer display name
|
||||||
|
|
||||||
|
|
||||||
## [0.1.7] - 2023.12.19 - 91c6c7c11c71f96006cc876fc946f1f98a274ba2
|
## [0.1.7] - 2023.12.19 - 91c6c7c11c71f96006cc876fc946f1f98a274ba2
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.1.7
|
||||||
|
|
||||||
- Icons
|
- Icons
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.1.7
|
||||||
|
|
||||||
- Notification switch now shows message
|
- Notification switch now shows message
|
||||||
- Prod/test server warning message at top of page
|
- Prod/test server warning message at top of page
|
||||||
|
|
||||||
|
|
||||||
## [0.1.6] - 2023.12.17 - b445b1234fbfcf6b37d695373f259aab0eda1118
|
## [0.1.6] - 2023.12.17 - b445b1234fbfcf6b37d695373f259aab0eda1118
|
||||||
### Added
|
|
||||||
|
### Added in 0.1.6
|
||||||
|
|
||||||
- Infinite scroll on home page
|
- Infinite scroll on home page
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.1.6
|
||||||
|
|
||||||
- UI improvements
|
- UI improvements
|
||||||
- Show web-push subscription info
|
- Show web-push subscription info
|
||||||
- Icon
|
- Icon
|
||||||
|
|
||||||
|
|
||||||
## [0.1.5] - 2023.12.09 - 9c36bb509a9bae9bb3306d3bd9eeb144b67aa8ad
|
## [0.1.5] - 2023.12.09 - 9c36bb509a9bae9bb3306d3bd9eeb144b67aa8ad
|
||||||
### Added
|
|
||||||
|
### Added in 0.1.5
|
||||||
|
|
||||||
- Web push notifications (though not finalized)
|
- Web push notifications (though not finalized)
|
||||||
- Credentials details page
|
- Credentials details page
|
||||||
- See more data without an ID
|
- See more data without an ID
|
||||||
- Change units of a give
|
- Change units of a give
|
||||||
|
|
||||||
|
|
||||||
## [0.1.4] - 2023.11.20 - 7311d36726f3667ec4c68f241f91d404273ad4db
|
## [0.1.4] - 2023.11.20 - 7311d36726f3667ec4c68f241f91d404273ad4db
|
||||||
### Added
|
|
||||||
|
### Added in 0.1.4
|
||||||
|
|
||||||
- Offer on a project
|
- Offer on a project
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.1.4
|
||||||
|
|
||||||
- Automatically set as visible when importing a contact
|
- Automatically set as visible when importing a contact
|
||||||
|
|
||||||
|
|
||||||
## [0.1.3] - 2023.11.08 - 910f57ec7d2e50803ae3d04f4b927e0f5219fbde
|
## [0.1.3] - 2023.11.08 - 910f57ec7d2e50803ae3d04f4b927e0f5219fbde
|
||||||
### Added
|
|
||||||
|
### Added in 0.1.3
|
||||||
|
|
||||||
- Contact name editing
|
- Contact name editing
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.1.3
|
||||||
|
|
||||||
- Don't show actions on front page if not registered.
|
- Don't show actions on front page if not registered.
|
||||||
### Removed
|
|
||||||
|
### Removed in 0.1.3
|
||||||
|
|
||||||
- Home page Notiwind test buttons
|
- Home page Notiwind test buttons
|
||||||
|
|
||||||
|
|
||||||
## [0.1.2] - 2023.11.01 - 7f6c93802911a030a89fe3706e18b5c17151e5bb
|
## [0.1.2] - 2023.11.01 - 7f6c93802911a030a89fe3706e18b5c17151e5bb
|
||||||
### Added
|
|
||||||
|
### Added in 0.1.2
|
||||||
|
|
||||||
- Basics: create ID, record a give, declare a project, search, and get notifications.
|
- Basics: create ID, record a give, declare a project, search, and get notifications.
|
||||||
|
|||||||
5
Gemfile
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
gem "fastlane"
|
||||||
|
gem "cocoapods"
|
||||||
|
|
||||||
321
Gemfile.lock
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
GEM
|
||||||
|
remote: https://rubygems.org/
|
||||||
|
specs:
|
||||||
|
CFPropertyList (3.0.7)
|
||||||
|
base64
|
||||||
|
nkf
|
||||||
|
rexml
|
||||||
|
activesupport (7.2.2.1)
|
||||||
|
base64
|
||||||
|
benchmark (>= 0.3)
|
||||||
|
bigdecimal
|
||||||
|
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||||
|
connection_pool (>= 2.2.5)
|
||||||
|
drb
|
||||||
|
i18n (>= 1.6, < 2)
|
||||||
|
logger (>= 1.4.2)
|
||||||
|
minitest (>= 5.1)
|
||||||
|
securerandom (>= 0.3)
|
||||||
|
tzinfo (~> 2.0, >= 2.0.5)
|
||||||
|
addressable (2.8.7)
|
||||||
|
public_suffix (>= 2.0.2, < 7.0)
|
||||||
|
algoliasearch (1.27.5)
|
||||||
|
httpclient (~> 2.8, >= 2.8.3)
|
||||||
|
json (>= 1.5.1)
|
||||||
|
artifactory (3.0.17)
|
||||||
|
atomos (0.1.3)
|
||||||
|
aws-eventstream (1.3.2)
|
||||||
|
aws-partitions (1.1066.0)
|
||||||
|
aws-sdk-core (3.220.1)
|
||||||
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
|
aws-partitions (~> 1, >= 1.992.0)
|
||||||
|
aws-sigv4 (~> 1.9)
|
||||||
|
base64
|
||||||
|
jmespath (~> 1, >= 1.6.1)
|
||||||
|
aws-sdk-kms (1.99.0)
|
||||||
|
aws-sdk-core (~> 3, >= 3.216.0)
|
||||||
|
aws-sigv4 (~> 1.5)
|
||||||
|
aws-sdk-s3 (1.182.0)
|
||||||
|
aws-sdk-core (~> 3, >= 3.216.0)
|
||||||
|
aws-sdk-kms (~> 1)
|
||||||
|
aws-sigv4 (~> 1.5)
|
||||||
|
aws-sigv4 (1.11.0)
|
||||||
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
|
babosa (1.0.4)
|
||||||
|
base64 (0.2.0)
|
||||||
|
benchmark (0.4.0)
|
||||||
|
bigdecimal (3.1.9)
|
||||||
|
claide (1.1.0)
|
||||||
|
cocoapods (1.16.2)
|
||||||
|
addressable (~> 2.8)
|
||||||
|
claide (>= 1.0.2, < 2.0)
|
||||||
|
cocoapods-core (= 1.16.2)
|
||||||
|
cocoapods-deintegrate (>= 1.0.3, < 2.0)
|
||||||
|
cocoapods-downloader (>= 2.1, < 3.0)
|
||||||
|
cocoapods-plugins (>= 1.0.0, < 2.0)
|
||||||
|
cocoapods-search (>= 1.0.0, < 2.0)
|
||||||
|
cocoapods-trunk (>= 1.6.0, < 2.0)
|
||||||
|
cocoapods-try (>= 1.1.0, < 2.0)
|
||||||
|
colored2 (~> 3.1)
|
||||||
|
escape (~> 0.0.4)
|
||||||
|
fourflusher (>= 2.3.0, < 3.0)
|
||||||
|
gh_inspector (~> 1.0)
|
||||||
|
molinillo (~> 0.8.0)
|
||||||
|
nap (~> 1.0)
|
||||||
|
ruby-macho (>= 2.3.0, < 3.0)
|
||||||
|
xcodeproj (>= 1.27.0, < 2.0)
|
||||||
|
cocoapods-core (1.16.2)
|
||||||
|
activesupport (>= 5.0, < 8)
|
||||||
|
addressable (~> 2.8)
|
||||||
|
algoliasearch (~> 1.0)
|
||||||
|
concurrent-ruby (~> 1.1)
|
||||||
|
fuzzy_match (~> 2.0.4)
|
||||||
|
nap (~> 1.0)
|
||||||
|
netrc (~> 0.11)
|
||||||
|
public_suffix (~> 4.0)
|
||||||
|
typhoeus (~> 1.0)
|
||||||
|
cocoapods-deintegrate (1.0.5)
|
||||||
|
cocoapods-downloader (2.1)
|
||||||
|
cocoapods-plugins (1.0.0)
|
||||||
|
nap
|
||||||
|
cocoapods-search (1.0.1)
|
||||||
|
cocoapods-trunk (1.6.0)
|
||||||
|
nap (>= 0.8, < 2.0)
|
||||||
|
netrc (~> 0.11)
|
||||||
|
cocoapods-try (1.2.0)
|
||||||
|
colored (1.2)
|
||||||
|
colored2 (3.1.2)
|
||||||
|
commander (4.6.0)
|
||||||
|
highline (~> 2.0.0)
|
||||||
|
concurrent-ruby (1.3.5)
|
||||||
|
connection_pool (2.5.0)
|
||||||
|
declarative (0.0.20)
|
||||||
|
digest-crc (0.7.0)
|
||||||
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
|
domain_name (0.6.20240107)
|
||||||
|
dotenv (2.8.1)
|
||||||
|
drb (2.2.1)
|
||||||
|
emoji_regex (3.2.3)
|
||||||
|
escape (0.0.4)
|
||||||
|
ethon (0.16.0)
|
||||||
|
ffi (>= 1.15.0)
|
||||||
|
excon (0.112.0)
|
||||||
|
faraday (1.10.4)
|
||||||
|
faraday-em_http (~> 1.0)
|
||||||
|
faraday-em_synchrony (~> 1.0)
|
||||||
|
faraday-excon (~> 1.1)
|
||||||
|
faraday-httpclient (~> 1.0)
|
||||||
|
faraday-multipart (~> 1.0)
|
||||||
|
faraday-net_http (~> 1.0)
|
||||||
|
faraday-net_http_persistent (~> 1.0)
|
||||||
|
faraday-patron (~> 1.0)
|
||||||
|
faraday-rack (~> 1.0)
|
||||||
|
faraday-retry (~> 1.0)
|
||||||
|
ruby2_keywords (>= 0.0.4)
|
||||||
|
faraday-cookie_jar (0.0.7)
|
||||||
|
faraday (>= 0.8.0)
|
||||||
|
http-cookie (~> 1.0.0)
|
||||||
|
faraday-em_http (1.0.0)
|
||||||
|
faraday-em_synchrony (1.0.0)
|
||||||
|
faraday-excon (1.1.0)
|
||||||
|
faraday-httpclient (1.0.1)
|
||||||
|
faraday-multipart (1.1.0)
|
||||||
|
multipart-post (~> 2.0)
|
||||||
|
faraday-net_http (1.0.2)
|
||||||
|
faraday-net_http_persistent (1.2.0)
|
||||||
|
faraday-patron (1.0.0)
|
||||||
|
faraday-rack (1.0.0)
|
||||||
|
faraday-retry (1.0.3)
|
||||||
|
faraday_middleware (1.2.1)
|
||||||
|
faraday (~> 1.0)
|
||||||
|
fastimage (2.4.0)
|
||||||
|
fastlane (2.227.0)
|
||||||
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
|
addressable (>= 2.8, < 3.0.0)
|
||||||
|
artifactory (~> 3.0)
|
||||||
|
aws-sdk-s3 (~> 1.0)
|
||||||
|
babosa (>= 1.0.3, < 2.0.0)
|
||||||
|
bundler (>= 1.12.0, < 3.0.0)
|
||||||
|
colored (~> 1.2)
|
||||||
|
commander (~> 4.6)
|
||||||
|
dotenv (>= 2.1.1, < 3.0.0)
|
||||||
|
emoji_regex (>= 0.1, < 4.0)
|
||||||
|
excon (>= 0.71.0, < 1.0.0)
|
||||||
|
faraday (~> 1.0)
|
||||||
|
faraday-cookie_jar (~> 0.0.6)
|
||||||
|
faraday_middleware (~> 1.0)
|
||||||
|
fastimage (>= 2.1.0, < 3.0.0)
|
||||||
|
fastlane-sirp (>= 1.0.0)
|
||||||
|
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||||
|
google-apis-androidpublisher_v3 (~> 0.3)
|
||||||
|
google-apis-playcustomapp_v1 (~> 0.1)
|
||||||
|
google-cloud-env (>= 1.6.0, < 2.0.0)
|
||||||
|
google-cloud-storage (~> 1.31)
|
||||||
|
highline (~> 2.0)
|
||||||
|
http-cookie (~> 1.0.5)
|
||||||
|
json (< 3.0.0)
|
||||||
|
jwt (>= 2.1.0, < 3)
|
||||||
|
mini_magick (>= 4.9.4, < 5.0.0)
|
||||||
|
multipart-post (>= 2.0.0, < 3.0.0)
|
||||||
|
naturally (~> 2.2)
|
||||||
|
optparse (>= 0.1.1, < 1.0.0)
|
||||||
|
plist (>= 3.1.0, < 4.0.0)
|
||||||
|
rubyzip (>= 2.0.0, < 3.0.0)
|
||||||
|
security (= 0.1.5)
|
||||||
|
simctl (~> 1.6.3)
|
||||||
|
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||||
|
terminal-table (~> 3)
|
||||||
|
tty-screen (>= 0.6.3, < 1.0.0)
|
||||||
|
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||||
|
word_wrap (~> 1.0.0)
|
||||||
|
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||||
|
xcpretty (~> 0.4.0)
|
||||||
|
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||||
|
fastlane-sirp (1.0.0)
|
||||||
|
sysrandom (~> 1.0)
|
||||||
|
ffi (1.17.1)
|
||||||
|
ffi (1.17.1-aarch64-linux-gnu)
|
||||||
|
ffi (1.17.1-aarch64-linux-musl)
|
||||||
|
ffi (1.17.1-arm-linux-gnu)
|
||||||
|
ffi (1.17.1-arm-linux-musl)
|
||||||
|
ffi (1.17.1-arm64-darwin)
|
||||||
|
ffi (1.17.1-x86-linux-gnu)
|
||||||
|
ffi (1.17.1-x86-linux-musl)
|
||||||
|
ffi (1.17.1-x86_64-darwin)
|
||||||
|
ffi (1.17.1-x86_64-linux-gnu)
|
||||||
|
ffi (1.17.1-x86_64-linux-musl)
|
||||||
|
fourflusher (2.3.1)
|
||||||
|
fuzzy_match (2.0.4)
|
||||||
|
gh_inspector (1.1.3)
|
||||||
|
google-apis-androidpublisher_v3 (0.54.0)
|
||||||
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
|
google-apis-core (0.11.3)
|
||||||
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
|
httpclient (>= 2.8.1, < 3.a)
|
||||||
|
mini_mime (~> 1.0)
|
||||||
|
representable (~> 3.0)
|
||||||
|
retriable (>= 2.0, < 4.a)
|
||||||
|
rexml
|
||||||
|
google-apis-iamcredentials_v1 (0.17.0)
|
||||||
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
|
google-apis-playcustomapp_v1 (0.13.0)
|
||||||
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
|
google-apis-storage_v1 (0.31.0)
|
||||||
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
|
google-cloud-core (1.8.0)
|
||||||
|
google-cloud-env (>= 1.0, < 3.a)
|
||||||
|
google-cloud-errors (~> 1.0)
|
||||||
|
google-cloud-env (1.6.0)
|
||||||
|
faraday (>= 0.17.3, < 3.0)
|
||||||
|
google-cloud-errors (1.5.0)
|
||||||
|
google-cloud-storage (1.47.0)
|
||||||
|
addressable (~> 2.8)
|
||||||
|
digest-crc (~> 0.4)
|
||||||
|
google-apis-iamcredentials_v1 (~> 0.1)
|
||||||
|
google-apis-storage_v1 (~> 0.31.0)
|
||||||
|
google-cloud-core (~> 1.6)
|
||||||
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
|
mini_mime (~> 1.0)
|
||||||
|
googleauth (1.8.1)
|
||||||
|
faraday (>= 0.17.3, < 3.a)
|
||||||
|
jwt (>= 1.4, < 3.0)
|
||||||
|
multi_json (~> 1.11)
|
||||||
|
os (>= 0.9, < 2.0)
|
||||||
|
signet (>= 0.16, < 2.a)
|
||||||
|
highline (2.0.3)
|
||||||
|
http-cookie (1.0.8)
|
||||||
|
domain_name (~> 0.5)
|
||||||
|
httpclient (2.9.0)
|
||||||
|
mutex_m
|
||||||
|
i18n (1.14.7)
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
|
jmespath (1.6.2)
|
||||||
|
json (2.10.2)
|
||||||
|
jwt (2.10.1)
|
||||||
|
base64
|
||||||
|
logger (1.6.6)
|
||||||
|
mini_magick (4.13.2)
|
||||||
|
mini_mime (1.1.5)
|
||||||
|
minitest (5.25.5)
|
||||||
|
molinillo (0.8.0)
|
||||||
|
multi_json (1.15.0)
|
||||||
|
multipart-post (2.4.1)
|
||||||
|
mutex_m (0.3.0)
|
||||||
|
nanaimo (0.4.0)
|
||||||
|
nap (1.1.0)
|
||||||
|
naturally (2.2.1)
|
||||||
|
netrc (0.11.0)
|
||||||
|
nkf (0.2.0)
|
||||||
|
optparse (0.6.0)
|
||||||
|
os (1.1.4)
|
||||||
|
plist (3.7.2)
|
||||||
|
public_suffix (4.0.7)
|
||||||
|
rake (13.2.1)
|
||||||
|
representable (3.2.0)
|
||||||
|
declarative (< 0.1.0)
|
||||||
|
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||||
|
uber (< 0.2.0)
|
||||||
|
retriable (3.1.2)
|
||||||
|
rexml (3.4.1)
|
||||||
|
rouge (3.28.0)
|
||||||
|
ruby-macho (2.5.1)
|
||||||
|
ruby2_keywords (0.0.5)
|
||||||
|
rubyzip (2.4.1)
|
||||||
|
securerandom (0.4.1)
|
||||||
|
security (0.1.5)
|
||||||
|
signet (0.19.0)
|
||||||
|
addressable (~> 2.8)
|
||||||
|
faraday (>= 0.17.5, < 3.a)
|
||||||
|
jwt (>= 1.5, < 3.0)
|
||||||
|
multi_json (~> 1.10)
|
||||||
|
simctl (1.6.10)
|
||||||
|
CFPropertyList
|
||||||
|
naturally
|
||||||
|
sysrandom (1.0.5)
|
||||||
|
terminal-notifier (2.0.0)
|
||||||
|
terminal-table (3.0.2)
|
||||||
|
unicode-display_width (>= 1.1.1, < 3)
|
||||||
|
trailblazer-option (0.1.2)
|
||||||
|
tty-cursor (0.7.1)
|
||||||
|
tty-screen (0.8.2)
|
||||||
|
tty-spinner (0.9.3)
|
||||||
|
tty-cursor (~> 0.7)
|
||||||
|
typhoeus (1.4.1)
|
||||||
|
ethon (>= 0.9.0)
|
||||||
|
tzinfo (2.0.6)
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
|
uber (0.1.0)
|
||||||
|
unicode-display_width (2.6.0)
|
||||||
|
word_wrap (1.0.0)
|
||||||
|
xcodeproj (1.27.0)
|
||||||
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
|
atomos (~> 0.1.3)
|
||||||
|
claide (>= 1.0.2, < 2.0)
|
||||||
|
colored2 (~> 3.1)
|
||||||
|
nanaimo (~> 0.4.0)
|
||||||
|
rexml (>= 3.3.6, < 4.0)
|
||||||
|
xcpretty (0.4.0)
|
||||||
|
rouge (~> 3.28.0)
|
||||||
|
xcpretty-travis-formatter (1.0.1)
|
||||||
|
xcpretty (~> 0.2, >= 0.0.7)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
aarch64-linux-gnu
|
||||||
|
aarch64-linux-musl
|
||||||
|
arm-linux-gnu
|
||||||
|
arm-linux-musl
|
||||||
|
arm64-darwin
|
||||||
|
ruby
|
||||||
|
x86-linux-gnu
|
||||||
|
x86-linux-musl
|
||||||
|
x86_64-darwin
|
||||||
|
x86_64-linux-gnu
|
||||||
|
x86_64-linux-musl
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
cocoapods
|
||||||
|
fastlane
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
2.6.5
|
||||||
63
README.md
@@ -8,8 +8,6 @@ and expand to crowd-fund with time & money, then record and see the impact of co
|
|||||||
See [project.task.yaml](project.task.yaml) for current priorities.
|
See [project.task.yaml](project.task.yaml) for current priorities.
|
||||||
(Numbers at the beginning of lines are estimated hours. See [taskyaml.org](https://taskyaml.org/) for details.)
|
(Numbers at the beginning of lines are estimated hours. See [taskyaml.org](https://taskyaml.org/) for details.)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Setup & Building
|
## Setup & Building
|
||||||
|
|
||||||
Quick start:
|
Quick start:
|
||||||
@@ -19,63 +17,7 @@ npm install
|
|||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
See the test locations for "IMAGE_API_SERVER" or "PARTNER_API_SERVER" below, or use http://localhost:3000 for local endorser.ch
|
See [BUILDING.md](BUILDING.md) for more details.
|
||||||
|
|
||||||
### Build the test & production app
|
|
||||||
```
|
|
||||||
npm run serve
|
|
||||||
```
|
|
||||||
|
|
||||||
### Lint and fix files
|
|
||||||
```
|
|
||||||
npm run lint
|
|
||||||
```
|
|
||||||
|
|
||||||
### Run all UI tests
|
|
||||||
|
|
||||||
Look below for the "test-all" instructions.
|
|
||||||
|
|
||||||
|
|
||||||
### 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):
|
|
||||||
|
|
||||||
```
|
|
||||||
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: `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.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -91,8 +33,6 @@ See [TESTING.md](test-playwright/TESTING.md) for detailed test instructions.
|
|||||||
|
|
||||||
To add an icon, add to main.ts and reference with `fa` element and `icon` attribute with the hyphenated name.
|
To add an icon, add to main.ts and reference with `fa` element and `icon` attribute with the hyphenated name.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Other
|
## Other
|
||||||
|
|
||||||
### Reference Material
|
### Reference Material
|
||||||
@@ -104,7 +44,6 @@ To add an icon, add to main.ts and reference with `fa` element and `icon` attrib
|
|||||||
|
|
||||||
* If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js, eg: `publicPath: "/app/time-tracker/",`
|
* If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js, eg: `publicPath: "/app/time-tracker/",`
|
||||||
|
|
||||||
|
|
||||||
### Kudos
|
### Kudos
|
||||||
|
|
||||||
Gifts make the world go 'round!
|
Gifts make the world go 'round!
|
||||||
|
|||||||
3
android/.gitignore
vendored
@@ -1,5 +1,8 @@
|
|||||||
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
|
# 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
|
# Built application files
|
||||||
*.apk
|
*.apk
|
||||||
*.aar
|
*.aar
|
||||||
|
|||||||
2
android/.gradle/buildOutputCleanup/cache.properties
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#Wed Apr 09 09:01:13 UTC 2025
|
||||||
|
gradle.version=8.11.1
|
||||||
BIN
android/.gradle/file-system.probe
Normal file
0
android/.gradle/vcs-1/gc.properties
Normal file
@@ -1,14 +1,38 @@
|
|||||||
apply plugin: 'com.android.application'
|
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("app/gradle.properties.secrets")
|
||||||
|
if (secretsPropertiesFile.exists()) {
|
||||||
|
Properties secretsProperties = new Properties()
|
||||||
|
secretsProperties.load(new FileInputStream(secretsPropertiesFile))
|
||||||
|
secretsProperties.each { name, value ->
|
||||||
|
project.ext[name] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace "app.timesafari.app"
|
namespace 'app.timesafari'
|
||||||
compileSdk rootProject.ext.compileSdkVersion
|
compileSdk rootProject.ext.compileSdkVersion
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "app.timesafari.app"
|
applicationId "app.timesafari.app"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 1
|
versionCode 10
|
||||||
versionName "1.0"
|
versionName "0.4.4"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||||
@@ -16,10 +40,41 @@ android {
|
|||||||
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
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 {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
// Only sign if we have the signing config
|
||||||
|
if (signingConfigs.release.storeFile != null) {
|
||||||
|
signingConfig signingConfigs.release
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable bundle builds (without which it doesn't work right for bundleDebug vs bundleRelease)
|
||||||
|
bundle {
|
||||||
|
language {
|
||||||
|
enableSplit = true
|
||||||
|
}
|
||||||
|
density {
|
||||||
|
enableSplit = true
|
||||||
|
}
|
||||||
|
abi {
|
||||||
|
enableSplit = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ android {
|
|||||||
|
|
||||||
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation project(':capacitor-app')
|
||||||
|
implementation project(':capacitor-camera')
|
||||||
|
implementation project(':capacitor-filesystem')
|
||||||
|
implementation project(':capacitor-share')
|
||||||
|
implementation project(':capawesome-capacitor-file-picker')
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,6 @@ public class ExampleInstrumentedTest {
|
|||||||
// Context of the app under test.
|
// Context of the app under test.
|
||||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||||
|
|
||||||
assertEquals("com.getcapacitor.app", appContext.getPackageName());
|
assertEquals("app.timesafari.app", appContext.getPackageName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
@@ -8,20 +7,24 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
|
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
|
||||||
|
android:exported="true"
|
||||||
android:label="@string/title_activity_main"
|
android:label="@string/title_activity_main"
|
||||||
android:theme="@style/AppTheme.NoActionBarLaunch"
|
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:exported="true">
|
android:theme="@style/AppTheme.NoActionBarLaunch">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="timesafari" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
@@ -29,13 +32,13 @@
|
|||||||
android:authorities="${applicationId}.fileprovider"
|
android:authorities="${applicationId}.fileprovider"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:grantUriPermissions="true">
|
android:grantUriPermissions="true">
|
||||||
<meta-data
|
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
|
||||||
android:resource="@xml/file_paths"></meta-data>
|
|
||||||
</provider>
|
</provider>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
<!-- Permissions -->
|
<!-- Permissions -->
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
21
android/app/src/main/assets/capacitor.config.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"appId": "app.timesafari",
|
||||||
|
"appName": "TimeSafari",
|
||||||
|
"webDir": "dist",
|
||||||
|
"bundledWebRuntime": false,
|
||||||
|
"server": {
|
||||||
|
"cleartext": true
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"App": {
|
||||||
|
"appUrlOpen": {
|
||||||
|
"handlers": [
|
||||||
|
{
|
||||||
|
"url": "timesafari://*",
|
||||||
|
"autoVerify": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
android/app/src/main/assets/capacitor.plugins.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"pkg": "@capacitor/app",
|
||||||
|
"classpath": "com.capacitorjs.plugins.app.AppPlugin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pkg": "@capacitor/camera",
|
||||||
|
"classpath": "com.capacitorjs.plugins.camera.CameraPlugin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pkg": "@capacitor/filesystem",
|
||||||
|
"classpath": "com.capacitorjs.plugins.filesystem.FilesystemPlugin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pkg": "@capacitor/share",
|
||||||
|
"classpath": "com.capacitorjs.plugins.share.SharePlugin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pkg": "@capawesome/capacitor-file-picker",
|
||||||
|
"classpath": "io.capawesome.capacitorjs.plugins.filepicker.FilePickerPlugin"
|
||||||
|
}
|
||||||
|
]
|
||||||
0
android/app/src/main/assets/public/cordova.js
vendored
Normal file
0
android/app/src/main/assets/public/cordova_plugins.js
vendored
Normal file
BIN
android/app/src/main/assets/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 270 KiB |
|
After Width: | Height: | Size: 332 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 463 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 150 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 70 KiB |
BIN
android/app/src/main/assets/public/img/icons/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
android/app/src/main/assets/public/img/icons/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 46 KiB |
BIN
android/app/src/main/assets/public/img/icons/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
@@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
|
||||||
|
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M2480 4005 c-25 -7 -58 -20 -75 -29 -16 -9 -40 -16 -52 -16 -17 0
|
||||||
|
-24 -7 -28 -27 -3 -16 -14 -45 -24 -65 -21 -41 -13 -55 18 -38 25 13 67 13 92
|
||||||
|
-1 15 -8 35 -4 87 17 99 39 130 41 197 10 64 -29 77 -31 107 -15 20 11 20 11
|
||||||
|
-3 35 -12 13 -30 24 -38 24 -24 1 -132 38 -148 51 -8 7 -11 20 -7 32 12 37
|
||||||
|
-40 47 -126 22z"/>
|
||||||
|
<path d="M1450 3775 c-7 -8 -18 -15 -24 -15 -7 0 -31 -14 -54 -32 -29 -22 -38
|
||||||
|
-34 -29 -40 17 -11 77 -10 77 1 0 5 16 16 35 25 60 29 220 19 290 -18 17 -9
|
||||||
|
33 -16 37 -16 4 0 31 -15 60 -34 108 -70 224 -215 282 -353 30 -71 53 -190 42
|
||||||
|
-218 -10 -27 -23 -8 -52 75 -30 90 -88 188 -120 202 -13 6 -26 9 -29 6 -3 -2
|
||||||
|
11 -51 30 -108 28 -83 35 -119 35 -179 0 -120 -22 -127 -54 -17 -11 37 -13 21
|
||||||
|
-18 -154 -5 -180 -8 -200 -32 -264 -51 -132 -129 -245 -199 -288 -21 -12 -79
|
||||||
|
-49 -129 -80 -161 -102 -294 -141 -473 -141 -228 0 -384 76 -535 259 -81 99
|
||||||
|
-118 174 -154 312 -31 121 -35 273 -11 437 19 127 19 125 -4 125 -23 0 -51
|
||||||
|
-34 -87 -104 -14 -28 -33 -64 -41 -81 -19 -34 -22 -253 -7 -445 9 -106 12
|
||||||
|
-119 44 -170 19 -30 42 -67 50 -81 64 -113 85 -140 130 -169 28 -18 53 -44 61
|
||||||
|
-62 8 -20 36 -45 83 -76 62 -39 80 -46 151 -54 44 -5 96 -13 115 -18 78 -20
|
||||||
|
238 -31 282 -19 24 6 66 8 95 5 76 -9 169 24 319 114 32 19 80 56 106 82 27
|
||||||
|
26 52 48 58 48 5 0 27 26 50 58 48 66 56 70 132 71 62 1 165 29 238 64 112 55
|
||||||
|
177 121 239 245 37 76 39 113 10 267 -12 61 -23 131 -26 156 -5 46 -5 47 46
|
||||||
|
87 92 73 182 70 263 -8 l51 -49 -6 -61 c-4 -34 -13 -85 -21 -113 -28 -103 -30
|
||||||
|
-161 -4 -228 16 -44 32 -67 55 -83 18 -11 39 -37 47 -58 10 -23 37 -53 73 -81
|
||||||
|
32 -25 69 -57 82 -71 14 -14 34 -26 47 -26 12 0 37 -7 56 -15 20 -8 66 -17
|
||||||
|
104 -20 107 -10 110 -11 150 -71 50 -75 157 -177 197 -187 18 -5 53 -24 78
|
||||||
|
-42 71 -51 176 -82 304 -89 61 -4 127 -12 147 -18 29 -9 45 -8 77 6 23 9 50
|
||||||
|
16 60 16 31 0 163 46 216 76 28 15 75 46 105 69 30 23 69 49 85 58 17 8 46 31
|
||||||
|
64 51 19 20 40 36 47 36 18 0 77 70 100 120 32 66 45 108 55 173 5 32 16 71
|
||||||
|
24 87 43 84 43 376 0 549 -27 105 -43 127 -135 188 -30 21 -65 46 -77 57 -13
|
||||||
|
11 -23 17 -23 14 0 -3 21 -46 47 -94 79 -151 85 -166 115 -263 25 -83 28 -110
|
||||||
|
28 -226 0 -144 -17 -221 -75 -335 -39 -77 -208 -244 -304 -299 -451 -263 -975
|
||||||
|
-67 -1138 426 -23 70 -26 95 -28 254 -1 108 -7 183 -14 196 -6 12 -11 31 -11
|
||||||
|
43 0 32 31 122 52 149 10 13 18 28 18 34 0 5 25 40 56 78 60 73 172 170 219
|
||||||
|
190 30 12 30 13 6 17 -15 2 -29 -2 -37 -12 -6 -9 -16 -16 -22 -16 -6 0 -23
|
||||||
|
-11 -39 -24 -15 -12 -33 -25 -40 -27 -17 -6 -82 -60 -117 -97 -65 -70 -75 -82
|
||||||
|
-107 -133 -23 -34 -35 -46 -37 -35 -3 16 20 87 44 134 6 12 9 34 6 48 -4 22
|
||||||
|
-8 25 -31 19 -14 -3 -38 -15 -53 -26 -34 -24 -34 -21 -6 28 65 112 184 206
|
||||||
|
291 227 15 3 39 9 55 12 l27 6 -24 9 c-90 35 -304 -66 -478 -225 -39 -36 -74
|
||||||
|
-66 -77 -66 -22 0 18 82 72 148 19 23 32 46 28 49 -4 4 -26 13 -49 19 -73 21
|
||||||
|
-161 54 -171 64 -6 6 -20 10 -32 10 -21 0 -21 -1 -8 -40 45 -130 8 -247 -93
|
||||||
|
-299 -25 -13 -31 0 -14 29 15 22 1 33 -22 17 -56 -36 -117 -22 -117 28 0 13
|
||||||
|
-16 47 -35 76 -22 34 -33 60 -29 73 4 16 -3 26 -26 39 -16 10 -30 21 -30 25 1
|
||||||
|
18 54 64 87 76 l38 13 -33 5 c-30 4 -115 -18 -154 -42 -13 -7 -20 -5 -27 8 -9
|
||||||
|
16 -12 16 -53 1 -160 -61 -258 -104 -258 -114 0 -7 10 -20 21 -31 103 -91 217
|
||||||
|
-297 249 -449 28 -135 41 -237 35 -276 -14 -91 -48 -170 -97 -220 -44 -47 -68
|
||||||
|
-60 -68 -40 0 6 4 12 8 15 5 3 24 35 42 72 l33 67 -6 141 c-4 103 -11 158 -26
|
||||||
|
205 -12 35 -21 70 -21 77 0 7 -20 56 -45 108 -82 173 -227 322 -392 401 -67
|
||||||
|
33 -90 39 -163 42 -108 5 -130 10 -130 28 0 20 -63 20 -80 0z"/>
|
||||||
|
<path d="M3710 3765 c0 -20 8 -28 39 -41 22 -8 42 -22 45 -30 5 -14 42 -19 70
|
||||||
|
-8 10 4 -7 21 -58 55 -41 27 -79 49 -85 49 -6 0 -11 -11 -11 -25z"/>
|
||||||
|
<path d="M3173 3734 c-9 -25 10 -36 35 -18 12 8 22 19 22 25 0 16 -50 10 -57
|
||||||
|
-7z"/>
|
||||||
|
<path d="M1982 3728 c6 -16 36 -34 44 -26 3 4 4 14 1 23 -7 17 -51 21 -45 3z"/>
|
||||||
|
<path d="M1540 3620 c0 -5 7 -10 16 -10 8 0 12 5 9 10 -3 6 -10 10 -16 10 -5
|
||||||
|
0 -9 -4 -9 -10z"/>
|
||||||
|
<path d="M4467 3624 c-4 -4 23 -27 60 -50 84 -56 99 -58 67 -9 -28 43 -107 79
|
||||||
|
-127 59z"/>
|
||||||
|
<path d="M655 3552 c-11 -2 -26 -9 -33 -14 -7 -6 -27 -18 -45 -27 -36 -18 -58
|
||||||
|
-64 -39 -83 9 -9 25 1 70 43 53 48 78 78 70 84 -2 1 -12 -1 -23 -3z"/>
|
||||||
|
<path d="M1015 3460 c-112 -24 -247 -98 -303 -165 -53 -65 -118 -214 -136
|
||||||
|
-311 -20 -113 -20 -145 -1 -231 20 -88 49 -153 102 -230 79 -113 186 -182 331
|
||||||
|
-214 108 -24 141 -24 247 1 130 30 202 72 316 181 102 100 153 227 152 384 0
|
||||||
|
142 -58 293 -150 395 -60 67 -180 145 -261 171 -75 23 -232 34 -297 19z m340
|
||||||
|
-214 c91 -43 174 -154 175 -234 0 -18 -9 -51 -21 -73 -19 -37 -19 -42 -5 -64
|
||||||
|
35 -54 12 -121 -48 -142 -22 -7 -47 -19 -55 -27 -9 -8 -41 -27 -71 -42 -50
|
||||||
|
-26 -64 -29 -155 -29 -111 0 -152 14 -206 68 -49 49 -63 85 -64 162 0 59 4 78
|
||||||
|
28 118 31 52 96 105 141 114 23 5 33 17 56 68 46 103 121 130 225 81z"/>
|
||||||
|
<path d="M3985 3464 c-44 -7 -154 -44 -200 -67 -55 -28 -138 -96 -162 -132
|
||||||
|
-10 -16 -39 -75 -64 -130 l-44 -100 0 -160 0 -160 45 -90 c53 -108 152 -214
|
||||||
|
245 -264 59 -31 215 -71 281 -71 53 0 206 40 255 67 98 53 203 161 247 253 53
|
||||||
|
113 74 193 74 280 -1 304 -253 564 -557 575 -49 2 -103 1 -120 -1z m311 -220
|
||||||
|
c129 -68 202 -209 160 -309 -15 -35 -15 -42 -1 -72 26 -55 -3 -118 -59 -129
|
||||||
|
-19 -3 -43 -15 -53 -26 -26 -29 -99 -64 -165 -78 -45 -10 -69 -10 -120 -1 -74
|
||||||
|
15 -113 37 -161 91 -110 120 -50 331 109 385 24 8 44 23 52 39 6 14 18 38 25
|
||||||
|
53 33 72 127 93 213 47z"/>
|
||||||
|
<path d="M487 3394 c-21 -12 -27 -21 -25 -40 2 -14 7 -26 12 -27 14 -3 48 48
|
||||||
|
44 66 -3 14 -6 14 -31 1z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 705 KiB |
@@ -0,0 +1,11 @@
|
|||||||
|
Model Information:
|
||||||
|
* title: Lupine Plant
|
||||||
|
* source: https://sketchfab.com/3d-models/lupine-plant-bf30f1110c174d4baedda0ed63778439
|
||||||
|
* author: rufusrockwell (https://sketchfab.com/rufusrockwell)
|
||||||
|
|
||||||
|
Model License:
|
||||||
|
* license type: CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)
|
||||||
|
* requirements: Author must be credited. Commercial use is allowed.
|
||||||
|
|
||||||
|
If you use this 3D model in your project be sure to copy paste this credit wherever you share it:
|
||||||
|
This work is based on "Lupine Plant" (https://sketchfab.com/3d-models/lupine-plant-bf30f1110c174d4baedda0ed63778439) by rufusrockwell (https://sketchfab.com/rufusrockwell) licensed under CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)
|
||||||
BIN
android/app/src/main/assets/public/models/lupine_plant/scene.bin
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
{
|
||||||
|
"accessors": [
|
||||||
|
{
|
||||||
|
"bufferView": 2,
|
||||||
|
"componentType": 5126,
|
||||||
|
"count": 2759,
|
||||||
|
"max": [
|
||||||
|
41.3074951171875,
|
||||||
|
40.37548828125,
|
||||||
|
87.85917663574219
|
||||||
|
],
|
||||||
|
"min": [
|
||||||
|
-35.245540618896484,
|
||||||
|
-36.895416259765625,
|
||||||
|
-0.9094290137290955
|
||||||
|
],
|
||||||
|
"type": "VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView": 2,
|
||||||
|
"byteOffset": 33108,
|
||||||
|
"componentType": 5126,
|
||||||
|
"count": 2759,
|
||||||
|
"max": [
|
||||||
|
0.9999382495880127,
|
||||||
|
0.9986748695373535,
|
||||||
|
0.9985831379890442
|
||||||
|
],
|
||||||
|
"min": [
|
||||||
|
-0.9998949766159058,
|
||||||
|
-0.9975876212120056,
|
||||||
|
-0.411094069480896
|
||||||
|
],
|
||||||
|
"type": "VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView": 3,
|
||||||
|
"componentType": 5126,
|
||||||
|
"count": 2759,
|
||||||
|
"max": [
|
||||||
|
0.9987699389457703,
|
||||||
|
0.9998998045921326,
|
||||||
|
0.9577858448028564,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"min": [
|
||||||
|
-0.9987726807594299,
|
||||||
|
-0.9990445971488953,
|
||||||
|
-0.999801516532898,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"type": "VEC4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView": 1,
|
||||||
|
"componentType": 5126,
|
||||||
|
"count": 2759,
|
||||||
|
"max": [
|
||||||
|
1.0061479806900024,
|
||||||
|
0.9993550181388855
|
||||||
|
],
|
||||||
|
"min": [
|
||||||
|
0.00279300007969141,
|
||||||
|
0.0011620000004768372
|
||||||
|
],
|
||||||
|
"type": "VEC2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView": 0,
|
||||||
|
"componentType": 5125,
|
||||||
|
"count": 6378,
|
||||||
|
"type": "SCALAR"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"asset": {
|
||||||
|
"extras": {
|
||||||
|
"author": "rufusrockwell (https://sketchfab.com/rufusrockwell)",
|
||||||
|
"license": "CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)",
|
||||||
|
"source": "https://sketchfab.com/3d-models/lupine-plant-bf30f1110c174d4baedda0ed63778439",
|
||||||
|
"title": "Lupine Plant"
|
||||||
|
},
|
||||||
|
"generator": "Sketchfab-12.68.0",
|
||||||
|
"version": "2.0"
|
||||||
|
},
|
||||||
|
"bufferViews": [
|
||||||
|
{
|
||||||
|
"buffer": 0,
|
||||||
|
"byteLength": 25512,
|
||||||
|
"name": "floatBufferViews",
|
||||||
|
"target": 34963
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer": 0,
|
||||||
|
"byteLength": 22072,
|
||||||
|
"byteOffset": 25512,
|
||||||
|
"byteStride": 8,
|
||||||
|
"name": "floatBufferViews",
|
||||||
|
"target": 34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer": 0,
|
||||||
|
"byteLength": 66216,
|
||||||
|
"byteOffset": 47584,
|
||||||
|
"byteStride": 12,
|
||||||
|
"name": "floatBufferViews",
|
||||||
|
"target": 34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer": 0,
|
||||||
|
"byteLength": 44144,
|
||||||
|
"byteOffset": 113800,
|
||||||
|
"byteStride": 16,
|
||||||
|
"name": "floatBufferViews",
|
||||||
|
"target": 34962
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buffers": [
|
||||||
|
{
|
||||||
|
"byteLength": 157944,
|
||||||
|
"uri": "scene.bin"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"uri": "textures/lambert2SG_baseColor.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uri": "textures/lambert2SG_normal.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"materials": [
|
||||||
|
{
|
||||||
|
"alphaCutoff": 0.2,
|
||||||
|
"alphaMode": "MASK",
|
||||||
|
"doubleSided": true,
|
||||||
|
"name": "lambert2SG",
|
||||||
|
"normalTexture": {
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"pbrMetallicRoughness": {
|
||||||
|
"baseColorTexture": {
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
"metallicFactor": 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meshes": [
|
||||||
|
{
|
||||||
|
"name": "Object_0",
|
||||||
|
"primitives": [
|
||||||
|
{
|
||||||
|
"attributes": {
|
||||||
|
"NORMAL": 1,
|
||||||
|
"POSITION": 0,
|
||||||
|
"TANGENT": 2,
|
||||||
|
"TEXCOORD_0": 3
|
||||||
|
},
|
||||||
|
"indices": 4,
|
||||||
|
"material": 0,
|
||||||
|
"mode": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"matrix": [
|
||||||
|
1.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
2.220446049250313e-16,
|
||||||
|
-1.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
2.220446049250313e-16,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"name": "Sketchfab_model"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"name": "LupineSF.obj.cleaner.materialmerger.gles"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mesh": 0,
|
||||||
|
"name": "Object_2"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"samplers": [
|
||||||
|
{
|
||||||
|
"magFilter": 9729,
|
||||||
|
"minFilter": 9987,
|
||||||
|
"wrapS": 10497,
|
||||||
|
"wrapT": 10497
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scene": 0,
|
||||||
|
"scenes": [
|
||||||
|
{
|
||||||
|
"name": "Sketchfab_Scene",
|
||||||
|
"nodes": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"textures": [
|
||||||
|
{
|
||||||
|
"sampler": 0,
|
||||||
|
"source": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sampler": 0,
|
||||||
|
"source": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 3.6 MiB |
|
After Width: | Height: | Size: 4.7 MiB |
2
android/app/src/main/assets/public/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
@@ -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;
|
import com.getcapacitor.BridgeActivity;
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 23 KiB |
@@ -2,6 +2,6 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">TimeSafari</string>
|
<string name="app_name">TimeSafari</string>
|
||||||
<string name="title_activity_main">TimeSafari</string>
|
<string name="title_activity_main">TimeSafari</string>
|
||||||
<string name="package_name">app.timesafari.app</string>
|
<string name="package_name">timesafari.app</string>
|
||||||
<string name="custom_url_scheme">app.timesafari.app</string>
|
<string name="custom_url_scheme">timesafari.app</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
6
android/app/src/main/res/xml/config.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<widget version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
||||||
|
<access origin="*" />
|
||||||
|
|
||||||
|
|
||||||
|
</widget>
|
||||||
@@ -2,4 +2,5 @@
|
|||||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<external-path name="my_images" path="." />
|
<external-path name="my_images" path="." />
|
||||||
<cache-path name="my_cache_images" path="." />
|
<cache-path name="my_cache_images" path="." />
|
||||||
|
<files-path name="my_files" path="." />
|
||||||
</paths>
|
</paths>
|
||||||
@@ -7,7 +7,7 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:8.8.1'
|
classpath 'com.android.tools.build:gradle:8.9.1'
|
||||||
classpath 'com.google.gms:google-services:4.4.0'
|
classpath 'com.google.gms:google-services:4.4.0'
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
|||||||
59
android/capacitor-cordova-android-plugins/build.gradle
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
ext {
|
||||||
|
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.6.1'
|
||||||
|
cordovaAndroidVersion = project.hasProperty('cordovaAndroidVersion') ? rootProject.ext.cordovaAndroidVersion : '10.1.1'
|
||||||
|
}
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:8.2.1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace "capacitor.cordova.android.plugins"
|
||||||
|
compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 34
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 22
|
||||||
|
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 34
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
}
|
||||||
|
lintOptions {
|
||||||
|
abortOnError false
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
flatDir{
|
||||||
|
dirs 'src/main/libs', 'libs'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(dir: 'src/main/libs', include: ['*.jar'])
|
||||||
|
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
||||||
|
implementation "org.apache.cordova:framework:$cordovaAndroidVersion"
|
||||||
|
// SUB-PROJECT DEPENDENCIES START
|
||||||
|
|
||||||
|
// SUB-PROJECT DEPENDENCIES END
|
||||||
|
}
|
||||||
|
|
||||||
|
// PLUGIN GRADLE EXTENSIONS START
|
||||||
|
apply from: "cordova.variables.gradle"
|
||||||
|
// PLUGIN GRADLE EXTENSIONS END
|
||||||
|
|
||||||
|
for (def func : cdvPluginPostBuildExtras) {
|
||||||
|
func()
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||||
|
ext {
|
||||||
|
cdvMinSdkVersion = project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 22
|
||||||
|
// Plugin gradle extensions can append to this to have code run at the end.
|
||||||
|
cdvPluginPostBuildExtras = []
|
||||||
|
cordovaConfig = [:]
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:amazon="http://schemas.amazon.com/apk/res/android">
|
||||||
|
<application android:usesCleartextTraffic="true">
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -1,3 +1,18 @@
|
|||||||
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||||
include ':capacitor-android'
|
include ':capacitor-android'
|
||||||
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
|
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
|
||||||
|
|
||||||
|
include ':capacitor-app'
|
||||||
|
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
|
||||||
|
|
||||||
|
include ':capacitor-camera'
|
||||||
|
project(':capacitor-camera').projectDir = new File('../node_modules/@capacitor/camera/android')
|
||||||
|
|
||||||
|
include ':capacitor-filesystem'
|
||||||
|
project(':capacitor-filesystem').projectDir = new File('../node_modules/@capacitor/filesystem/android')
|
||||||
|
|
||||||
|
include ':capacitor-share'
|
||||||
|
project(':capacitor-share').projectDir = new File('../node_modules/@capacitor/share/android')
|
||||||
|
|
||||||
|
include ':capawesome-capacitor-file-picker'
|
||||||
|
project(':capawesome-capacitor-file-picker').projectDir = new File('../node_modules/@capawesome/capacitor-file-picker/android')
|
||||||
|
|||||||
@@ -20,3 +20,4 @@ org.gradle.jvmargs=-Xmx1536m
|
|||||||
# Android operating system, and which are packaged with your app's APK
|
# Android operating system, and which are packaged with your app's APK
|
||||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
|
android.suppressUnsupportedCompileSdk=34
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
BIN
assets/icon-only.jpg
Normal file
|
After Width: | Height: | Size: 60 KiB |
@@ -1,13 +1,25 @@
|
|||||||
import type { CapacitorConfig } from '@capacitor/cli';
|
import { CapacitorConfig } from '@capacitor/cli';
|
||||||
|
|
||||||
const config: CapacitorConfig = {
|
const config: CapacitorConfig = {
|
||||||
appId: 'app.timesafari.app',
|
appId: 'app.timesafari',
|
||||||
appName: 'TimeSafari',
|
appName: 'TimeSafari',
|
||||||
webDir: 'dist',
|
webDir: 'dist',
|
||||||
bundledWebRuntime: false,
|
bundledWebRuntime: false,
|
||||||
server: {
|
server: {
|
||||||
cleartext: true,
|
cleartext: true,
|
||||||
},
|
},
|
||||||
|
plugins: {
|
||||||
|
App: {
|
||||||
|
appUrlOpen: {
|
||||||
|
handlers: [
|
||||||
|
{
|
||||||
|
url: "timesafari://*",
|
||||||
|
autoVerify: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|||||||
91
docs/DEEP_LINKS.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# TimeSafari Deep Linking Documentation
|
||||||
|
|
||||||
|
## Type System Overview
|
||||||
|
|
||||||
|
The deep linking system uses a multi-layered type safety approach:
|
||||||
|
|
||||||
|
1. **Runtime Validation (Zod Schemas)**
|
||||||
|
- Validates URL structure
|
||||||
|
- Enforces parameter requirements
|
||||||
|
- Sanitizes input data
|
||||||
|
- Provides detailed validation errors
|
||||||
|
|
||||||
|
2. **TypeScript Types**
|
||||||
|
- Generated from Zod schemas
|
||||||
|
- Ensures compile-time type safety
|
||||||
|
- Provides IDE autocompletion
|
||||||
|
- Catches type errors during development
|
||||||
|
|
||||||
|
3. **Router Integration**
|
||||||
|
- Type-safe parameter passing
|
||||||
|
- Route-specific parameter validation
|
||||||
|
- Query parameter type checking
|
||||||
|
|
||||||
|
## Implementation Files
|
||||||
|
|
||||||
|
- `src/types/deepLinks.ts`: Type definitions and validation schemas
|
||||||
|
- `src/services/deepLinks.ts`: Deep link processing service
|
||||||
|
- `src/main.capacitor.ts`: Capacitor integration
|
||||||
|
|
||||||
|
## Type Safety Examples
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Parameter type safety
|
||||||
|
type ClaimParams = DeepLinkParams["claim"];
|
||||||
|
// TypeScript knows this has:
|
||||||
|
// - id: string
|
||||||
|
// - view?: "details" | "certificate" | "raw"
|
||||||
|
// Runtime validation
|
||||||
|
const result = deepLinkSchemas.claim.safeParse({
|
||||||
|
id: "123",
|
||||||
|
view: "details"
|
||||||
|
});
|
||||||
|
// Validates at runtime with detailed error messages
|
||||||
|
```
|
||||||
|
|
||||||
|
## Supported URL Schemes
|
||||||
|
|
||||||
|
All deep links follow the format: `timesafari://<route>/<param>?<query>`
|
||||||
|
|
||||||
|
### Claim Routes
|
||||||
|
|
||||||
|
- `timesafari://claim/:id`
|
||||||
|
- Query params:
|
||||||
|
- `view`: "details" | "certificate" | "raw"
|
||||||
|
|
||||||
|
- `timesafari://claim-cert/:id`
|
||||||
|
- `timesafari://claim-add-raw/:id`
|
||||||
|
- Query params:
|
||||||
|
- `claim`: JSON string of claim data
|
||||||
|
- `claimJwtId`: JWT ID for claim
|
||||||
|
|
||||||
|
### Contact Routes
|
||||||
|
|
||||||
|
- `timesafari://contact-edit/:did`
|
||||||
|
- `timesafari://contact-import/:jwt`
|
||||||
|
- Query params:
|
||||||
|
- `contacts`: JSON array of contacts
|
||||||
|
|
||||||
|
### Project Routes
|
||||||
|
|
||||||
|
- `timesafari://project/:id`
|
||||||
|
- Query params:
|
||||||
|
- `view`: "details" | "edit"
|
||||||
|
|
||||||
|
### Invite Routes
|
||||||
|
|
||||||
|
- `timesafari://invite-one-accept/:jwt`
|
||||||
|
- Query params:
|
||||||
|
- `type`: "one" | "many"
|
||||||
|
|
||||||
|
### Gift Routes
|
||||||
|
|
||||||
|
- `timesafari://confirm-gift/:id`
|
||||||
|
- Query params:
|
||||||
|
- `action`: "confirm" | "details"
|
||||||
|
|
||||||
|
### Offer Routes
|
||||||
|
|
||||||
|
- `timesafari://offer-details/:id`
|
||||||
|
- Query params:
|
||||||
|
- `view`: "details"
|
||||||
17
index.html
@@ -12,6 +12,21 @@
|
|||||||
<strong>We're sorry but TimeSafari doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
<strong>We're sorry but TimeSafari doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
</noscript>
|
</noscript>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module">
|
||||||
|
const platform = process.env.VITE_PLATFORM;
|
||||||
|
switch (platform) {
|
||||||
|
case 'capacitor':
|
||||||
|
import('./src/main.capacitor.ts');
|
||||||
|
break;
|
||||||
|
case 'electron':
|
||||||
|
import('./src/main.electron.ts');
|
||||||
|
break;
|
||||||
|
case 'pywebview':
|
||||||
|
import('./src/main.pywebview.ts');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
import('./src/main.web.ts');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
17
ios/.gitignore
vendored
@@ -1,13 +1,16 @@
|
|||||||
App/build
|
App/build
|
||||||
App/Pods
|
|
||||||
App/output
|
App/output
|
||||||
App/App/public
|
App/Pods
|
||||||
DerivedData
|
|
||||||
xcuserdata
|
App/*.xcodeproj/xcuserdata/
|
||||||
|
App/*.xcworkspace/xcuserdata/
|
||||||
|
App/*/public
|
||||||
|
|
||||||
|
# Generated Config files
|
||||||
|
App/*/capacitor.config.json
|
||||||
|
App/*/config.xml
|
||||||
|
|
||||||
# Cordova plugins for Capacitor
|
# Cordova plugins for Capacitor
|
||||||
capacitor-cordova-ios-plugins
|
capacitor-cordova-ios-plugins
|
||||||
|
|
||||||
# Generated Config files
|
DerivedData
|
||||||
App/App/capacitor.config.json
|
|
||||||
App/App/config.xml
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
2BC611FE3D7967BDB623FF21 /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0C2082015AEE6A0776A3EAB /* Pods_App.framework */; };
|
||||||
2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; };
|
2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; };
|
||||||
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */ = {isa = PBXBuildFile; fileRef = 50379B222058CBB4000EE86E /* capacitor.config.json */; };
|
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */ = {isa = PBXBuildFile; fileRef = 50379B222058CBB4000EE86E /* capacitor.config.json */; };
|
||||||
504EC3081FED79650016851F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504EC3071FED79650016851F /* AppDelegate.swift */; };
|
504EC3081FED79650016851F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504EC3071FED79650016851F /* AppDelegate.swift */; };
|
||||||
@@ -14,7 +15,6 @@
|
|||||||
504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; };
|
504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; };
|
||||||
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; };
|
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; };
|
||||||
50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; };
|
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 */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
@@ -27,8 +27,10 @@
|
|||||||
504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; 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>"; };
|
504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; 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; };
|
821226CEE4D47A540167CC8F /* Pods-Time Safari.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Time Safari.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Time Safari/Pods-Time Safari.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
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>"; };
|
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>"; };
|
||||||
|
E0C2082015AEE6A0776A3EAB /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
EF03C3F99471948925ED5AC3 /* Pods-Time Safari.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Time Safari.release.xcconfig"; path = "Pods/Target Support Files/Pods-Time Safari/Pods-Time Safari.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>"; };
|
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 */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
@@ -37,7 +39,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */,
|
2BC611FE3D7967BDB623FF21 /* Pods_App.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -47,7 +49,7 @@
|
|||||||
27E2DDA53C4D2A4D1A88CE4A /* Frameworks */ = {
|
27E2DDA53C4D2A4D1A88CE4A /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */,
|
E0C2082015AEE6A0776A3EAB /* Pods_App.framework */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -90,6 +92,8 @@
|
|||||||
children = (
|
children = (
|
||||||
FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */,
|
FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */,
|
||||||
AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */,
|
AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */,
|
||||||
|
821226CEE4D47A540167CC8F /* Pods-Time Safari.debug.xcconfig */,
|
||||||
|
EF03C3F99471948925ED5AC3 /* Pods-Time Safari.release.xcconfig */,
|
||||||
);
|
);
|
||||||
name = Pods;
|
name = Pods;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -112,7 +116,7 @@
|
|||||||
dependencies = (
|
dependencies = (
|
||||||
);
|
);
|
||||||
name = App;
|
name = App;
|
||||||
productName = App;
|
productName = "Time Safari";
|
||||||
productReference = 504EC3041FED79650016851F /* App.app */;
|
productReference = 504EC3041FED79650016851F /* App.app */;
|
||||||
productType = "com.apple.product-type.application";
|
productType = "com.apple.product-type.application";
|
||||||
};
|
};
|
||||||
@@ -122,8 +126,8 @@
|
|||||||
504EC2FC1FED79650016851F /* Project object */ = {
|
504EC2FC1FED79650016851F /* Project object */ = {
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastSwiftUpdateCheck = 0920;
|
LastSwiftUpdateCheck = 920;
|
||||||
LastUpgradeCheck = 0920;
|
LastUpgradeCheck = 920;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
504EC3031FED79650016851F = {
|
504EC3031FED79650016851F = {
|
||||||
CreatedOnToolsVersion = 9.2;
|
CreatedOnToolsVersion = 9.2;
|
||||||
@@ -141,8 +145,6 @@
|
|||||||
Base,
|
Base,
|
||||||
);
|
);
|
||||||
mainGroup = 504EC2FB1FED79650016851F;
|
mainGroup = 504EC2FB1FED79650016851F;
|
||||||
packageReferences = (
|
|
||||||
);
|
|
||||||
productRefGroup = 504EC3051FED79650016851F /* Products */;
|
productRefGroup = 504EC3051FED79650016851F /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
@@ -348,13 +350,14 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 12;
|
||||||
|
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 0.4.4;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari.app;
|
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -368,12 +371,13 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 12;
|
||||||
|
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 0.4.4;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari.app;
|
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
|||||||
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:App.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:Pods/Pods.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?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>
|
|
||||||
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 116 KiB |
@@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images": [
|
||||||
{
|
{
|
||||||
"filename" : "AppIcon-512@2x.png",
|
"idiom": "universal",
|
||||||
"idiom" : "universal",
|
"size": "1024x1024",
|
||||||
"platform" : "ios",
|
"filename": "AppIcon-512@2x.png",
|
||||||
"size" : "1024x1024"
|
"platform": "ios"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info": {
|
||||||
"author" : "xcode",
|
"author": "xcode",
|
||||||
"version" : 1
|
"version": 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,6 +22,10 @@
|
|||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>Upload photos and scan friends' QR codes</string>
|
||||||
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
<string>Upload photos for gifts</string>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
@@ -45,5 +49,16 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>app.timesafari</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>timesafari</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array></dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ install! 'cocoapods', :disable_input_output_paths => true
|
|||||||
def capacitor_pods
|
def capacitor_pods
|
||||||
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
||||||
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
||||||
|
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
|
||||||
|
pod 'CapacitorCamera', :path => '../../node_modules/@capacitor/camera'
|
||||||
|
pod 'CapacitorFilesystem', :path => '../../node_modules/@capacitor/filesystem'
|
||||||
|
pod 'CapacitorShare', :path => '../../node_modules/@capacitor/share'
|
||||||
|
pod 'CapawesomeCapacitorFilePicker', :path => '../../node_modules/@capawesome/capacitor-file-picker'
|
||||||
end
|
end
|
||||||
|
|
||||||
target 'App' do
|
target 'App' do
|
||||||
|
|||||||
52
ios/App/Podfile.lock
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
PODS:
|
||||||
|
- Capacitor (6.2.1):
|
||||||
|
- CapacitorCordova
|
||||||
|
- CapacitorApp (6.0.2):
|
||||||
|
- Capacitor
|
||||||
|
- CapacitorCamera (6.1.2):
|
||||||
|
- Capacitor
|
||||||
|
- CapacitorCordova (6.2.1)
|
||||||
|
- CapacitorFilesystem (6.0.3):
|
||||||
|
- Capacitor
|
||||||
|
- CapacitorShare (6.0.3):
|
||||||
|
- Capacitor
|
||||||
|
- CapawesomeCapacitorFilePicker (6.2.0):
|
||||||
|
- Capacitor
|
||||||
|
|
||||||
|
DEPENDENCIES:
|
||||||
|
- "Capacitor (from `../../node_modules/@capacitor/ios`)"
|
||||||
|
- "CapacitorApp (from `../../node_modules/@capacitor/app`)"
|
||||||
|
- "CapacitorCamera (from `../../node_modules/@capacitor/camera`)"
|
||||||
|
- "CapacitorCordova (from `../../node_modules/@capacitor/ios`)"
|
||||||
|
- "CapacitorFilesystem (from `../../node_modules/@capacitor/filesystem`)"
|
||||||
|
- "CapacitorShare (from `../../node_modules/@capacitor/share`)"
|
||||||
|
- "CapawesomeCapacitorFilePicker (from `../../node_modules/@capawesome/capacitor-file-picker`)"
|
||||||
|
|
||||||
|
EXTERNAL SOURCES:
|
||||||
|
Capacitor:
|
||||||
|
:path: "../../node_modules/@capacitor/ios"
|
||||||
|
CapacitorApp:
|
||||||
|
:path: "../../node_modules/@capacitor/app"
|
||||||
|
CapacitorCamera:
|
||||||
|
:path: "../../node_modules/@capacitor/camera"
|
||||||
|
CapacitorCordova:
|
||||||
|
:path: "../../node_modules/@capacitor/ios"
|
||||||
|
CapacitorFilesystem:
|
||||||
|
:path: "../../node_modules/@capacitor/filesystem"
|
||||||
|
CapacitorShare:
|
||||||
|
:path: "../../node_modules/@capacitor/share"
|
||||||
|
CapawesomeCapacitorFilePicker:
|
||||||
|
:path: "../../node_modules/@capawesome/capacitor-file-picker"
|
||||||
|
|
||||||
|
SPEC CHECKSUMS:
|
||||||
|
Capacitor: c95400d761e376be9da6be5a05f226c0e865cebf
|
||||||
|
CapacitorApp: e1e6b7d05e444d593ca16fd6d76f2b7c48b5aea7
|
||||||
|
CapacitorCamera: 9bc7b005d0e6f1d5f525b8137045b60cffffce79
|
||||||
|
CapacitorCordova: 8d93e14982f440181be7304aa9559ca631d77fff
|
||||||
|
CapacitorFilesystem: 59270a63c60836248812671aa3b15df673fbaf74
|
||||||
|
CapacitorShare: d2a742baec21c8f3b92b361a2fbd2401cdd8288e
|
||||||
|
CapawesomeCapacitorFilePicker: c40822f0a39f86855321943c7829d52bca7f01bd
|
||||||
|
|
||||||
|
PODFILE CHECKSUM: 1e9280368fd410520414f5741bf8fdfe7847b965
|
||||||
|
|
||||||
|
COCOAPODS: 1.16.2
|
||||||
29
main.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
const { app, BrowserWindow } = require('electron');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
function createWindow() {
|
||||||
|
const win = new BrowserWindow({
|
||||||
|
width: 1200,
|
||||||
|
height: 800,
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: true,
|
||||||
|
contextIsolation: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
win.loadFile(path.join(__dirname, 'dist-electron/www/index.html'));
|
||||||
|
}
|
||||||
|
|
||||||
|
app.whenReady().then(createWindow);
|
||||||
|
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('activate', () => {
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
|
createWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
9923
package-lock.json
generated
77
package.json
@@ -1,43 +1,61 @@
|
|||||||
{
|
{
|
||||||
"name": "timesafari",
|
"name": "timesafari",
|
||||||
"version": "0.4.4",
|
"version": "0.4.4",
|
||||||
"description": "TimeSafari Desktop Application",
|
"description": "Time Safari Application",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "TimeSafari Team"
|
"name": "Time Safari Team"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite --config vite.config.dev.mts",
|
||||||
"serve": "vite preview",
|
"serve": "vite preview",
|
||||||
"build": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build",
|
"build": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --config vite.config.mts",
|
||||||
"lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src",
|
"lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src",
|
||||||
"lint-fix": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix 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",
|
"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 build && npx playwright test -c playwright.config-local.ts --trace on",
|
"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",
|
||||||
|
"test:android": "node scripts/test-android.js",
|
||||||
|
"test:ios": "node scripts/test-ios.js",
|
||||||
|
"check:android-device": "adb devices | grep -w 'device' || (echo 'No Android device connected' && exit 1)",
|
||||||
|
"check:ios-device": "xcrun xctrace list devices 2>&1 | grep -w 'Booted' || (echo 'No iOS simulator running' && exit 1)",
|
||||||
"clean:electron": "rimraf dist-electron",
|
"clean:electron": "rimraf dist-electron",
|
||||||
"build:electron": "npm run clean:electron && vite build --mode electron && node scripts/build-electron.js",
|
"build:pywebview": "vite build --config vite.config.pywebview.mts",
|
||||||
"build:capacitor": "vite build --mode capacitor",
|
"build:electron": "npm run clean:electron && vite build --config vite.config.electron.mts && node scripts/build-electron.js",
|
||||||
"build:web": "vite build",
|
"build:capacitor": "vite build --config vite.config.capacitor.mts",
|
||||||
|
"build:web": "vite build --config vite.config.web.mts",
|
||||||
"electron:dev": "npm run build && electron dist-electron",
|
"electron:dev": "npm run build && electron dist-electron",
|
||||||
"electron:start": "electron dist-electron",
|
"electron:start": "electron dist-electron",
|
||||||
"electron:build-linux": "electron-builder --linux AppImage",
|
"build:android": "rm -rf dist && npm run build:web && npm run build:capacitor && cd android && ./gradlew clean && ./gradlew assembleDebug && cd .. && npx cap sync android && npx capacitor-assets generate --android && npx cap open android",
|
||||||
"electron:build-linux-deb": "electron-builder --linux deb",
|
"electron:build-linux": "npm run build:electron && electron-builder --linux AppImage",
|
||||||
|
"electron:build-linux-deb": "npm run build:electron && electron-builder --linux deb",
|
||||||
|
"electron:build-linux-prod": "NODE_ENV=production npm run build:electron && electron-builder --linux AppImage",
|
||||||
"build:electron-prod": "NODE_ENV=production npm run build:electron",
|
"build:electron-prod": "NODE_ENV=production npm run build:electron",
|
||||||
"electron:build-linux-prod": "npm run build:electron-prod && electron-builder --linux AppImage",
|
"pywebview:dev": "vite build --config vite.config.pywebview.mts && .venv/bin/python src/pywebview/main.py",
|
||||||
"pywebview:dev": "vite build --mode pywebview && .venv/bin/python src/pywebview/main.py",
|
"pywebview:build": "vite build --config vite.config.pywebview.mts && .venv/bin/python src/pywebview/main.py",
|
||||||
"pywebview:build": "vite build --mode pywebview && .venv/bin/python src/pywebview/main.py",
|
"pywebview:package-linux": "vite build --mode pywebview --config vite.config.pywebview.mts && .venv/bin/python -m PyInstaller --name TimeSafari --add-data 'dist:www' src/pywebview/main.py",
|
||||||
"pywebview:package-linux": "vite build --mode pywebview && .venv/bin/python -m PyInstaller --name TimeSafari --add-data 'dist:www' src/pywebview/main.py",
|
"pywebview:package-win": "vite build --mode pywebview --config vite.config.pywebview.mts && .venv/Scripts/python -m PyInstaller --name TimeSafari --add-data 'dist;www' src/pywebview/main.py",
|
||||||
"pywebview:package-win": "vite build --mode pywebview && .venv/Scripts/python -m PyInstaller --name TimeSafari --add-data 'dist;www' src/pywebview/main.py",
|
"pywebview:package-mac": "vite build --mode pywebview --config vite.config.pywebview.mts && .venv/bin/python -m PyInstaller --name TimeSafari --add-data 'dist:www' src/pywebview/main.py",
|
||||||
"pywebview:package-mac": "vite build --mode pywebview && .venv/bin/python -m PyInstaller --name TimeSafari --add-data 'dist:www' src/pywebview/main.py"
|
"fastlane:ios:beta": "cd ios && fastlane beta",
|
||||||
|
"fastlane:ios:release": "cd ios && fastlane release",
|
||||||
|
"fastlane:android:beta": "cd android && fastlane beta",
|
||||||
|
"fastlane:android:release": "cd android && fastlane release"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor/android": "^6.2.0",
|
"@capacitor/android": "^6.2.0",
|
||||||
|
"@capacitor/app": "^6.0.0",
|
||||||
|
"@capacitor/camera": "^6.0.0",
|
||||||
"@capacitor/cli": "^6.2.0",
|
"@capacitor/cli": "^6.2.0",
|
||||||
"@capacitor/core": "^6.2.0",
|
"@capacitor/core": "^6.2.0",
|
||||||
|
"@capacitor/filesystem": "^6.0.0",
|
||||||
"@capacitor/ios": "^6.2.0",
|
"@capacitor/ios": "^6.2.0",
|
||||||
|
"@capacitor/share": "^6.0.3",
|
||||||
|
"@capawesome/capacitor-file-picker": "^6.2.0",
|
||||||
"@dicebear/collection": "^5.4.1",
|
"@dicebear/collection": "^5.4.1",
|
||||||
"@dicebear/core": "^5.4.1",
|
"@dicebear/core": "^5.4.1",
|
||||||
"@ethersproject/hdnode": "^5.7.0",
|
"@ethersproject/hdnode": "^5.7.0",
|
||||||
|
"@ethersproject/wallet": "^5.8.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.5.1",
|
"@fortawesome/fontawesome-svg-core": "^6.5.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.5.1",
|
"@fortawesome/free-solid-svg-icons": "^6.5.1",
|
||||||
"@fortawesome/vue-fontawesome": "^3.0.6",
|
"@fortawesome/vue-fontawesome": "^3.0.6",
|
||||||
@@ -64,9 +82,10 @@
|
|||||||
"cbor-x": "^1.5.9",
|
"cbor-x": "^1.5.9",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"dexie": "^3.2.7",
|
"dexie": "^3.2.7",
|
||||||
"dexie-export-import": "^4.1.1",
|
"dexie-export-import": "^4.1.4",
|
||||||
"did-jwt": "^7.4.7",
|
"did-jwt": "^7.4.7",
|
||||||
"did-resolver": "^4.1.0",
|
"did-resolver": "^4.1.0",
|
||||||
|
"dotenv": "^16.0.3",
|
||||||
"ethereum-cryptography": "^2.1.3",
|
"ethereum-cryptography": "^2.1.3",
|
||||||
"ethereumjs-util": "^7.1.5",
|
"ethereumjs-util": "^7.1.5",
|
||||||
"jdenticon": "^3.2.0",
|
"jdenticon": "^3.2.0",
|
||||||
@@ -89,24 +108,30 @@
|
|||||||
"reflect-metadata": "^0.1.14",
|
"reflect-metadata": "^0.1.14",
|
||||||
"register-service-worker": "^1.7.2",
|
"register-service-worker": "^1.7.2",
|
||||||
"simple-vue-camera": "^1.1.3",
|
"simple-vue-camera": "^1.1.3",
|
||||||
|
"sqlite3": "^5.1.7",
|
||||||
|
"stream-browserify": "^3.0.0",
|
||||||
"three": "^0.156.1",
|
"three": "^0.156.1",
|
||||||
"ua-parser-js": "^1.0.37",
|
"ua-parser-js": "^1.0.37",
|
||||||
"util": "^0.12.5",
|
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-axios": "^3.5.2",
|
"vue-axios": "^3.5.2",
|
||||||
"vue-facing-decorator": "^3.0.4",
|
"vue-facing-decorator": "^3.0.4",
|
||||||
"vue-picture-cropper": "^0.7.0",
|
"vue-picture-cropper": "^0.7.0",
|
||||||
"vue-qrcode-reader": "^5.5.3",
|
"vue-qrcode-reader": "^5.5.3",
|
||||||
"vue-router": "^4.5.0",
|
"vue-router": "^4.5.0",
|
||||||
"web-did-resolver": "^2.0.27"
|
"web-did-resolver": "^2.0.27",
|
||||||
|
"zod": "^3.24.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@capacitor/assets": "^3.0.5",
|
||||||
"@playwright/test": "^1.45.2",
|
"@playwright/test": "^1.45.2",
|
||||||
|
"@types/dom-webcodecs": "^0.1.7",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/leaflet": "^1.9.8",
|
"@types/leaflet": "^1.9.8",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@types/node": "^20.14.11",
|
"@types/node": "^20.14.11",
|
||||||
|
"@types/node-fetch": "^2.6.12",
|
||||||
"@types/ramda": "^0.29.11",
|
"@types/ramda": "^0.29.11",
|
||||||
|
"@types/sqlite3": "^3.1.11",
|
||||||
"@types/three": "^0.155.1",
|
"@types/three": "^0.155.1",
|
||||||
"@types/ua-parser-js": "^0.7.39",
|
"@types/ua-parser-js": "^0.7.39",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||||
@@ -116,11 +141,14 @@
|
|||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"concurrently": "^8.2.2",
|
"concurrently": "^8.2.2",
|
||||||
"electron": "^33.2.1",
|
"electron": "^33.2.1",
|
||||||
|
"electron-builder": "^25.1.8",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"eslint-plugin-vue": "^9.32.0",
|
"eslint-plugin-vue": "^9.32.0",
|
||||||
"fs-extra": "^11.3.0",
|
"fs-extra": "^11.3.0",
|
||||||
|
"markdownlint": "^0.37.4",
|
||||||
|
"markdownlint-cli": "^0.44.0",
|
||||||
"npm-check-updates": "^17.1.13",
|
"npm-check-updates": "^17.1.13",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.38",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
@@ -132,19 +160,20 @@
|
|||||||
},
|
},
|
||||||
"main": "./dist-electron/main.js",
|
"main": "./dist-electron/main.js",
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "org.timesafari.app",
|
"appId": "app.timesafari",
|
||||||
"productName": "TimeSafari",
|
"productName": "TimeSafari",
|
||||||
"directories": {
|
"directories": {
|
||||||
"output": "dist-electron-packages"
|
"output": "dist-electron-packages"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist-electron/**/*",
|
"dist-electron/**/*",
|
||||||
"src/electron/**/*"
|
"src/electron/**/*",
|
||||||
|
"main.js"
|
||||||
],
|
],
|
||||||
"extraResources": [
|
"extraResources": [
|
||||||
{
|
{
|
||||||
"from": "dist-electron/www",
|
"from": "dist-electron",
|
||||||
"to": "www"
|
"to": "."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"linux": {
|
"linux": {
|
||||||
|
|||||||
5
pkgx.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
dependencies:
|
||||||
|
- gradle
|
||||||
|
- java
|
||||||
|
|
||||||
|
# other dependencies are discovered via package.json & requirements.txt & Gemfile (I'm guessing).
|
||||||
@@ -46,21 +46,21 @@ export default defineConfig({
|
|||||||
|
|
||||||
/* Configure projects for major browsers */
|
/* Configure projects for major browsers */
|
||||||
projects: [
|
projects: [
|
||||||
{
|
// {
|
||||||
name: 'chromium-serial',
|
// name: 'chromium-serial',
|
||||||
testMatch: /.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts/,
|
// testMatch: /.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts/,
|
||||||
use: {
|
// use: {
|
||||||
...devices['Desktop Chrome'],
|
// ...devices['Desktop Chrome'],
|
||||||
permissions: ["clipboard-read"],
|
// permissions: ["clipboard-read"],
|
||||||
},
|
// },
|
||||||
workers: 1, // Force serial execution for problematic tests
|
// workers: 1, // Force serial execution for problematic tests
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
name: 'firefox-serial',
|
// name: 'firefox-serial',
|
||||||
testMatch: /.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts/,
|
// testMatch: /.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts/,
|
||||||
use: { ...devices['Desktop Firefox'] },
|
// use: { ...devices['Desktop Firefox'] },
|
||||||
workers: 1,
|
// workers: 1,
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
name: 'chromium',
|
name: 'chromium',
|
||||||
testMatch: /^(?!.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts).+\.spec\.ts$/,
|
testMatch: /^(?!.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts).+\.spec\.ts$/,
|
||||||
@@ -75,37 +75,27 @@ export default defineConfig({
|
|||||||
use: { ...devices['Desktop Firefox'] },
|
use: { ...devices['Desktop Firefox'] },
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
name: "webkit",
|
|
||||||
use: { ...devices["Desktop Safari"] },
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Test against mobile viewports. */
|
/* Test against mobile viewports. */
|
||||||
|
// {
|
||||||
{
|
// name: "Mobile Chrome",
|
||||||
name: "Mobile Chrome",
|
// use: { ...devices["Pixel 5"] },
|
||||||
use: { ...devices["Pixel 5"] },
|
// },
|
||||||
},
|
// {
|
||||||
{
|
// name: "Mobile Safari",
|
||||||
name: "Mobile Safari",
|
// use: { ...devices["iPhone 12"] },
|
||||||
use: { ...devices["iPhone 12"] },
|
// },
|
||||||
},
|
|
||||||
|
|
||||||
/* Test against branded browsers. */
|
/* Test against branded browsers. */
|
||||||
|
|
||||||
// {
|
// {
|
||||||
// name: 'Microsoft Edge',
|
// name: 'Microsoft Edge',
|
||||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||||
// },
|
// },
|
||||||
{
|
|
||||||
name: "Google Chrome",
|
|
||||||
use: { ...devices["Desktop Chrome"], channel: "chrome" },
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
|
|
||||||
/* Configure global timeout; default is 30000 milliseconds */
|
/* Configure global timeout; default is 30000 milliseconds */
|
||||||
// the image upload will often not succeed at 5 seconds
|
// the image upload will often not succeed in 5 seconds
|
||||||
timeout: 30000, // various tests fail at various times with 25000
|
// 33-record-gift-x10.spec.ts:90:5 > Record 9 new gifts will often not succeed in 30 seconds
|
||||||
|
timeout: 45000, // various tests fail at various times with 25000
|
||||||
|
|
||||||
/* Run your local dev server before starting the tests */
|
/* Run your local dev server before starting the tests */
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
eth_keys
|
||||||
pywebview
|
pywebview
|
||||||
pyinstaller>=6.12.0
|
pyinstaller>=6.12.0
|
||||||
# For development
|
# For development
|
||||||
|
|||||||
185
scripts/check-prerequisites.js
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Prerequisites checker for mobile development environment
|
||||||
|
*
|
||||||
|
* This script verifies that all necessary tools and configurations are in place
|
||||||
|
* for mobile app development, including both Android and iOS platforms.
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Validates development environment setup
|
||||||
|
* - Checks required command-line tools
|
||||||
|
* - Verifies Android SDK and device connectivity
|
||||||
|
* - Confirms iOS development tools and simulator status
|
||||||
|
*
|
||||||
|
* Prerequisites checked:
|
||||||
|
* - Node.js and npm installation
|
||||||
|
* - Gradle for Android builds
|
||||||
|
* - Xcode and command line tools for iOS
|
||||||
|
* - ANDROID_HOME environment variable
|
||||||
|
* - Android platform files
|
||||||
|
* - Connected Android devices/emulators
|
||||||
|
* - iOS platform files
|
||||||
|
* - Running iOS simulators
|
||||||
|
*
|
||||||
|
* Exit codes:
|
||||||
|
* - 0: All checks passed
|
||||||
|
* - 1: One or more checks failed
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Run directly
|
||||||
|
* node scripts/check-prerequisites.js
|
||||||
|
*
|
||||||
|
* // Run via npm script
|
||||||
|
* npm run test:prerequisites
|
||||||
|
*
|
||||||
|
* @author TimeSafari Team
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
const { existsSync } = require('fs');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a command-line tool is available by attempting to run its --version command
|
||||||
|
*
|
||||||
|
* @param {string} command - The command to check (e.g., 'node', 'npm', 'gradle')
|
||||||
|
* @param {string} errorMessage - The error message to display if the command is not available
|
||||||
|
* @returns {boolean} - True if the command exists and is executable, false otherwise
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* checkCommand('node', 'Node.js is required')
|
||||||
|
* // Returns true if node is available, false otherwise
|
||||||
|
*/
|
||||||
|
function checkCommand(command, errorMessage) {
|
||||||
|
try {
|
||||||
|
execSync(command + ' --version', { stdio: 'ignore' });
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`❌ ${errorMessage}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies Android development environment setup
|
||||||
|
*
|
||||||
|
* Checks for:
|
||||||
|
* 1. ANDROID_HOME environment variable
|
||||||
|
* 2. Android platform files in project
|
||||||
|
* 3. Connected Android devices or running emulators
|
||||||
|
*
|
||||||
|
* @returns {boolean} - True if Android setup is complete and valid, false otherwise
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* if (!checkAndroidSetup()) {
|
||||||
|
* console.error('Android prerequisites not met');
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
function checkAndroidSetup() {
|
||||||
|
// Check ANDROID_HOME environment variable
|
||||||
|
// This is required for Android SDK tools access
|
||||||
|
if (!process.env.ANDROID_HOME) {
|
||||||
|
console.error('❌ ANDROID_HOME environment variable not set');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if Android platform was added to the project
|
||||||
|
// The 'android' directory should exist if platform was added via 'npx cap add android'
|
||||||
|
if (!existsSync('android')) {
|
||||||
|
console.error('❌ Android platform not added. Run: npx cap add android');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for connected devices or running emulators
|
||||||
|
// Uses ADB (Android Debug Bridge) to list connected devices
|
||||||
|
try {
|
||||||
|
const devices = execSync('adb devices').toString();
|
||||||
|
// Parse ADB output - looking for lines ending with 'device' (not 'offline' or 'unauthorized')
|
||||||
|
if (!devices.split('\n').slice(1).some(line => line.includes('device'))) {
|
||||||
|
console.error('❌ No Android devices connected');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('❌ ADB not available');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies iOS development environment setup
|
||||||
|
*
|
||||||
|
* Checks for:
|
||||||
|
* 1. iOS platform files in project
|
||||||
|
* 2. Running iOS simulators
|
||||||
|
* 3. Xcode command line tools availability
|
||||||
|
*
|
||||||
|
* @returns {boolean} - True if iOS setup is complete and valid, false otherwise
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* if (!checkIosSetup()) {
|
||||||
|
* console.error('iOS prerequisites not met');
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
function checkIosSetup() {
|
||||||
|
// Check if iOS platform was added to the project
|
||||||
|
// The 'ios' directory should exist if platform was added via 'npx cap add ios'
|
||||||
|
if (!existsSync('ios')) {
|
||||||
|
console.error('❌ iOS platform not added. Run: npx cap add ios');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for available and running iOS simulators
|
||||||
|
// Uses xcrun simctl to list simulator devices
|
||||||
|
try {
|
||||||
|
const simulators = execSync('xcrun simctl list devices available').toString();
|
||||||
|
if (!simulators.includes('Booted')) {
|
||||||
|
console.error('❌ No iOS simulator running');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('❌ Xcode command line tools not available');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function to check all prerequisites for mobile development
|
||||||
|
*
|
||||||
|
* Verifies:
|
||||||
|
* 1. Required command line tools (node, npm, gradle, xcodebuild)
|
||||||
|
* 2. Android development setup
|
||||||
|
* 3. iOS development setup
|
||||||
|
*
|
||||||
|
* Exits with code 1 if any checks fail
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Run from package.json script:
|
||||||
|
* // "test:prerequisites": "node scripts/check-prerequisites.js"
|
||||||
|
*/
|
||||||
|
function main() {
|
||||||
|
let success = true;
|
||||||
|
|
||||||
|
// Check required command line tools
|
||||||
|
// These are essential for building and testing the application
|
||||||
|
success &= checkCommand('node', 'Node.js is required');
|
||||||
|
success &= checkCommand('npm', 'npm is required');
|
||||||
|
success &= checkCommand('gradle', 'Gradle is required for Android builds');
|
||||||
|
success &= checkCommand('xcodebuild', 'Xcode is required for iOS builds');
|
||||||
|
|
||||||
|
// Check platform-specific development environments
|
||||||
|
success &= checkAndroidSetup();
|
||||||
|
success &= checkIosSetup();
|
||||||
|
|
||||||
|
// Exit with error if any checks failed
|
||||||
|
if (!success) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ All prerequisites met!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the checks
|
||||||
|
main();
|
||||||
22
scripts/copy-web-assets.sh
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Clean the public directory
|
||||||
|
rm -rf android/app/src/main/assets/public/*
|
||||||
|
|
||||||
|
# Copy web assets
|
||||||
|
cp -r dist/* android/app/src/main/assets/public/
|
||||||
|
|
||||||
|
# Ensure the directory structure exists
|
||||||
|
mkdir -p android/app/src/main/assets/public/assets
|
||||||
|
|
||||||
|
# Copy the main index file
|
||||||
|
cp dist/index.html android/app/src/main/assets/public/
|
||||||
|
|
||||||
|
# Copy all assets
|
||||||
|
cp -r dist/assets/* android/app/src/main/assets/public/assets/
|
||||||
|
|
||||||
|
# Copy other necessary files
|
||||||
|
cp dist/favicon.ico android/app/src/main/assets/public/
|
||||||
|
cp dist/robots.txt android/app/src/main/assets/public/
|
||||||
|
|
||||||
|
echo "Web assets copied successfully!"
|
||||||
22
scripts/generate-icons.sh
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Create directories if they don't exist
|
||||||
|
mkdir -p android/app/src/main/res/mipmap-mdpi
|
||||||
|
mkdir -p android/app/src/main/res/mipmap-hdpi
|
||||||
|
mkdir -p android/app/src/main/res/mipmap-xhdpi
|
||||||
|
mkdir -p android/app/src/main/res/mipmap-xxhdpi
|
||||||
|
mkdir -p android/app/src/main/res/mipmap-xxxhdpi
|
||||||
|
|
||||||
|
# Generate placeholder icons using ImageMagick
|
||||||
|
convert -size 48x48 xc:blue -gravity center -pointsize 20 -fill white -annotate 0 "TS" android/app/src/main/res/mipmap-mdpi/ic_launcher.png
|
||||||
|
convert -size 72x72 xc:blue -gravity center -pointsize 30 -fill white -annotate 0 "TS" android/app/src/main/res/mipmap-hdpi/ic_launcher.png
|
||||||
|
convert -size 96x96 xc:blue -gravity center -pointsize 40 -fill white -annotate 0 "TS" android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
|
||||||
|
convert -size 144x144 xc:blue -gravity center -pointsize 60 -fill white -annotate 0 "TS" android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
|
||||||
|
convert -size 192x192 xc:blue -gravity center -pointsize 80 -fill white -annotate 0 "TS" android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
|
||||||
|
|
||||||
|
# Copy to round versions
|
||||||
|
cp android/app/src/main/res/mipmap-mdpi/ic_launcher.png android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
|
||||||
|
cp android/app/src/main/res/mipmap-hdpi/ic_launcher.png android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
|
||||||
|
cp android/app/src/main/res/mipmap-xhdpi/ic_launcher.png android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
|
||||||
|
cp android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
|
||||||
|
cp android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
|
||||||
132
scripts/run-available-mobile-tests.js
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Runs mobile tests based on available platforms and devices
|
||||||
|
*
|
||||||
|
* This script intelligently detects available mobile platforms and their
|
||||||
|
* associated devices/simulators, then runs tests only for the available
|
||||||
|
* configurations. This allows for flexible testing across different
|
||||||
|
* development environments without failing when a platform is unavailable.
|
||||||
|
*
|
||||||
|
* Platform detection:
|
||||||
|
* - Android: Checks for SDK and connected devices/emulators
|
||||||
|
* - iOS: Checks for macOS, Xcode, and running simulators
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Smart platform detection
|
||||||
|
* - Graceful handling of unavailable platforms
|
||||||
|
* - Clear logging of test execution
|
||||||
|
* - Comprehensive error reporting
|
||||||
|
*
|
||||||
|
* Exit codes:
|
||||||
|
* - 0: Tests completed successfully on available platforms
|
||||||
|
* - 1: Tests failed or no platforms available
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Run directly
|
||||||
|
* node scripts/run-available-mobile-tests.js
|
||||||
|
*
|
||||||
|
* // Run via npm script
|
||||||
|
* npm run test:mobile:available
|
||||||
|
*
|
||||||
|
* @requires child_process
|
||||||
|
* @requires fs
|
||||||
|
*
|
||||||
|
* @author TimeSafari Team
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
const { existsSync } = require('fs');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes mobile tests on available platforms
|
||||||
|
*
|
||||||
|
* This function performs the following steps:
|
||||||
|
* 1. Checks Android environment and device availability
|
||||||
|
* 2. Checks iOS environment and simulator availability (on macOS)
|
||||||
|
* 3. Runs tests on available platforms
|
||||||
|
* 4. Reports results and handles errors
|
||||||
|
*
|
||||||
|
* Platform-specific checks:
|
||||||
|
* Android:
|
||||||
|
* - ANDROID_HOME environment variable
|
||||||
|
* - Android platform files existence
|
||||||
|
* - Connected devices via ADB
|
||||||
|
*
|
||||||
|
* iOS:
|
||||||
|
* - macOS operating system
|
||||||
|
* - iOS platform files existence
|
||||||
|
* - Running simulators via xcrun
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @throws {Error} If tests fail or no platforms are available
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* runAvailableMobileTests().catch(error => {
|
||||||
|
* console.error('Test execution failed:', error);
|
||||||
|
* process.exit(1);
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
async function runAvailableMobileTests() {
|
||||||
|
try {
|
||||||
|
// Check Android availability
|
||||||
|
// Requires both SDK (ANDROID_HOME) and platform files
|
||||||
|
const androidAvailable = existsSync('android') && process.env.ANDROID_HOME;
|
||||||
|
let androidDeviceAvailable = false;
|
||||||
|
|
||||||
|
if (androidAvailable) {
|
||||||
|
try {
|
||||||
|
// Check for connected devices using ADB
|
||||||
|
const devices = execSync('adb devices').toString();
|
||||||
|
// Parse ADB output for actually connected devices
|
||||||
|
// Filters out unauthorized or offline devices
|
||||||
|
androidDeviceAvailable = devices.split('\n').slice(1).some(line => line.includes('device'));
|
||||||
|
} catch (e) {
|
||||||
|
console.log('⚠️ Android SDK available but no devices connected');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check iOS availability
|
||||||
|
// Only possible on macOS with Xcode installed
|
||||||
|
const iosAvailable = process.platform === 'darwin' && existsSync('ios');
|
||||||
|
let iosSimulatorAvailable = false;
|
||||||
|
|
||||||
|
if (iosAvailable) {
|
||||||
|
try {
|
||||||
|
// Check for running simulators using xcrun
|
||||||
|
const simulators = execSync('xcrun simctl list devices available').toString();
|
||||||
|
// Look for 'Booted' state in simulator list
|
||||||
|
iosSimulatorAvailable = simulators.includes('Booted');
|
||||||
|
} catch (e) {
|
||||||
|
console.log('⚠️ iOS platform available but no simulator running');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute tests for available platforms
|
||||||
|
if (androidDeviceAvailable) {
|
||||||
|
console.log('🤖 Running Android tests...');
|
||||||
|
// Run Android tests via npm script
|
||||||
|
execSync('npm run test:android', { stdio: 'inherit' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iosSimulatorAvailable) {
|
||||||
|
console.log('🍎 Running iOS tests...');
|
||||||
|
// Run iOS tests via npm script
|
||||||
|
execSync('npm run test:ios', { stdio: 'inherit' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error if no platforms are available for testing
|
||||||
|
if (!androidDeviceAvailable && !iosSimulatorAvailable) {
|
||||||
|
console.error('❌ No mobile platforms available for testing');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ Available mobile tests completed successfully');
|
||||||
|
} catch (error) {
|
||||||
|
// Handle any errors during test execution
|
||||||
|
console.error('❌ Mobile tests failed:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the test runner
|
||||||
|
runAvailableMobileTests();
|
||||||
451
scripts/test-android.js
Normal file
@@ -0,0 +1,451 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Android test runner for Capacitor-based mobile app
|
||||||
|
*
|
||||||
|
* This script handles the build, installation, and testing of the Android app.
|
||||||
|
* It ensures the app is properly synced, built, installed on a device/emulator,
|
||||||
|
* and runs the test suite.
|
||||||
|
*
|
||||||
|
* Process flow:
|
||||||
|
* 1. Sync Capacitor project with latest web build
|
||||||
|
* 2. Build debug APK
|
||||||
|
* 3. Install APK on connected device/emulator
|
||||||
|
* 4. Run instrumented tests
|
||||||
|
*
|
||||||
|
* Prerequisites:
|
||||||
|
* - Android SDK installed and ANDROID_HOME set
|
||||||
|
* - Gradle installed and in PATH
|
||||||
|
* - Connected Android device or running emulator
|
||||||
|
* - Capacitor Android platform added to project
|
||||||
|
*
|
||||||
|
* Exit codes:
|
||||||
|
* - 0: Tests completed successfully
|
||||||
|
* - 1: Build, installation, or test failure
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Run directly
|
||||||
|
* node scripts/test-android.js
|
||||||
|
*
|
||||||
|
* // Run via npm script
|
||||||
|
* npm run test:android
|
||||||
|
*
|
||||||
|
* @requires child_process
|
||||||
|
* @requires path
|
||||||
|
* @requires readline
|
||||||
|
*
|
||||||
|
* @author TimeSafari Team
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
const { join } = require('path');
|
||||||
|
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 = () => {
|
||||||
|
const now = new Date();
|
||||||
|
const date = now.toISOString().split('T')[0];
|
||||||
|
const time = now.toTimeString().split(' ')[0].replace(/:/g, '');
|
||||||
|
return `build_logs/android-build-${date}-${time}.log`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create logger function
|
||||||
|
const createLogger = (logFile) => {
|
||||||
|
return (message) => {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
const logMessage = `[${timestamp}] ${message}\n`;
|
||||||
|
console.log(message);
|
||||||
|
appendFileSync(logFile, logMessage);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check for connected Android devices
|
||||||
|
const checkConnectedDevices = async (log) => {
|
||||||
|
log('🔍 Checking for Android devices...');
|
||||||
|
const devices = execSync('adb devices').toString();
|
||||||
|
const connectedDevices = devices.split('\n')
|
||||||
|
.slice(1)
|
||||||
|
.filter(line => line.includes('device'))
|
||||||
|
.map(line => line.split('\t')[0])
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if (connectedDevices.length === 0) {
|
||||||
|
throw new Error('No Android devices or emulators connected. Please connect a device or start an emulator.');
|
||||||
|
}
|
||||||
|
|
||||||
|
log(`📱 Found ${connectedDevices.length} device(s): ${connectedDevices.join(', ')}`);
|
||||||
|
return connectedDevices;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Verify Java installation
|
||||||
|
const verifyJavaInstallation = (log) => {
|
||||||
|
log('🔍 Checking Java...');
|
||||||
|
const javaHome = process.env.JAVA_HOME;
|
||||||
|
if (!existsSync(javaHome)) {
|
||||||
|
throw new Error(`Required Java not found at ${javaHome}. Please install OpenJDK.`);
|
||||||
|
}
|
||||||
|
log('✅ Java found');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
// 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) {
|
||||||
|
log(`❌ Failed to generate test data: ${error.message}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse shell environment file
|
||||||
|
const parseEnvFile = (filePath) => {
|
||||||
|
const content = readFileSync(filePath, 'utf8');
|
||||||
|
const env = {};
|
||||||
|
content.split('\n').forEach(line => {
|
||||||
|
const match = line.match(/^export\s+(\w+)="(.+)"$/);
|
||||||
|
if (match) {
|
||||||
|
env[match[1]] = match[2];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return env;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run individual deeplink test
|
||||||
|
const executeDeeplink = async (url, description, log) => {
|
||||||
|
log(`\n🔗 Testing deeplink: ${description}`);
|
||||||
|
log(`URL: ${url}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Stop the app before executing the deep link
|
||||||
|
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 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}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run all deeplink tests
|
||||||
|
const runDeeplinkTests = async (log) => {
|
||||||
|
log('🔗 Starting deeplink tests...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Load test data
|
||||||
|
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 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'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Show test plan
|
||||||
|
log('\n📋 Test Plan:');
|
||||||
|
deeplinkTests.forEach((test, i) => {
|
||||||
|
log(`${i + 1}. ${test.description}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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('\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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build web assets
|
||||||
|
const buildWebAssets = async (log) => {
|
||||||
|
log('🌐 Building web assets...');
|
||||||
|
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');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Configure Android project
|
||||||
|
const configureAndroidProject = async (log) => {
|
||||||
|
log('📱 Syncing Capacitor project...');
|
||||||
|
execSync('npx cap sync android', { stdio: 'inherit' });
|
||||||
|
log('✅ Capacitor sync completed');
|
||||||
|
|
||||||
|
log('⚙️ Configuring Gradle properties...');
|
||||||
|
const gradleProps = 'android/gradle.properties';
|
||||||
|
|
||||||
|
// 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...');
|
||||||
|
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
|
||||||
|
const runAndroidApp = async (log, env) => {
|
||||||
|
log('📱 Running app on device...');
|
||||||
|
execSync('npx cap run android', { stdio: 'inherit', env });
|
||||||
|
log('✅ App launched successfully');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the complete Android test suite including build, installation, and testing
|
||||||
|
*
|
||||||
|
* The function performs the following steps:
|
||||||
|
* 1. Checks for connected devices/emulators
|
||||||
|
* 2. Ensures correct Java version is used
|
||||||
|
* 3. Checks if app is already installed
|
||||||
|
* 4. Syncs the Capacitor project with latest build
|
||||||
|
* 5. Builds and runs instrumented Android tests
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @throws {Error} If any step in the build or test process fails
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* runAndroidTests().catch(error => {
|
||||||
|
* console.error('Test execution failed:', error);
|
||||||
|
* process.exit(1);
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
async function runAndroidTests() {
|
||||||
|
// Create build_logs directory if it doesn't exist
|
||||||
|
if (!existsSync('build_logs')) {
|
||||||
|
mkdirSync('build_logs');
|
||||||
|
}
|
||||||
|
|
||||||
|
const logFile = getLogFileName();
|
||||||
|
const log = createLogger(logFile);
|
||||||
|
|
||||||
|
try {
|
||||||
|
log('🚀 Starting Android build and test process...');
|
||||||
|
|
||||||
|
// Generate test data first
|
||||||
|
await generateTestData(log);
|
||||||
|
|
||||||
|
await checkConnectedDevices(log);
|
||||||
|
await verifyJavaInstallation(log);
|
||||||
|
await buildWebAssets(log);
|
||||||
|
await configureAndroidProject(log);
|
||||||
|
const env = process.env;
|
||||||
|
await buildAndTestAndroid(log, env);
|
||||||
|
await runAndroidApp(log, env);
|
||||||
|
|
||||||
|
// Run deeplink tests after app is installed
|
||||||
|
await runDeeplinkTests(log);
|
||||||
|
|
||||||
|
log('🎉 Android build and test process completed successfully');
|
||||||
|
log(`📝 Full build log available at: ${logFile}`);
|
||||||
|
} catch (error) {
|
||||||
|
log(`❌ Android tests failed: ${error.message}`);
|
||||||
|
log(`📝 Check build log for details: ${logFile}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the test suite
|
||||||
|
runAndroidTests();
|
||||||
|
|
||||||
|
// Add cleanup handler for SIGINT
|
||||||
|
process.on('SIGINT', () => {
|
||||||
|
rl.close();
|
||||||
|
process.exit();
|
||||||
|
});
|
||||||
939
scripts/test-ios.js
Normal file
@@ -0,0 +1,939 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview iOS test runner for Capacitor-based mobile app
|
||||||
|
*
|
||||||
|
* This script handles the build and testing of the iOS app using Xcode's
|
||||||
|
* command-line tools. It ensures the app is properly synced with the latest
|
||||||
|
* web build and runs the test suite on a specified iOS simulator.
|
||||||
|
*
|
||||||
|
* Process flow:
|
||||||
|
* 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
|
||||||
|
* - Xcode installed with command line tools
|
||||||
|
* - iOS simulator available
|
||||||
|
* - Capacitor iOS platform added to project
|
||||||
|
* - Valid iOS development certificates
|
||||||
|
*
|
||||||
|
* Exit codes:
|
||||||
|
* - 0: Tests completed successfully
|
||||||
|
* - 1: Build or test failure
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Run directly
|
||||||
|
* node scripts/test-ios.js
|
||||||
|
*
|
||||||
|
* // Run via npm script
|
||||||
|
* npm run test:ios
|
||||||
|
*
|
||||||
|
* @requires child_process
|
||||||
|
* @requires path
|
||||||
|
* @requires fs
|
||||||
|
*
|
||||||
|
* @author TimeSafari Team
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
const { join } = require('path');
|
||||||
|
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 = () => {
|
||||||
|
const now = new Date();
|
||||||
|
const date = now.toISOString().split('T')[0];
|
||||||
|
const time = now.toTimeString().split(' ')[0].replace(/:/g, '');
|
||||||
|
return `build_logs/ios-build-${date}-${time}.log`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create logger function
|
||||||
|
const createLogger = (logFile) => {
|
||||||
|
return (message) => {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
const logMessage = `[${timestamp}] ${message}\n`;
|
||||||
|
console.log(message);
|
||||||
|
appendFileSync(logFile, logMessage);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.build.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...');
|
||||||
|
const simulatorsOutput = execSync('xcrun simctl list devices available -j').toString();
|
||||||
|
const simulatorsData = JSON.parse(simulatorsOutput);
|
||||||
|
|
||||||
|
// Get all available devices/simulators with their UDIDs
|
||||||
|
const allDevices = [];
|
||||||
|
|
||||||
|
// Process all runtime groups (iOS versions)
|
||||||
|
Object.entries(simulatorsData.devices).forEach(([runtime, devices]) => {
|
||||||
|
devices.forEach(device => {
|
||||||
|
allDevices.push({
|
||||||
|
name: device.name,
|
||||||
|
udid: device.udid,
|
||||||
|
state: device.state,
|
||||||
|
runtime: runtime,
|
||||||
|
isIphone: device.name.includes('iPhone'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check for booted simulators first
|
||||||
|
const bootedDevices = allDevices.filter(device => device.state === 'Booted');
|
||||||
|
|
||||||
|
if (bootedDevices.length > 0) {
|
||||||
|
log(`📱 Found ${bootedDevices.length} running simulator(s): ${bootedDevices.map(d => d.name).join(', ')}`);
|
||||||
|
return bootedDevices;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No booted devices found, try to boot one
|
||||||
|
log('⚠️ No running iOS simulator found. Attempting to boot one...');
|
||||||
|
|
||||||
|
// Prefer iPhone devices, especially newer models
|
||||||
|
const preferredDevices = [
|
||||||
|
'iPhone 15', 'iPhone 14', 'iPhone 13', 'iPhone 12', 'iPhone', // Prefer newer iPhones first
|
||||||
|
'iPad' // Then iPads if no iPhones available
|
||||||
|
];
|
||||||
|
|
||||||
|
let deviceToLaunch = null;
|
||||||
|
|
||||||
|
// Try to find a device from our preferred list
|
||||||
|
for (const preferredName of preferredDevices) {
|
||||||
|
const matchingDevices = allDevices.filter(device =>
|
||||||
|
device.name.includes(preferredName) && device.state === 'Shutdown');
|
||||||
|
|
||||||
|
if (matchingDevices.length > 0) {
|
||||||
|
// Sort by runtime to prefer newer iOS versions
|
||||||
|
matchingDevices.sort((a, b) => b.runtime.localeCompare(a.runtime));
|
||||||
|
deviceToLaunch = matchingDevices[0];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no preferred device found, take any available device
|
||||||
|
if (!deviceToLaunch && allDevices.length > 0) {
|
||||||
|
const availableDevices = allDevices.filter(device => device.state === 'Shutdown');
|
||||||
|
if (availableDevices.length > 0) {
|
||||||
|
deviceToLaunch = availableDevices[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!deviceToLaunch) {
|
||||||
|
throw new Error('No available iOS simulators found. Please create a simulator in Xcode first.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boot the selected simulator
|
||||||
|
log(`🚀 Booting iOS simulator: ${deviceToLaunch.name} (${deviceToLaunch.runtime})`);
|
||||||
|
execSync(`xcrun simctl boot ${deviceToLaunch.udid}`);
|
||||||
|
|
||||||
|
// Wait for simulator to fully boot
|
||||||
|
log('⏳ Waiting for simulator to boot completely...');
|
||||||
|
// Give the simulator time to fully boot before proceeding
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 10000));
|
||||||
|
|
||||||
|
log(`✅ Successfully booted simulator: ${deviceToLaunch.name}`);
|
||||||
|
|
||||||
|
return [{ name: deviceToLaunch.name, udid: deviceToLaunch.udid }];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Verify Xcode installation
|
||||||
|
const verifyXcodeInstallation = (log) => {
|
||||||
|
log('🔍 Checking Xcode installation...');
|
||||||
|
try {
|
||||||
|
execSync('xcode-select -p');
|
||||||
|
log('✅ Xcode command line tools found');
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Xcode command line tools not found. Please install Xcode first.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate test data using generate_data.ts
|
||||||
|
const generateTestData = async (log) => {
|
||||||
|
log('\n🔍 DEBUG: Starting test data generation...');
|
||||||
|
|
||||||
|
// Check directory structure
|
||||||
|
log('📁 Current directory:', process.cwd());
|
||||||
|
log('📁 Directory contents:', require('fs').readdirSync('.'));
|
||||||
|
|
||||||
|
if (!existsSync('.generated')) {
|
||||||
|
log('📁 Creating .generated directory');
|
||||||
|
mkdirSync('.generated', { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
log('🔄 Attempting to run generate_data.ts...');
|
||||||
|
execSync('npx ts-node test-scripts/generate_data.ts', { stdio: 'inherit' });
|
||||||
|
log('✅ Test data generation completed');
|
||||||
|
|
||||||
|
// Verify and log generated files content
|
||||||
|
const requiredFiles = [
|
||||||
|
'.generated/test-env.json',
|
||||||
|
'.generated/claim_details.json',
|
||||||
|
'.generated/contacts.json'
|
||||||
|
];
|
||||||
|
|
||||||
|
log('\n📝 Verifying generated files:');
|
||||||
|
for (const file of requiredFiles) {
|
||||||
|
if (!existsSync(file)) {
|
||||||
|
log(`❌ Missing file: ${file}`);
|
||||||
|
} else {
|
||||||
|
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(`\n⚠️ Test data generation failed: ${error.message}`);
|
||||||
|
log('⚠️ Creating fallback test data...');
|
||||||
|
|
||||||
|
// Create fallback data with detailed logging
|
||||||
|
const fallbackTestEnv = {
|
||||||
|
"CONTACT1_DID": "did:ethr:0x35A71Ac3fA0A4D5a4903f10F0f7A3ac4034FaB5B",
|
||||||
|
"APP_URL": "https://app.timesafari.example"
|
||||||
|
};
|
||||||
|
|
||||||
|
const fallbackContacts = [
|
||||||
|
{
|
||||||
|
"id": "contact1",
|
||||||
|
"name": "Test Contact",
|
||||||
|
"did": "did:ethr:0x35A71Ac3fA0A4D5a4903f10F0f7A3ac4034FaB5B"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
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/contacts.json', JSON.stringify(fallbackContacts, null, 2));
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build web assets
|
||||||
|
const buildWebAssets = async (log) => {
|
||||||
|
log('🌐 Building web assets...');
|
||||||
|
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');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Configure iOS project
|
||||||
|
const configureIosProject = async (log) => {
|
||||||
|
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, 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...');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
const buildAndTestIos = async (log, simulator) => {
|
||||||
|
const simulatorName = simulator[0].name;
|
||||||
|
log('🏗️ Building iOS project...', simulator[0]);
|
||||||
|
execSync('cd ios/App && xcodebuild clean -workspace App.xcworkspace -scheme App', { stdio: 'inherit' });
|
||||||
|
log('✅ Xcode clean completed');
|
||||||
|
|
||||||
|
log(`🏗️ Building for simulator: ${simulatorName}`);
|
||||||
|
execSync(`cd ios/App && xcodebuild build -workspace App.xcworkspace -scheme App -destination "platform=iOS Simulator,OS=17.2,name=${simulatorName}"`, { stdio: 'inherit' });
|
||||||
|
log('✅ Xcode build completed');
|
||||||
|
|
||||||
|
// Check if the project is configured for testing by querying the scheme capabilities
|
||||||
|
try {
|
||||||
|
log(`🧪 Checking if scheme is configured for testing`);
|
||||||
|
const schemeInfo = execSync(`cd ios/App && xcodebuild -scheme App -showBuildSettings | grep TEST`).toString();
|
||||||
|
|
||||||
|
if (schemeInfo.includes('ENABLE_TESTABILITY = YES')) {
|
||||||
|
log(`🧪 Attempting to run tests on simulator: ${simulatorName}`);
|
||||||
|
try {
|
||||||
|
execSync(`cd ios/App && xcodebuild test -workspace App.xcworkspace -scheme App -destination "platform=iOS Simulator,name=${simulatorName}"`, { stdio: 'inherit' });
|
||||||
|
log('✅ iOS tests completed successfully');
|
||||||
|
} catch (testError) {
|
||||||
|
log(`⚠️ Tests failed or scheme not properly configured for testing: ${testError.message}`);
|
||||||
|
log('⚠️ This is normal if no test targets have been added to the project');
|
||||||
|
log('⚠️ Skipping test step and continuing with the app launch');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log('⚠️ Project does not have testing enabled in build settings');
|
||||||
|
log('⚠️ Skipping test step and continuing with the app launch');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
log('⚠️ Unable to determine if testing is configured');
|
||||||
|
log('⚠️ Skipping test step and continuing with the app launch');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the app
|
||||||
|
const runIosApp = async (log, simulator) => {
|
||||||
|
const simulatorName = simulator[0].name;
|
||||||
|
const simulatorUdid = simulator[0].udid;
|
||||||
|
|
||||||
|
log(`📱 Running app in simulator: ${simulatorName} (${simulatorUdid})...`);
|
||||||
|
// Use the --target parameter to specify the device directly, avoiding the UI prompt
|
||||||
|
execSync(`npx cap run ios --target="${simulatorUdid}"`, { stdio: 'inherit' });
|
||||||
|
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
|
||||||
|
*
|
||||||
|
* @param {function} log - Logging function
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
const runDeeplinkTests = async (log) => {
|
||||||
|
log('\n=== Starting Deeplink Tests ===');
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
// 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('🚀 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++;
|
||||||
|
|
||||||
|
// 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 || '';
|
||||||
|
|
||||||
|
// Handle specific error for URL scheme not registered
|
||||||
|
if (errorMessage.includes('OSStatus error -10814') || errorMessage.includes('NSOSStatusErrorDomain, code=-10814')) {
|
||||||
|
log(`⚠️ URL scheme not properly handled: ${test.description}`);
|
||||||
|
testsSkipped++;
|
||||||
|
} else {
|
||||||
|
log(`⚠️ Failed to execute deeplink test: ${test.description}`);
|
||||||
|
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('\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. 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');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check and register URL scheme if needed
|
||||||
|
const checkAndRegisterUrlScheme = (log) => {
|
||||||
|
log('🔍 Checking if URL scheme is registered in Info.plist...');
|
||||||
|
|
||||||
|
const infoPlistPath = 'ios/App/App/Info.plist';
|
||||||
|
|
||||||
|
// Check if Info.plist exists
|
||||||
|
if (!existsSync(infoPlistPath)) {
|
||||||
|
log('⚠️ Info.plist not found at: ' + infoPlistPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read Info.plist content
|
||||||
|
const infoPlistContent = readFileSync(infoPlistPath, 'utf8');
|
||||||
|
|
||||||
|
// Check if URL scheme is already registered
|
||||||
|
if (infoPlistContent.includes('<string>timesafari</string>')) {
|
||||||
|
log('✅ URL scheme "timesafari://" is already registered in Info.plist');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
log('⚠️ URL scheme "timesafari://" is not registered in Info.plist');
|
||||||
|
log('⚠️ Attempting to register the URL scheme automatically...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Look for the closing dict tag to insert our URL types
|
||||||
|
const closingDictIndex = infoPlistContent.lastIndexOf('</dict>');
|
||||||
|
if (closingDictIndex === -1) {
|
||||||
|
log('⚠️ Could not find closing dict tag in Info.plist');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create URL types entry
|
||||||
|
const urlTypesEntry = `
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>app.timesafari</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>timesafari</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>`;
|
||||||
|
|
||||||
|
// Insert URL types entry before closing dict
|
||||||
|
const updatedPlistContent =
|
||||||
|
infoPlistContent.substring(0, closingDictIndex) +
|
||||||
|
urlTypesEntry +
|
||||||
|
infoPlistContent.substring(closingDictIndex);
|
||||||
|
|
||||||
|
// Write updated content back to Info.plist
|
||||||
|
const { writeFileSync } = require('fs');
|
||||||
|
writeFileSync(infoPlistPath, updatedPlistContent, 'utf8');
|
||||||
|
|
||||||
|
log('✅ URL scheme "timesafari://" registered in Info.plist');
|
||||||
|
log('⚠️ You will need to rebuild the app for changes to take effect');
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
log(`⚠️ Failed to register URL scheme: ${error.message}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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. 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.
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @throws {Error} If any step in the build process fails
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* runIosTests().catch(error => {
|
||||||
|
* console.error('Test execution failed:', error);
|
||||||
|
* process.exit(1);
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
async function runIosTests() {
|
||||||
|
// Create build_logs directory if it doesn't exist
|
||||||
|
if (!existsSync('build_logs')) {
|
||||||
|
mkdirSync('build_logs');
|
||||||
|
}
|
||||||
|
|
||||||
|
const logFile = getLogFileName();
|
||||||
|
const log = createLogger(logFile);
|
||||||
|
|
||||||
|
try {
|
||||||
|
log('🚀 Starting iOS build and test process...');
|
||||||
|
|
||||||
|
// 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
|
||||||
|
verifyXcodeInstallation(log);
|
||||||
|
|
||||||
|
// Check for simulator or boot one if needed
|
||||||
|
const simulator = await checkSimulator(log);
|
||||||
|
|
||||||
|
// Configure iOS project
|
||||||
|
await configureIosProject(log);
|
||||||
|
|
||||||
|
// Build and test using the selected simulator
|
||||||
|
await buildAndTestIos(log, simulator);
|
||||||
|
|
||||||
|
// Run the app in the simulator
|
||||||
|
await runIosApp(log, simulator);
|
||||||
|
|
||||||
|
// Run deeplink tests after app is installed
|
||||||
|
await runDeeplinkTests(log);
|
||||||
|
|
||||||
|
log('🎉 iOS build and test process completed successfully');
|
||||||
|
log(`📝 Full build log available at: ${logFile}`);
|
||||||
|
} catch (error) {
|
||||||
|
log(`❌ iOS tests failed: ${error.message}`);
|
||||||
|
log(`📝 Check build log for details: ${logFile}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the test suite
|
||||||
|
runIosTests();
|
||||||
118
src/App.vue
@@ -40,7 +40,10 @@
|
|||||||
<div
|
<div
|
||||||
class="flex items-center justify-center w-12 bg-slate-600 text-slate-100"
|
class="flex items-center justify-center w-12 bg-slate-600 text-slate-100"
|
||||||
>
|
>
|
||||||
<fa icon="circle-info" class="fa-fw fa-xl"></fa>
|
<font-awesome
|
||||||
|
icon="circle-info"
|
||||||
|
class="fa-fw fa-xl"
|
||||||
|
></font-awesome>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative w-full pl-4 pr-8 py-2 text-slate-900">
|
<div class="relative w-full pl-4 pr-8 py-2 text-slate-900">
|
||||||
@@ -48,10 +51,10 @@
|
|||||||
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="close(notification.id)"
|
|
||||||
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-slate-200 text-slate-600"
|
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-slate-200 text-slate-600"
|
||||||
|
@click="close(notification.id)"
|
||||||
>
|
>
|
||||||
<fa icon="xmark" class="fa-fw"></fa>
|
<font-awesome icon="xmark" class="fa-fw"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -63,7 +66,10 @@
|
|||||||
<div
|
<div
|
||||||
class="flex items-center justify-center w-12 bg-emerald-600 text-emerald-100"
|
class="flex items-center justify-center w-12 bg-emerald-600 text-emerald-100"
|
||||||
>
|
>
|
||||||
<fa icon="circle-info" class="fa-fw fa-xl"></fa>
|
<font-awesome
|
||||||
|
icon="circle-info"
|
||||||
|
class="fa-fw fa-xl"
|
||||||
|
></font-awesome>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative w-full pl-4 pr-8 py-2 text-emerald-900">
|
<div class="relative w-full pl-4 pr-8 py-2 text-emerald-900">
|
||||||
@@ -71,10 +77,10 @@
|
|||||||
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="close(notification.id)"
|
|
||||||
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-emerald-200 text-emerald-600"
|
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-emerald-200 text-emerald-600"
|
||||||
|
@click="close(notification.id)"
|
||||||
>
|
>
|
||||||
<fa icon="xmark" class="fa-fw"></fa>
|
<font-awesome icon="xmark" class="fa-fw"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -86,7 +92,10 @@
|
|||||||
<div
|
<div
|
||||||
class="flex items-center justify-center w-12 bg-amber-600 text-amber-100"
|
class="flex items-center justify-center w-12 bg-amber-600 text-amber-100"
|
||||||
>
|
>
|
||||||
<fa icon="triangle-exclamation" class="fa-fw fa-xl"></fa>
|
<font-awesome
|
||||||
|
icon="triangle-exclamation"
|
||||||
|
class="fa-fw fa-xl"
|
||||||
|
></font-awesome>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative w-full pl-4 pr-8 py-2 text-amber-900">
|
<div class="relative w-full pl-4 pr-8 py-2 text-amber-900">
|
||||||
@@ -94,10 +103,10 @@
|
|||||||
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="close(notification.id)"
|
|
||||||
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-amber-200 text-amber-600"
|
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-amber-200 text-amber-600"
|
||||||
|
@click="close(notification.id)"
|
||||||
>
|
>
|
||||||
<fa icon="xmark" class="fa-fw"></fa>
|
<font-awesome icon="xmark" class="fa-fw"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -109,7 +118,10 @@
|
|||||||
<div
|
<div
|
||||||
class="flex items-center justify-center w-12 bg-rose-600 text-rose-100"
|
class="flex items-center justify-center w-12 bg-rose-600 text-rose-100"
|
||||||
>
|
>
|
||||||
<fa icon="triangle-exclamation" class="fa-fw fa-xl"></fa>
|
<font-awesome
|
||||||
|
icon="triangle-exclamation"
|
||||||
|
class="fa-fw fa-xl"
|
||||||
|
></font-awesome>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative w-full pl-4 pr-8 py-2 text-rose-900">
|
<div class="relative w-full pl-4 pr-8 py-2 text-rose-900">
|
||||||
@@ -117,10 +129,10 @@
|
|||||||
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="close(notification.id)"
|
|
||||||
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-rose-200 text-rose-600"
|
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-rose-200 text-rose-600"
|
||||||
|
@click="close(notification.id)"
|
||||||
>
|
>
|
||||||
<fa icon="xmark" class="fa-fw"></fa>
|
<font-awesome icon="xmark" class="fa-fw"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -131,7 +143,8 @@
|
|||||||
|
|
||||||
<!--
|
<!--
|
||||||
This "group" of "modal" is the prompt for an answer.
|
This "group" of "modal" is the prompt for an answer.
|
||||||
Set "type" as follows: "confirm" for yes/no, and "notification" ones: "-permission", "-mute", "-off"
|
Set "type" as follows: "confirm" for yes/no, and "notification" ones:
|
||||||
|
"-permission", "-mute", "-off"
|
||||||
-->
|
-->
|
||||||
<NotificationGroup group="modal">
|
<NotificationGroup group="modal">
|
||||||
<div class="fixed z-[100] top-0 inset-x-0 w-full">
|
<div class="fixed z-[100] top-0 inset-x-0 w-full">
|
||||||
@@ -174,11 +187,11 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
v-if="notification.onYes"
|
v-if="notification.onYes"
|
||||||
|
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
||||||
@click="
|
@click="
|
||||||
notification.onYes();
|
notification.onYes();
|
||||||
close(notification.id);
|
close(notification.id);
|
||||||
"
|
"
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
|
||||||
>
|
>
|
||||||
Yes{{
|
Yes{{
|
||||||
notification.yesText ? ", " + notification.yesText : ""
|
notification.yesText ? ", " + notification.yesText : ""
|
||||||
@@ -187,12 +200,12 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
v-if="notification.onNo"
|
v-if="notification.onNo"
|
||||||
|
class="block w-full text-center text-md font-bold uppercase bg-yellow-600 text-white px-2 py-2 rounded-md mb-2"
|
||||||
@click="
|
@click="
|
||||||
notification.onNo(stopAsking);
|
notification.onNo(stopAsking);
|
||||||
close(notification.id);
|
close(notification.id);
|
||||||
stopAsking = false; // reset value
|
stopAsking = false; // reset value
|
||||||
"
|
"
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-yellow-600 text-white px-2 py-2 rounded-md mb-2"
|
|
||||||
>
|
>
|
||||||
No{{ notification.noText ? ", " + notification.noText : "" }}
|
No{{ notification.noText ? ", " + notification.noText : "" }}
|
||||||
</button>
|
</button>
|
||||||
@@ -209,8 +222,8 @@
|
|||||||
<div class="relative ml-2">
|
<div class="relative ml-2">
|
||||||
<!-- input -->
|
<!-- input -->
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
|
||||||
v-model="stopAsking"
|
v-model="stopAsking"
|
||||||
|
type="checkbox"
|
||||||
name="stopAsking"
|
name="stopAsking"
|
||||||
class="sr-only"
|
class="sr-only"
|
||||||
/>
|
/>
|
||||||
@@ -224,6 +237,7 @@
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
||||||
@click="
|
@click="
|
||||||
notification.onCancel
|
notification.onCancel
|
||||||
? notification.onCancel(stopAsking)
|
? notification.onCancel(stopAsking)
|
||||||
@@ -231,7 +245,6 @@
|
|||||||
close(notification.id);
|
close(notification.id);
|
||||||
stopAsking = false; // reset value for next time they open this modal
|
stopAsking = false; // reset value for next time they open this modal
|
||||||
"
|
"
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
|
||||||
>
|
>
|
||||||
{{ notification.onYes ? "Cancel" : "Close" }}
|
{{ notification.onYes ? "Cancel" : "Close" }}
|
||||||
</button>
|
</button>
|
||||||
@@ -270,8 +283,8 @@
|
|||||||
Until I turn it back on
|
Until I turn it back on
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="close(notification.id)"
|
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
||||||
|
@click="close(notification.id)"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
@@ -292,17 +305,17 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
class="block w-full text-center text-md font-bold uppercase bg-rose-600 text-white px-2 py-2 rounded-md mb-2"
|
||||||
@click="
|
@click="
|
||||||
close(notification.id);
|
close(notification.id);
|
||||||
turnOffNotifications(notification);
|
turnOffNotifications(notification);
|
||||||
"
|
"
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-rose-600 text-white px-2 py-2 rounded-md mb-2"
|
|
||||||
>
|
>
|
||||||
Turn Off Notification
|
Turn Off Notification
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="close(notification.id)"
|
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
||||||
|
@click="close(notification.id)"
|
||||||
>
|
>
|
||||||
Leave it On
|
Leave it On
|
||||||
</button>
|
</button>
|
||||||
@@ -315,12 +328,11 @@
|
|||||||
</NotificationGroup>
|
</NotificationGroup>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style></style>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Vue, Component } from "vue-facing-decorator";
|
import { Vue, Component } from "vue-facing-decorator";
|
||||||
import { logConsoleAndDb, retrieveSettingsForActiveAccount } from "./db/index";
|
import { logConsoleAndDb, retrieveSettingsForActiveAccount } from "./db/index";
|
||||||
import { NotificationIface } from "./constants/app";
|
import { NotificationIface } from "./constants/app";
|
||||||
|
import { logger } from "./utils/logger";
|
||||||
|
|
||||||
interface Settings {
|
interface Settings {
|
||||||
notifyingNewActivityTime?: string;
|
notifyingNewActivityTime?: string;
|
||||||
@@ -334,38 +346,38 @@ export default class App extends Vue {
|
|||||||
stopAsking = false;
|
stopAsking = false;
|
||||||
|
|
||||||
// created() {
|
// created() {
|
||||||
// console.log(
|
// logger.log(
|
||||||
// "Component created: Reactivity set up.",
|
// "Component created: Reactivity set up.",
|
||||||
// window.location.pathname,
|
// window.location.pathname,
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// beforeCreate() {
|
// beforeCreate() {
|
||||||
// console.log("Component beforeCreate: Instance initialized.");
|
// logger.log("Component beforeCreate: Instance initialized.");
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// beforeMount() {
|
// beforeMount() {
|
||||||
// console.log("Component beforeMount: Template is about to be rendered.");
|
// logger.log("Component beforeMount: Template is about to be rendered.");
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// mounted() {
|
// mounted() {
|
||||||
// console.log("Component mounted: Template is now rendered.");
|
// logger.log("Component mounted: Template is now rendered.");
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// beforeUpdate() {
|
// beforeUpdate() {
|
||||||
// console.log("Component beforeUpdate: DOM is about to be updated.");
|
// logger.log("Component beforeUpdate: DOM is about to be updated.");
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// updated() {
|
// updated() {
|
||||||
// console.log("Component updated: DOM has been updated.");
|
// logger.log("Component updated: DOM has been updated.");
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// beforeUnmount() {
|
// beforeUnmount() {
|
||||||
// console.log("Component beforeUnmount: Cleaning up before removal.");
|
// logger.log("Component beforeUnmount: Cleaning up before removal.");
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// unmounted() {
|
// unmounted() {
|
||||||
// console.log("Component unmounted: Component removed from the DOM.");
|
// logger.log("Component unmounted: Component removed from the DOM.");
|
||||||
// }
|
// }
|
||||||
|
|
||||||
truncateLongWords(sentence: string) {
|
truncateLongWords(sentence: string) {
|
||||||
@@ -378,42 +390,42 @@ export default class App extends Vue {
|
|||||||
async turnOffNotifications(
|
async turnOffNotifications(
|
||||||
notification: NotificationIface,
|
notification: NotificationIface,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
console.log("Starting turnOffNotifications...");
|
logger.log("Starting turnOffNotifications...");
|
||||||
let subscription: PushSubscriptionJSON | null = null;
|
let subscription: PushSubscriptionJSON | null = null;
|
||||||
let allGoingOff = false;
|
let allGoingOff = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log("Retrieving settings for the active account...");
|
logger.log("Retrieving settings for the active account...");
|
||||||
const settings: Settings = await retrieveSettingsForActiveAccount();
|
const settings: Settings = await retrieveSettingsForActiveAccount();
|
||||||
console.log("Retrieved settings:", settings);
|
logger.log("Retrieved settings:", settings);
|
||||||
|
|
||||||
const notifyingNewActivity = !!settings?.notifyingNewActivityTime;
|
const notifyingNewActivity = !!settings?.notifyingNewActivityTime;
|
||||||
const notifyingReminder = !!settings?.notifyingReminderTime;
|
const notifyingReminder = !!settings?.notifyingReminderTime;
|
||||||
|
|
||||||
if (!notifyingNewActivity || !notifyingReminder) {
|
if (!notifyingNewActivity || !notifyingReminder) {
|
||||||
allGoingOff = true;
|
allGoingOff = true;
|
||||||
console.log("Both notifications are being turned off.");
|
logger.log("Both notifications are being turned off.");
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Checking service worker readiness...");
|
logger.log("Checking service worker readiness...");
|
||||||
await navigator.serviceWorker?.ready
|
await navigator.serviceWorker?.ready
|
||||||
.then((registration) => {
|
.then((registration) => {
|
||||||
console.log("Service worker is ready. Fetching subscription...");
|
logger.log("Service worker is ready. Fetching subscription...");
|
||||||
return registration.pushManager.getSubscription();
|
return registration.pushManager.getSubscription();
|
||||||
})
|
})
|
||||||
.then(async (subscript: PushSubscription | null) => {
|
.then(async (subscript: PushSubscription | null) => {
|
||||||
if (subscript) {
|
if (subscript) {
|
||||||
subscription = subscript.toJSON();
|
subscription = subscript.toJSON();
|
||||||
console.log("PushSubscription retrieved:", subscription);
|
logger.log("PushSubscription retrieved:", subscription);
|
||||||
|
|
||||||
if (allGoingOff) {
|
if (allGoingOff) {
|
||||||
console.log("Unsubscribing from push notifications...");
|
logger.log("Unsubscribing from push notifications...");
|
||||||
await subscript.unsubscribe();
|
await subscript.unsubscribe();
|
||||||
console.log("Successfully unsubscribed.");
|
logger.log("Successfully unsubscribed.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logConsoleAndDb("Subscription object is not available.");
|
logConsoleAndDb("Subscription object is not available.");
|
||||||
console.log("No subscription found.");
|
logger.log("No subscription found.");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -422,11 +434,11 @@ export default class App extends Vue {
|
|||||||
JSON.stringify(error),
|
JSON.stringify(error),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
console.error("Error during subscription fetch:", error);
|
logger.error("Error during subscription fetch:", error);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!subscription) {
|
if (!subscription) {
|
||||||
console.log("No subscription available. Notifying user...");
|
logger.log("No subscription available. Notifying user...");
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
@@ -436,7 +448,7 @@ export default class App extends Vue {
|
|||||||
},
|
},
|
||||||
5000,
|
5000,
|
||||||
);
|
);
|
||||||
console.log("Exiting as there is no subscription to process.");
|
logger.log("Exiting as there is no subscription to process.");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,12 +457,12 @@ export default class App extends Vue {
|
|||||||
};
|
};
|
||||||
if (!allGoingOff) {
|
if (!allGoingOff) {
|
||||||
serverSubscription["notifyType"] = notification.title;
|
serverSubscription["notifyType"] = notification.title;
|
||||||
console.log(
|
logger.log(
|
||||||
`Server subscription updated with notifyType: ${notification.title}`,
|
`Server subscription updated with notifyType: ${notification.title}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Sending unsubscribe request to the server...");
|
logger.log("Sending unsubscribe request to the server...");
|
||||||
const pushServerSuccess = await fetch("/web-push/unsubscribe", {
|
const pushServerSuccess = await fetch("/web-push/unsubscribe", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -465,9 +477,9 @@ export default class App extends Vue {
|
|||||||
`Push server failed: ${response.status} ${errorBody}`,
|
`Push server failed: ${response.status} ${errorBody}`,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
console.error("Push server error response:", errorBody);
|
logger.error("Push server error response:", errorBody);
|
||||||
}
|
}
|
||||||
console.log(`Server response status: ${response.status}`);
|
logger.log(`Server response status: ${response.status}`);
|
||||||
return response.ok;
|
return response.ok;
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -475,14 +487,14 @@ export default class App extends Vue {
|
|||||||
"Push server communication failed: " + JSON.stringify(error),
|
"Push server communication failed: " + JSON.stringify(error),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
console.error("Error during server communication:", error);
|
logger.error("Error during server communication:", error);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
const message = pushServerSuccess
|
const message = pushServerSuccess
|
||||||
? "Notification is off."
|
? "Notification is off."
|
||||||
: "Notification is still on. Try to turn it off again.";
|
: "Notification is still on. Try to turn it off again.";
|
||||||
console.log("Server response processed. Message:", message);
|
logger.log("Server response processed. Message:", message);
|
||||||
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
@@ -495,11 +507,11 @@ export default class App extends Vue {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (notification.callback) {
|
if (notification.callback) {
|
||||||
console.log("Executing notification callback...");
|
logger.log("Executing notification callback...");
|
||||||
notification.callback(pushServerSuccess);
|
notification.callback(pushServerSuccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
logger.log(
|
||||||
"Completed turnOffNotifications with success:",
|
"Completed turnOffNotifications with success:",
|
||||||
pushServerSuccess,
|
pushServerSuccess,
|
||||||
);
|
);
|
||||||
@@ -509,7 +521,7 @@ export default class App extends Vue {
|
|||||||
"Error turning off notifications: " + JSON.stringify(error),
|
"Error turning off notifications: " + JSON.stringify(error),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
console.error("Critical error in turnOffNotifications:", error);
|
logger.error("Critical error in turnOffNotifications:", error);
|
||||||
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
@@ -526,3 +538,5 @@ export default class App extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style></style>
|
||||||
|
|||||||
@@ -2,26 +2,34 @@
|
|||||||
<li>
|
<li>
|
||||||
<!-- Last viewed separator -->
|
<!-- Last viewed separator -->
|
||||||
<div
|
<div
|
||||||
class="border-b border-dashed border-slate-300 text-orange-400 mt-4 mb-6 font-bold text-sm"
|
|
||||||
v-if="record.jwtId == lastViewedClaimId"
|
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">
|
<span class="block w-fit mx-auto -mb-2.5 bg-white px-2">
|
||||||
You've already seen all the following
|
You've already seen all the following
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-slate-100 rounded-t-md border border-slate-300 p-3 sm:p-4">
|
<div
|
||||||
<div class="flex items-center gap-2 mb-6">
|
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"
|
||||||
<img
|
>
|
||||||
src="https://a.fsdn.com/con/app/proj/identicons/screenshots/225506.jpg"
|
<div class="flex items-center gap-2">
|
||||||
class="size-8 object-cover rounded-full"
|
<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>
|
<div>
|
||||||
<h3 class="font-semibold">
|
<h3 class="font-semibold">
|
||||||
{{
|
{{ record.issuer.known ? record.issuer.displayName : "" }}
|
||||||
record.giver.known ? record.giver.displayName : "Anonymous Giver"
|
|
||||||
}}
|
|
||||||
</h3>
|
</h3>
|
||||||
<p class="ms-auto text-xs text-slate-500 italic">
|
<p class="ms-auto text-xs text-slate-500 italic">
|
||||||
{{ friendlyDate }}
|
{{ friendlyDate }}
|
||||||
@@ -29,10 +37,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 -->
|
<!-- Record Image -->
|
||||||
<div
|
<div
|
||||||
v-if="record.image"
|
v-if="record.image"
|
||||||
class="bg-cover mb-6 -mx-3 sm:-mx-4"
|
class="bg-cover mb-6 -mt-3 sm:-mt-4 -mx-3 sm:-mx-4"
|
||||||
:style="`background-image: url(${record.image});`"
|
:style="`background-image: url(${record.image});`"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
@@ -42,68 +56,63 @@
|
|||||||
<img
|
<img
|
||||||
class="w-full h-auto max-w-lg max-h-96 object-contain mx-auto drop-shadow-md"
|
class="w-full h-auto max-w-lg max-h-96 object-contain mx-auto drop-shadow-md"
|
||||||
:src="record.image"
|
:src="record.image"
|
||||||
@load="$emit('cacheImage', record.image)"
|
|
||||||
alt="Activity image"
|
alt="Activity image"
|
||||||
|
@load="$emit('cacheImage', record.image)"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative flex justify-between gap-4 max-w-lg mx-auto mb-5">
|
<div
|
||||||
|
class="relative flex justify-between gap-4 max-w-[40rem] mx-auto mb-5"
|
||||||
|
>
|
||||||
<!-- Source -->
|
<!-- Source -->
|
||||||
<div
|
<div
|
||||||
class="w-28 sm:w-40 text-center bg-white border border-slate-200 rounded p-2 sm:p-3"
|
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 class="relative w-fit mx-auto">
|
||||||
<template v-if="record.giver.profileImageUrl">
|
<div>
|
||||||
<EntityIcon
|
|
||||||
:profile-image-url="record.giver.profileImageUrl"
|
|
||||||
:class="[
|
|
||||||
!record.providerPlanName
|
|
||||||
? 'rounded-full size-[3rem] sm:size-[4rem] object-cover'
|
|
||||||
: 'rounded size-[3rem] sm:size-[4rem] object-cover',
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<!-- Project Icon -->
|
<!-- Project Icon -->
|
||||||
<template v-if="record.providerPlanName">
|
<div v-if="record.providerPlanName">
|
||||||
<ProjectIcon
|
<ProjectIcon
|
||||||
:entityId="record.providerPlanName"
|
:entity-id="record.providerPlanName"
|
||||||
:iconSize="48"
|
:icon-size="48"
|
||||||
class="rounded size-[3rem] sm:size-[4rem] *:w-full *:h-full"
|
class="rounded size-[3rem] sm:size-[4rem] *:w-full *:h-full"
|
||||||
/>
|
/>
|
||||||
</template>
|
</div>
|
||||||
<!-- Identicon for DIDs -->
|
<!-- Identicon for DIDs -->
|
||||||
<template v-else-if="record.giver.did">
|
<div v-else-if="record.agentDid">
|
||||||
<img
|
<EntityIcon
|
||||||
:src="`https://a.fsdn.com/con/app/proj/identicons/screenshots/225506.jpg`"
|
:entity-id="record.agentDid"
|
||||||
class="rounded-full size-[3rem] sm:size-[4rem]"
|
:profile-image-url="record.issuer.profileImageUrl"
|
||||||
alt="Identicon"
|
class="rounded-full bg-slate-100 overflow-hidden !size-[3rem] sm:!size-[4rem]"
|
||||||
/>
|
/>
|
||||||
</template>
|
</div>
|
||||||
<!-- Unknown Person -->
|
<!-- Unknown Person -->
|
||||||
<template v-else>
|
<div v-else>
|
||||||
<fa
|
<font-awesome
|
||||||
icon="person-circle-question"
|
icon="person-circle-question"
|
||||||
class="text-slate-300 text-[3rem] sm:text-[4rem]"
|
class="text-slate-300 text-[3rem] sm:text-[4rem]"
|
||||||
/>
|
/>
|
||||||
</template>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs mt-2 line-clamp-3 sm:line-clamp-2">
|
<div
|
||||||
<fa
|
v-if="record.providerPlanName || record.giver.known"
|
||||||
:icon="record.providerPlanName ? 'building' : 'user'"
|
class="text-xs mt-2 truncate"
|
||||||
|
>
|
||||||
|
<font-awesome
|
||||||
|
:icon="record.providerPlanName ? 'users' : 'user'"
|
||||||
class="fa-fw text-slate-400"
|
class="fa-fw text-slate-400"
|
||||||
/>
|
/>
|
||||||
{{ record.giver.displayName }}
|
{{ record.providerPlanName || record.giver.displayName }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Arrow -->
|
<!-- Arrow -->
|
||||||
<div
|
<div
|
||||||
class="absolute inset-x-28 sm:inset-x-40 mx-2 top-1/2 -translate-y-1/2"
|
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">
|
<div class="text-sm text-center leading-none font-semibold pe-[15px]">
|
||||||
{{ fetchAmount }}
|
{{ fetchAmount }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -120,70 +129,54 @@
|
|||||||
|
|
||||||
<!-- Destination -->
|
<!-- Destination -->
|
||||||
<div
|
<div
|
||||||
class="w-28 sm:w-40 text-center bg-white border border-slate-200 rounded p-2 sm:p-3"
|
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 class="relative w-fit mx-auto">
|
||||||
<template v-if="record.receiver.profileImageUrl">
|
<div>
|
||||||
<EntityIcon
|
|
||||||
:profile-image-url="record.receiver.profileImageUrl"
|
|
||||||
:class="[
|
|
||||||
!record.recipientProjectName
|
|
||||||
? 'rounded-full size-[3rem] sm:size-[4rem] object-cover'
|
|
||||||
: 'rounded size-[3rem] sm:size-[4rem] object-cover',
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<!-- Project Icon -->
|
<!-- Project Icon -->
|
||||||
<template v-if="record.recipientProjectName">
|
<div v-if="record.recipientProjectName">
|
||||||
<ProjectIcon
|
<ProjectIcon
|
||||||
:entityId="record.recipientProjectName"
|
:entity-id="record.recipientProjectName"
|
||||||
:iconSize="48"
|
:icon-size="48"
|
||||||
class="rounded size-[3rem] sm:size-[4rem] *:w-full *:h-full"
|
class="rounded size-[3rem] sm:size-[4rem] *:w-full *:h-full"
|
||||||
/>
|
/>
|
||||||
</template>
|
</div>
|
||||||
<!-- Identicon for DIDs -->
|
<!-- Identicon for DIDs -->
|
||||||
<template v-else-if="record.receiver.did">
|
<div v-else-if="record.recipientDid">
|
||||||
<img
|
<EntityIcon
|
||||||
:src="`https://a.fsdn.com/con/app/proj/identicons/screenshots/225506.jpg`"
|
:entity-id="record.recipientDid"
|
||||||
class="rounded-full size-[3rem] sm:size-[4rem]"
|
:profile-image-url="record.receiver.profileImageUrl"
|
||||||
alt="Identicon"
|
class="rounded-full bg-slate-100 overflow-hidden !size-[3rem] sm:!size-[4rem]"
|
||||||
/>
|
/>
|
||||||
</template>
|
</div>
|
||||||
<!-- Unknown Person -->
|
<!-- Unknown Person -->
|
||||||
<template v-else>
|
<div v-else>
|
||||||
<fa
|
<font-awesome
|
||||||
icon="person-circle-question"
|
icon="person-circle-question"
|
||||||
class="text-slate-300 text-[3rem] sm:text-[4rem]"
|
class="text-slate-300 text-[3rem] sm:text-[4rem]"
|
||||||
/>
|
/>
|
||||||
</template>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs mt-2 line-clamp-3 sm:line-clamp-2">
|
<div
|
||||||
<fa
|
v-if="record.recipientProjectName || record.receiver.known"
|
||||||
:icon="record.recipientProjectName ? 'building' : 'user'"
|
class="text-xs mt-2 truncate"
|
||||||
|
>
|
||||||
|
<font-awesome
|
||||||
|
:icon="record.recipientProjectName ? 'users' : 'user'"
|
||||||
class="fa-fw text-slate-400"
|
class="fa-fw text-slate-400"
|
||||||
/>
|
/>
|
||||||
{{ record.receiver.displayName }}
|
{{ record.recipientProjectName || record.receiver.displayName }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Description -->
|
<!-- Description -->
|
||||||
<p class="font-medium">
|
<p class="font-medium">
|
||||||
<a @click="$emit('loadClaim', record.jwtId)" class="cursor-pointer">
|
<a class="cursor-pointer" @click="$emit('loadClaim', record.jwtId)">
|
||||||
{{ description }}
|
{{ description }}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm">{{ subDescription }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="flex items-center gap-2 text-lg bg-slate-300 rounded-b-md px-3 sm:px-4 py-1 sm:py-2"
|
|
||||||
>
|
|
||||||
<a @click="$emit('loadClaim', record.jwtId)" class="cursor-pointer">
|
|
||||||
<fa icon="circle-info" class="fa-fw text-slate-500" />
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
@@ -220,70 +213,13 @@ export default class ActivityListItem extends Vue {
|
|||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private formatParticipantInfo(): string {
|
|
||||||
const { giver, receiver } = this.record;
|
|
||||||
|
|
||||||
// Both participants are known contacts
|
|
||||||
if (giver.known && receiver.known) {
|
|
||||||
return `${giver.displayName} gave to ${receiver.displayName}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only giver is known
|
|
||||||
if (giver.known) {
|
|
||||||
const recipient = this.record.recipientProjectName
|
|
||||||
? `the project "${this.record.recipientProjectName}"`
|
|
||||||
: receiver.displayName;
|
|
||||||
return `${giver.displayName} gave to ${recipient}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only receiver is known
|
|
||||||
if (receiver.known) {
|
|
||||||
const provider = this.record.providerPlanName
|
|
||||||
? `the project "${this.record.providerPlanName}"`
|
|
||||||
: giver.displayName;
|
|
||||||
return `${receiver.displayName} received from ${provider}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Neither is known
|
|
||||||
return this.formatUnknownParticipants();
|
|
||||||
}
|
|
||||||
|
|
||||||
private formatUnknownParticipants(): string {
|
|
||||||
const { giver, receiver, providerPlanName, recipientProjectName } =
|
|
||||||
this.record;
|
|
||||||
|
|
||||||
if (providerPlanName || recipientProjectName) {
|
|
||||||
const from = providerPlanName
|
|
||||||
? `the project "${providerPlanName}"`
|
|
||||||
: giver.displayName;
|
|
||||||
const to = recipientProjectName
|
|
||||||
? `the project "${recipientProjectName}"`
|
|
||||||
: receiver.displayName;
|
|
||||||
return `from ${from} to ${to}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return giver.displayName === receiver.displayName
|
|
||||||
? `between two who are ${giver.displayName}`
|
|
||||||
: `from ${giver.displayName} to ${receiver.displayName}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
get description(): string {
|
get description(): string {
|
||||||
const claim =
|
const claim =
|
||||||
(this.record.fullClaim as unknown).claim || this.record.fullClaim;
|
(this.record.fullClaim as unknown).claim || this.record.fullClaim;
|
||||||
|
|
||||||
if (!claim.description) {
|
|
||||||
return "something not described";
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${claim.description}`;
|
return `${claim.description}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
get subDescription(): string {
|
|
||||||
const participants = this.formatParticipantInfo();
|
|
||||||
|
|
||||||
return `${participants}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private displayAmount(code: string, amt: number) {
|
private displayAmount(code: string, amt: number) {
|
||||||
return `${amt} ${this.currencyShortWordForCode(code, amt === 1)}`;
|
return `${amt} ${this.currencyShortWordForCode(code, amt === 1)}`;
|
||||||
}
|
}
|
||||||
@@ -292,11 +228,6 @@ export default class ActivityListItem extends Vue {
|
|||||||
return unitCode === "HUR" ? (single ? "hour" : "hours") : unitCode;
|
return unitCode === "HUR" ? (single ? "hour" : "hours") : unitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
get formattedTimestamp() {
|
|
||||||
// Add your timestamp formatting logic here
|
|
||||||
return this.record.timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
get canConfirm(): boolean {
|
get canConfirm(): boolean {
|
||||||
if (!this.isRegistered) return false;
|
if (!this.isRegistered) return false;
|
||||||
if (!isGiveClaimType(this.record.fullClaim?.["@type"])) return false;
|
if (!isGiveClaimType(this.record.fullClaim?.["@type"])) return false;
|
||||||
|
|||||||
@@ -29,29 +29,29 @@
|
|||||||
<p class="text-sm mb-2">{{ text }}</p>
|
<p class="text-sm mb-2">{{ text }}</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="handleOption1(close)"
|
|
||||||
class="block w-full text-center text-md font-bold capitalize bg-blue-800 text-white px-2 py-2 rounded-md mb-2"
|
class="block w-full text-center text-md font-bold capitalize bg-blue-800 text-white px-2 py-2 rounded-md mb-2"
|
||||||
|
@click="handleOption1(close)"
|
||||||
>
|
>
|
||||||
{{ option1Text }}
|
{{ option1Text }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="handleOption2(close)"
|
|
||||||
class="block w-full text-center text-md font-bold capitalize bg-blue-700 text-white px-2 py-2 rounded-md mb-2"
|
class="block w-full text-center text-md font-bold capitalize bg-blue-700 text-white px-2 py-2 rounded-md mb-2"
|
||||||
|
@click="handleOption2(close)"
|
||||||
>
|
>
|
||||||
{{ option2Text }}
|
{{ option2Text }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="handleOption3(close)"
|
|
||||||
class="block w-full text-center text-md font-bold capitalize bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
class="block w-full text-center text-md font-bold capitalize bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
||||||
|
@click="handleOption3(close)"
|
||||||
>
|
>
|
||||||
{{ option3Text }}
|
{{ option3Text }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="handleCancel(close)"
|
|
||||||
class="block w-full text-center text-md font-bold capitalize bg-slate-600 text-white px-2 py-2 rounded-md"
|
class="block w-full text-center text-md font-bold capitalize bg-slate-600 text-white px-2 py-2 rounded-md"
|
||||||
|
@click="handleCancel(close)"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
{{ message }}
|
{{ message }}
|
||||||
Note that their name is only stored on this device.
|
Note that their name is only stored on this device.
|
||||||
<input
|
<input
|
||||||
|
v-model="newText"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
||||||
v-model="newText"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
|
|||||||
196
src/components/DataExportSection.vue
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
/** * Data Export Section Component * * Provides UI and functionality for
|
||||||
|
exporting user data and backing up identifier seeds. * Includes buttons for seed
|
||||||
|
backup and database export, with platform-specific download instructions. * *
|
||||||
|
@component * @displayName DataExportSection * @example * ```vue *
|
||||||
|
<DataExportSection :active-did="currentDid" />
|
||||||
|
* ``` */
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
id="sectionDataExport"
|
||||||
|
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
|
||||||
|
>
|
||||||
|
<div class="mb-2 font-bold">Data Export</div>
|
||||||
|
<router-link
|
||||||
|
v-if="activeDid"
|
||||||
|
:to="{ name: 'seed-backup' }"
|
||||||
|
class="block w-full text-center text-md 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 mt-2"
|
||||||
|
>
|
||||||
|
Backup Identifier Seed
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<button
|
||||||
|
:class="computedStartDownloadLinkClassNames()"
|
||||||
|
class="block w-full text-center text-md 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"
|
||||||
|
@click="exportDatabase()"
|
||||||
|
>
|
||||||
|
Download Settings & Contacts
|
||||||
|
<br />
|
||||||
|
(excluding Identifier Data)
|
||||||
|
</button>
|
||||||
|
<a
|
||||||
|
ref="downloadLink"
|
||||||
|
:class="computedDownloadLinkClassNames()"
|
||||||
|
class="block w-full text-center text-md bg-gradient-to-b from-green-500 to-green-800 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6"
|
||||||
|
>
|
||||||
|
If no download happened yet, click again here to download now.
|
||||||
|
</a>
|
||||||
|
<div v-if="platformCapabilities.needsFileHandlingInstructions" class="mt-4">
|
||||||
|
<p>
|
||||||
|
After the download, you can save the file in your preferred storage
|
||||||
|
location.
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li
|
||||||
|
v-if="platformCapabilities.isIOS"
|
||||||
|
class="list-disc list-outside ml-4"
|
||||||
|
>
|
||||||
|
On iOS: You will be prompted to choose a location to save your backup
|
||||||
|
file.
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
v-if="platformCapabilities.isMobile && !platformCapabilities.isIOS"
|
||||||
|
class="list-disc list-outside ml-4"
|
||||||
|
>
|
||||||
|
On Android: You will be prompted to choose a location to save your
|
||||||
|
backup file.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from "vue-facing-decorator";
|
||||||
|
import { NotificationIface } from "../constants/app";
|
||||||
|
import { db } from "../db/index";
|
||||||
|
import { logger } from "../utils/logger";
|
||||||
|
import { PlatformServiceFactory } from "../services/PlatformServiceFactory";
|
||||||
|
import {
|
||||||
|
PlatformService,
|
||||||
|
PlatformCapabilities,
|
||||||
|
} from "../services/PlatformService";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @vue-component
|
||||||
|
* Data Export Section Component
|
||||||
|
* Handles database export and seed backup functionality with platform-specific behavior
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
export default class DataExportSection extends Vue {
|
||||||
|
/**
|
||||||
|
* Notification function injected by Vue
|
||||||
|
* Used to show success/error messages to the user
|
||||||
|
*/
|
||||||
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Active DID (Decentralized Identifier) of the user
|
||||||
|
* Controls visibility of seed backup option
|
||||||
|
* @required
|
||||||
|
*/
|
||||||
|
@Prop({ required: true }) readonly activeDid!: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL for the database export download
|
||||||
|
* Created and revoked dynamically during export process
|
||||||
|
* Only used in web platform
|
||||||
|
*/
|
||||||
|
downloadUrl = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Platform service instance for platform-specific operations
|
||||||
|
*/
|
||||||
|
private platformService: PlatformService =
|
||||||
|
PlatformServiceFactory.getInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Platform capabilities for the current platform
|
||||||
|
*/
|
||||||
|
private get platformCapabilities(): PlatformCapabilities {
|
||||||
|
return this.platformService.getCapabilities();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lifecycle hook to clean up resources
|
||||||
|
* Revokes object URL when component is unmounted (web platform only)
|
||||||
|
*/
|
||||||
|
beforeUnmount() {
|
||||||
|
if (this.downloadUrl && this.platformCapabilities.hasFileDownload) {
|
||||||
|
URL.revokeObjectURL(this.downloadUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports the database to a JSON file
|
||||||
|
* Uses platform-specific methods for saving the exported data
|
||||||
|
* Shows success/error notifications to user
|
||||||
|
*
|
||||||
|
* @throws {Error} If export fails
|
||||||
|
* @emits {Notification} Success or error notification
|
||||||
|
*/
|
||||||
|
public async exportDatabase() {
|
||||||
|
try {
|
||||||
|
const blob = await db.export({ prettyJson: true });
|
||||||
|
const fileName = `${db.name}-backup.json`;
|
||||||
|
|
||||||
|
if (this.platformCapabilities.hasFileDownload) {
|
||||||
|
// Web platform: Use download link
|
||||||
|
this.downloadUrl = URL.createObjectURL(blob);
|
||||||
|
const downloadAnchor = this.$refs.downloadLink as HTMLAnchorElement;
|
||||||
|
downloadAnchor.href = this.downloadUrl;
|
||||||
|
downloadAnchor.download = fileName;
|
||||||
|
downloadAnchor.click();
|
||||||
|
setTimeout(() => URL.revokeObjectURL(this.downloadUrl), 1000);
|
||||||
|
} else if (this.platformCapabilities.hasFileSystem) {
|
||||||
|
// Native platform: Write to app directory
|
||||||
|
const content = await blob.text();
|
||||||
|
await this.platformService.writeAndShareFile(fileName, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "success",
|
||||||
|
title: "Export Successful",
|
||||||
|
text: this.platformCapabilities.hasFileDownload
|
||||||
|
? "See your downloads directory for the backup. It is in the Dexie format."
|
||||||
|
: "Please choose a location to save your backup file.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Export Error:", error);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Export Error",
|
||||||
|
text: "There was an error exporting the data.",
|
||||||
|
},
|
||||||
|
3000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes class names for the initial download button
|
||||||
|
* @returns Object with 'hidden' class when download is in progress (web platform only)
|
||||||
|
*/
|
||||||
|
public computedStartDownloadLinkClassNames() {
|
||||||
|
return {
|
||||||
|
hidden: this.downloadUrl && this.platformCapabilities.hasFileDownload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes class names for the secondary download link
|
||||||
|
* @returns Object with 'hidden' class when no download is available or not on web platform
|
||||||
|
*/
|
||||||
|
public computedDownloadLinkClassNames() {
|
||||||
|
return {
|
||||||
|
hidden: !this.downloadUrl || !this.platformCapabilities.hasFileDownload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||