59 changed files with 4955 additions and 1672 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,2 +1,2 @@ |
|||
#Tue Mar 11 10:01:05 UTC 2025 |
|||
gradle.version=8.10.2 |
|||
#Fri Mar 21 07:27:50 UTC 2025 |
|||
gradle.version=8.2.1 |
|||
|
Binary file not shown.
@ -1,3 +0,0 @@ |
|||
source "https://rubygems.org" |
|||
|
|||
gem "fastlane" |
@ -1,3 +1,2 @@ |
|||
/build/* |
|||
!/build/.npmkeep |
|||
src/main/assets/public/assets/ |
@ -1,398 +0,0 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<issues format="6" by="lint 8.1.0" type="baseline" client="gradle" dependencies="true" name="AGP (8.1.0)" variant="all" version="8.1.0"> |
|||
|
|||
<issue |
|||
id="UnknownIssueId" |
|||
message="Unknown issue id "UnsanitizedFilenameFromContentProvider"" |
|||
errorLine1=" disable 'UnsanitizedFilenameFromContentProvider'" |
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="build.gradle" |
|||
line="26" |
|||
column="18"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="UnknownIssueId" |
|||
message="Unknown issue id "UnsanitizedFilenameFromContentProvider"" |
|||
errorLine1=" disable 'UnsanitizedFilenameFromContentProvider'" |
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="build.gradle" |
|||
line="26" |
|||
column="18"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="UnknownIssueId" |
|||
message="Unknown issue id "LintBaselineFixed"" |
|||
errorLine1=" ignore 'LintBaselineFixed'" |
|||
errorLine2=" ~~~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="build.gradle" |
|||
line="34" |
|||
column="17"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="UnknownIssueId" |
|||
message="Unknown issue id "LintBaselineFixed"" |
|||
errorLine1=" ignore 'LintBaselineFixed'" |
|||
errorLine2=" ~~~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="build.gradle" |
|||
line="34" |
|||
column="17"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="DefaultLocale" |
|||
message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead" |
|||
errorLine1=" String msg = String.format(" |
|||
errorLine2=" ^"> |
|||
<location |
|||
file="src/main/java/com/getcapacitor/BridgeWebChromeClient.java" |
|||
line="467" |
|||
column="26"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="DefaultLocale" |
|||
message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`." |
|||
errorLine1=" return mask.toUpperCase().equals(string.toUpperCase());" |
|||
errorLine2=" ~~~~~~~~~~~"> |
|||
<location |
|||
file="src/main/java/com/getcapacitor/util/HostMask.java" |
|||
line="110" |
|||
column="29"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="DefaultLocale" |
|||
message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`." |
|||
errorLine1=" return mask.toUpperCase().equals(string.toUpperCase());" |
|||
errorLine2=" ~~~~~~~~~~~"> |
|||
<location |
|||
file="src/main/java/com/getcapacitor/util/HostMask.java" |
|||
line="110" |
|||
column="57"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="DefaultLocale" |
|||
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`." |
|||
errorLine1=" if (header.getKey().equalsIgnoreCase("Accept") && header.getValue().toLowerCase().contains("text/html")) {" |
|||
errorLine2=" ~~~~~~~~~~~"> |
|||
<location |
|||
file="src/main/java/com/getcapacitor/WebViewLocalServer.java" |
|||
line="474" |
|||
column="93"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="SimpleDateFormat" |
|||
message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates." |
|||
errorLine1=" String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());" |
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="src/main/java/com/getcapacitor/BridgeWebChromeClient.java" |
|||
line="504" |
|||
column="28"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="SimpleDateFormat" |
|||
message="To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, or `getTimeInstance()`, or use `new SimpleDateFormat(String template, Locale locale)` with for example `Locale.US` for ASCII dates." |
|||
errorLine1=" DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'");" |
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="src/main/java/com/getcapacitor/PluginResult.java" |
|||
line="44" |
|||
column="25"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="UnusedAttribute" |
|||
message="Attribute `usesCleartextTraffic` is only used in API level 23 and higher (current min is 22)" |
|||
errorLine1="<application android:usesCleartextTraffic="true">" |
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="src/main/AndroidManifest.xml" |
|||
line="4" |
|||
column="15"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="UnusedAttribute" |
|||
message="Attribute `autoVerify` is only used in API level 23 and higher (current min is 22)" |
|||
errorLine1=" <intent-filter android:autoVerify="true">" |
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="src/main/AndroidManifest.xml" |
|||
line="25" |
|||
column="28"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="ManifestOrder" |
|||
message="`<uses-permission>` tag appears after `<application>` tag" |
|||
errorLine1=" <uses-permission android:name="android.permission.INTERNET" />" |
|||
errorLine2=" ~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="src/main/AndroidManifest.xml" |
|||
line="47" |
|||
column="6"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="AndroidGradlePluginVersion" |
|||
message="A newer version of com.android.tools.build:gradle than 8.2.1 is available: 8.9.0. (There is also a newer version of 8.2.𝑥 available, if upgrading to 8.9.0 is difficult: 8.2.2)" |
|||
errorLine1=" classpath 'com.android.tools.build:gradle:8.2.1'" |
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="build.gradle" |
|||
line="12" |
|||
column="9"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="AndroidGradlePluginVersion" |
|||
message="A newer version of com.android.tools.build:gradle than 8.2.1 is available: 8.9.0. (There is also a newer version of 8.2.𝑥 available, if upgrading to 8.9.0 is difficult: 8.2.2)" |
|||
errorLine1=" classpath 'com.android.tools.build:gradle:8.2.1'" |
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="build.gradle" |
|||
line="18" |
|||
column="9"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="GradleDependency" |
|||
message="A newer version of androidx.appcompat:appcompat than 1.6.1 is available: 1.7.0" |
|||
errorLine1=" implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"" |
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="build.gradle" |
|||
line="46" |
|||
column="20"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="GradleDependency" |
|||
message="A newer version of androidx.appcompat:appcompat than 1.6.1 is available: 1.7.0" |
|||
errorLine1=" implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"" |
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="build.gradle" |
|||
line="46" |
|||
column="20"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="GradleDependency" |
|||
message="A newer version of androidx.coordinatorlayout:coordinatorlayout than 1.2.0 is available: 1.3.0" |
|||
errorLine1=" implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"" |
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="build.gradle" |
|||
line="47" |
|||
column="20"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="GradleDependency" |
|||
message="A newer version of androidx.test.ext:junit than 1.1.5 is available: 1.2.1" |
|||
errorLine1=" androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"" |
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="build.gradle" |
|||
line="51" |
|||
column="31"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="GradleDependency" |
|||
message="A newer version of androidx.test.espresso:espresso-core than 3.5.1 is available: 3.6.1" |
|||
errorLine1=" androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"" |
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="build.gradle" |
|||
line="52" |
|||
column="31"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="GradleDependency" |
|||
message="A newer version of androidx.appcompat:appcompat than 1.6.1 is available: 1.7.0" |
|||
errorLine1=" implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"" |
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="build.gradle" |
|||
line="75" |
|||
column="20"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="GradleDependency" |
|||
message="A newer version of androidx.test.ext:junit than 1.1.5 is available: 1.2.1" |
|||
errorLine1=" androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"" |
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="build.gradle" |
|||
line="77" |
|||
column="31"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="GradleDependency" |
|||
message="A newer version of androidx.test.espresso:espresso-core than 3.5.1 is available: 3.6.1" |
|||
errorLine1=" androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"" |
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="build.gradle" |
|||
line="78" |
|||
column="31"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="Recycle" |
|||
message="This `TypedArray` should be recycled after use with `#recycle()`" |
|||
errorLine1=" TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.bridge_fragment);" |
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="src/main/java/com/getcapacitor/BridgeFragment.java" |
|||
line="99" |
|||
column="32"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="Overdraw" |
|||
message="Possible overdraw: Root element paints background `#F0FF1414` with a theme that also paints a background (inferred theme is `@android:style/Theme.Holo`)" |
|||
errorLine1=" android:background="#F0FF1414"" |
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="src/main/res/layout/fragment_bridge.xml" |
|||
line="5" |
|||
column="5"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="UnusedResources" |
|||
message="The resource `R.layout.activity_main` appears to be unused" |
|||
errorLine1="<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"" |
|||
errorLine2="^"> |
|||
<location |
|||
file="src/main/res/layout/activity_main.xml" |
|||
line="2" |
|||
column="1"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="UnusedResources" |
|||
message="The resource `R.xml.config` appears to be unused" |
|||
errorLine1="<widget version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">" |
|||
errorLine2="^"> |
|||
<location |
|||
file="src/main/res/xml/config.xml" |
|||
line="2" |
|||
column="1"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="UnusedResources" |
|||
message="The resource `R.drawable.ic_launcher_background` appears to be unused" |
|||
errorLine1="<vector xmlns:android="http://schemas.android.com/apk/res/android"" |
|||
errorLine2="^"> |
|||
<location |
|||
file="src/main/res/drawable/ic_launcher_background.xml" |
|||
line="2" |
|||
column="1"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="UnusedResources" |
|||
message="The resource `R.drawable.ic_launcher_foreground` appears to be unused" |
|||
errorLine1="<vector xmlns:android="http://schemas.android.com/apk/res/android"" |
|||
errorLine2="^"> |
|||
<location |
|||
file="src/main/res/drawable-v24/ic_launcher_foreground.xml" |
|||
line="1" |
|||
column="1"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="UnusedResources" |
|||
message="The resource `R.string.package_name` appears to be unused" |
|||
errorLine1=" <string name="package_name">app.timesafari.app</string>" |
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="src/main/res/values/strings.xml" |
|||
line="5" |
|||
column="13"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="UnusedResources" |
|||
message="The resource `R.string.custom_url_scheme` appears to be unused" |
|||
errorLine1=" <string name="custom_url_scheme">app.timesafari.app</string>" |
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> |
|||
<location |
|||
file="src/main/res/values/strings.xml" |
|||
line="6" |
|||
column="13"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="MonochromeLauncherIcon" |
|||
message="The application adaptive icon is missing a monochrome tag" |
|||
errorLine1="<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">" |
|||
errorLine2="^"> |
|||
<location |
|||
file="src/main/res/mipmap-anydpi-v26/ic_launcher.xml" |
|||
line="2" |
|||
column="1"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="MonochromeLauncherIcon" |
|||
message="The application adaptive roundIcon is missing a monochrome tag" |
|||
errorLine1="<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">" |
|||
errorLine2="^"> |
|||
<location |
|||
file="src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml" |
|||
line="2" |
|||
column="1"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="IconDipSize" |
|||
message="The image `splash.png` varies significantly in its density-independent (dip) size across the various density versions: drawable-land-hdpi/splash.png: 533x320 dp (800x480 px), drawable-land-mdpi/splash.png: 480x320 dp (480x320 px), drawable-land-xhdpi/splash.png: 640x360 dp (1280x720 px), drawable-land-xxhdpi/splash.png: 533x320 dp (1600x960 px), drawable-land-xxxhdpi/splash.png: 480x320 dp (1920x1280 px)"> |
|||
<location |
|||
file="src/main/res/drawable-land-mdpi/splash.png"/> |
|||
<location |
|||
file="src/main/res/drawable-land-xxxhdpi/splash.png"/> |
|||
<location |
|||
file="src/main/res/drawable-land-hdpi/splash.png"/> |
|||
<location |
|||
file="src/main/res/drawable-land-xxhdpi/splash.png"/> |
|||
<location |
|||
file="src/main/res/drawable-land-xhdpi/splash.png"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="IconDuplicatesConfig" |
|||
message="The `splash.png` icon has identical contents in the following configuration folders: drawable-land-mdpi, drawable"> |
|||
<location |
|||
file="src/main/res/drawable/splash.png"/> |
|||
<location |
|||
file="src/main/res/drawable-land-mdpi/splash.png"/> |
|||
</issue> |
|||
|
|||
<issue |
|||
id="IconLocation" |
|||
message="Found bitmap drawable `res/drawable/splash.png` in densityless folder"> |
|||
<location |
|||
file="src/main/res/drawable/splash.png"/> |
|||
</issue> |
|||
|
|||
</issues> |
@ -1,20 +1,26 @@ |
|||
package app.timesafari.app; |
|||
package com.getcapacitor.myapp; |
|||
|
|||
import static org.junit.Assert.*; |
|||
|
|||
import android.content.Context; |
|||
import androidx.test.platform.app.InstrumentationRegistry; |
|||
import androidx.test.ext.junit.runners.AndroidJUnit4; |
|||
|
|||
import androidx.test.platform.app.InstrumentationRegistry; |
|||
import org.junit.Test; |
|||
import org.junit.runner.RunWith; |
|||
|
|||
import static org.junit.Assert.*; |
|||
|
|||
/** |
|||
* Instrumented test, which will execute on an Android device. |
|||
* |
|||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a> |
|||
*/ |
|||
@RunWith(AndroidJUnit4.class) |
|||
public class ExampleInstrumentedTest { |
|||
|
|||
@Test |
|||
public void useAppContext() { |
|||
public void useAppContext() throws Exception { |
|||
// Context of the app under test.
|
|||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); |
|||
|
|||
assertEquals("app.timesafari", appContext.getPackageName()); |
|||
} |
|||
} |
@ -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; |
|||
|
@ -1,7 +0,0 @@ |
|||
android { |
|||
lintOptions { |
|||
disable 'UnsanitizedFilenameFromContentProvider' |
|||
abortOnError false |
|||
baseline file("lint-baseline.xml") |
|||
} |
|||
} |
@ -1,2 +0,0 @@ |
|||
json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one |
|||
package_name("app.timesafari.app") # e.g. com.krausefx.app |
@ -1,38 +0,0 @@ |
|||
# This file contains the fastlane.tools configuration |
|||
# You can find the documentation at https://docs.fastlane.tools |
|||
# |
|||
# For a list of all available actions, check out |
|||
# |
|||
# https://docs.fastlane.tools/actions |
|||
# |
|||
# For a list of all available plugins, check out |
|||
# |
|||
# https://docs.fastlane.tools/plugins/available-plugins |
|||
# |
|||
|
|||
# Uncomment the line if you want fastlane to automatically update itself |
|||
# update_fastlane |
|||
|
|||
default_platform(:android) |
|||
|
|||
platform :android do |
|||
desc "Build and deploy Android app" |
|||
lane :beta do |
|||
gradle( |
|||
task: "clean assembleRelease" |
|||
) |
|||
upload_to_play_store( |
|||
track: 'beta', |
|||
aab: '../app/build/outputs/bundle/release/app-release.aab' |
|||
) |
|||
end |
|||
|
|||
lane :release do |
|||
gradle( |
|||
task: "clean assembleRelease" |
|||
) |
|||
upload_to_play_store( |
|||
aab: '../app/build/outputs/bundle/release/app-release.aab' |
|||
) |
|||
end |
|||
end |
@ -1,40 +0,0 @@ |
|||
fastlane documentation |
|||
---- |
|||
|
|||
# Installation |
|||
|
|||
Make sure you have the latest version of the Xcode command line tools installed: |
|||
|
|||
```sh |
|||
xcode-select --install |
|||
``` |
|||
|
|||
For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) |
|||
|
|||
# Available Actions |
|||
|
|||
## Android |
|||
|
|||
### android beta |
|||
|
|||
```sh |
|||
[bundle exec] fastlane android beta |
|||
``` |
|||
|
|||
Build and deploy Android app |
|||
|
|||
### android release |
|||
|
|||
```sh |
|||
[bundle exec] fastlane android release |
|||
``` |
|||
|
|||
|
|||
|
|||
---- |
|||
|
|||
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. |
|||
|
|||
More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). |
|||
|
|||
The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools). |
@ -1,8 +0,0 @@ |
|||
## This file must *NOT* be checked into Version Control Systems, |
|||
# as it contains information specific to your local configuration. |
|||
# |
|||
# Location of the SDK. This is only used by Gradle. |
|||
# For customization when using a Version Control System, please read the |
|||
# header note. |
|||
#Sun Mar 09 06:14:41 UTC 2025 |
|||
sdk.dir=/opt/android-sdk |
After Width: | Height: | Size: 190 KiB |
File diff suppressed because it is too large
@ -0,0 +1,334 @@ |
|||
<template> |
|||
<li> |
|||
<!-- Last viewed separator --> |
|||
<div |
|||
v-if="record.jwtId == lastViewedClaimId" |
|||
class="border-b border-dashed border-slate-300 text-orange-400 mt-4 mb-6 font-bold text-sm" |
|||
> |
|||
<span class="block w-fit mx-auto -mb-2.5 bg-white px-2"> |
|||
You've already seen all the following |
|||
</span> |
|||
</div> |
|||
|
|||
<div class="bg-slate-100 rounded-t-md border border-slate-300 p-3 sm:p-4"> |
|||
<div class="flex items-center gap-2 mb-6"> |
|||
<img |
|||
src="https://a.fsdn.com/con/app/proj/identicons/screenshots/225506.jpg" |
|||
class="size-8 object-cover rounded-full" |
|||
/> |
|||
|
|||
<div> |
|||
<h3 class="font-semibold"> |
|||
{{ |
|||
record.giver.known ? record.giver.displayName : "Anonymous Giver" |
|||
}} |
|||
</h3> |
|||
<p class="ms-auto text-xs text-slate-500 italic"> |
|||
{{ friendlyDate }} |
|||
</p> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Record Image --> |
|||
<div |
|||
v-if="record.image" |
|||
class="bg-cover mb-6 -mx-3 sm:-mx-4" |
|||
:style="`background-image: url(${record.image});`" |
|||
> |
|||
<a |
|||
class="block bg-slate-100/50 backdrop-blur-md px-6 py-4 cursor-pointer" |
|||
@click="$emit('viewImage', record.image)" |
|||
> |
|||
<img |
|||
class="w-full h-auto max-w-lg max-h-96 object-contain mx-auto drop-shadow-md" |
|||
:src="record.image" |
|||
alt="Activity image" |
|||
@load="$emit('cacheImage', record.image)" |
|||
/> |
|||
</a> |
|||
</div> |
|||
|
|||
<div class="relative flex justify-between gap-4 max-w-lg mx-auto mb-5"> |
|||
<!-- Source --> |
|||
<div |
|||
class="w-28 sm:w-40 text-center bg-white border border-slate-200 rounded p-2 sm:p-3" |
|||
> |
|||
<div class="relative w-fit mx-auto"> |
|||
<template v-if="record.giver.profileImageUrl"> |
|||
<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 --> |
|||
<template v-if="record.providerPlanName"> |
|||
<ProjectIcon |
|||
:entity-id="record.providerPlanName" |
|||
:icon-size="48" |
|||
class="rounded size-[3rem] sm:size-[4rem] *:w-full *:h-full" |
|||
/> |
|||
</template> |
|||
<!-- Identicon for DIDs --> |
|||
<template v-else-if="record.giver.did"> |
|||
<img |
|||
:src="`https://a.fsdn.com/con/app/proj/identicons/screenshots/225506.jpg`" |
|||
class="rounded-full size-[3rem] sm:size-[4rem]" |
|||
alt="Identicon" |
|||
/> |
|||
</template> |
|||
<!-- Unknown Person --> |
|||
<template v-else> |
|||
<fa |
|||
icon="person-circle-question" |
|||
class="text-slate-300 text-[3rem] sm:text-[4rem]" |
|||
/> |
|||
</template> |
|||
</template> |
|||
</div> |
|||
<div class="text-xs mt-2 line-clamp-3 sm:line-clamp-2"> |
|||
<fa |
|||
:icon="record.providerPlanName ? 'building' : 'user'" |
|||
class="fa-fw text-slate-400" |
|||
/> |
|||
{{ record.giver.displayName }} |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Arrow --> |
|||
<div |
|||
class="absolute inset-x-28 sm:inset-x-40 mx-2 top-1/2 -translate-y-1/2" |
|||
> |
|||
<div class="text-sm text-center leading-none font-semibold"> |
|||
{{ fetchAmount }} |
|||
</div> |
|||
|
|||
<div class="flex items-center"> |
|||
<hr |
|||
class="grow border-t-[18px] sm:border-t-[24px] border-slate-300" |
|||
/> |
|||
|
|||
<div |
|||
class="shrink-0 w-0 h-0 border border-slate-300 border-t-[20px] sm:border-t-[25px] border-t-transparent border-b-[20px] sm:border-b-[25px] border-b-transparent border-s-[27px] sm:border-s-[34px] border-e-0" |
|||
></div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Destination --> |
|||
<div |
|||
class="w-28 sm:w-40 text-center bg-white border border-slate-200 rounded p-2 sm:p-3" |
|||
> |
|||
<div class="relative w-fit mx-auto"> |
|||
<template v-if="record.receiver.profileImageUrl"> |
|||
<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 --> |
|||
<template v-if="record.recipientProjectName"> |
|||
<ProjectIcon |
|||
:entity-id="record.recipientProjectName" |
|||
:icon-size="48" |
|||
class="rounded size-[3rem] sm:size-[4rem] *:w-full *:h-full" |
|||
/> |
|||
</template> |
|||
<!-- Identicon for DIDs --> |
|||
<template v-else-if="record.receiver.did"> |
|||
<img |
|||
:src="`https://a.fsdn.com/con/app/proj/identicons/screenshots/225506.jpg`" |
|||
class="rounded-full size-[3rem] sm:size-[4rem]" |
|||
alt="Identicon" |
|||
/> |
|||
</template> |
|||
<!-- Unknown Person --> |
|||
<template v-else> |
|||
<fa |
|||
icon="person-circle-question" |
|||
class="text-slate-300 text-[3rem] sm:text-[4rem]" |
|||
/> |
|||
</template> |
|||
</template> |
|||
</div> |
|||
<div class="text-xs mt-2 line-clamp-3 sm:line-clamp-2"> |
|||
<fa |
|||
:icon="record.recipientProjectName ? 'building' : 'user'" |
|||
class="fa-fw text-slate-400" |
|||
/> |
|||
{{ record.receiver.displayName }} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Description --> |
|||
<p class="font-medium"> |
|||
<a class="cursor-pointer" @click="$emit('loadClaim', record.jwtId)"> |
|||
{{ description }} |
|||
</a> |
|||
</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 class="cursor-pointer" @click="$emit('loadClaim', record.jwtId)"> |
|||
<fa icon="circle-info" class="fa-fw text-slate-500" /> |
|||
</a> |
|||
</div> |
|||
</li> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import { Component, Prop, Vue } from "vue-facing-decorator"; |
|||
import { GiveRecordWithContactInfo } from "../types"; |
|||
import EntityIcon from "./EntityIcon.vue"; |
|||
import { isGiveClaimType, notifyWhyCannotConfirm } from "../libs/util"; |
|||
import { containsHiddenDid } from "../libs/endorserServer"; |
|||
import ProjectIcon from "./ProjectIcon.vue"; |
|||
|
|||
@Component({ |
|||
components: { |
|||
EntityIcon, |
|||
ProjectIcon, |
|||
}, |
|||
}) |
|||
export default class ActivityListItem extends Vue { |
|||
@Prop() record!: GiveRecordWithContactInfo; |
|||
@Prop() lastViewedClaimId?: string; |
|||
@Prop() isRegistered!: boolean; |
|||
@Prop() activeDid!: string; |
|||
@Prop() confirmerIdList?: string[]; |
|||
|
|||
get fetchAmount(): string { |
|||
const claim = |
|||
(this.record.fullClaim as unknown).claim || this.record.fullClaim; |
|||
|
|||
const amount = claim.object?.amountOfThisGood |
|||
? this.displayAmount(claim.object.unitCode, claim.object.amountOfThisGood) |
|||
: ""; |
|||
|
|||
return amount; |
|||
} |
|||
|
|||
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 { |
|||
const claim = |
|||
(this.record.fullClaim as unknown).claim || this.record.fullClaim; |
|||
|
|||
if (!claim.description) { |
|||
return "something not described"; |
|||
} |
|||
|
|||
return `${claim.description}`; |
|||
} |
|||
|
|||
get subDescription(): string { |
|||
const participants = this.formatParticipantInfo(); |
|||
|
|||
return `${participants}`; |
|||
} |
|||
|
|||
private displayAmount(code: string, amt: number) { |
|||
return `${amt} ${this.currencyShortWordForCode(code, amt === 1)}`; |
|||
} |
|||
|
|||
private currencyShortWordForCode(unitCode: string, single: boolean) { |
|||
return unitCode === "HUR" ? (single ? "hour" : "hours") : unitCode; |
|||
} |
|||
|
|||
get formattedTimestamp() { |
|||
// Add your timestamp formatting logic here |
|||
return this.record.timestamp; |
|||
} |
|||
|
|||
get canConfirm(): boolean { |
|||
if (!this.isRegistered) return false; |
|||
if (!isGiveClaimType(this.record.fullClaim?.["@type"])) return false; |
|||
if (this.confirmerIdList?.includes(this.activeDid)) return false; |
|||
if (this.record.issuerDid === this.activeDid) return false; |
|||
if (containsHiddenDid(this.record.fullClaim)) return false; |
|||
return true; |
|||
} |
|||
|
|||
handleConfirmClick() { |
|||
if (!this.canConfirm) { |
|||
notifyWhyCannotConfirm( |
|||
this.$notify, |
|||
this.isRegistered, |
|||
this.record.fullClaim?.["@type"], |
|||
this.record, |
|||
this.activeDid, |
|||
this.confirmerIdList, |
|||
); |
|||
return; |
|||
} |
|||
|
|||
this.$emit("confirmClaim", this.record); |
|||
} |
|||
|
|||
get friendlyDate(): string { |
|||
const date = new Date(this.record.issuedAt); |
|||
return date.toLocaleDateString(undefined, { |
|||
year: "numeric", |
|||
month: "short", |
|||
day: "numeric", |
|||
}); |
|||
} |
|||
} |
|||
</script> |
@ -0,0 +1,20 @@ |
|||
export interface GiveRecordWithContactInfo { |
|||
jwtId: string; |
|||
fullClaim: unknown; // Replace with proper type
|
|||
giver: { |
|||
known: boolean; |
|||
displayName: string; |
|||
profileImageUrl?: string; |
|||
}; |
|||
receiver: { |
|||
known: boolean; |
|||
displayName: string; |
|||
profileImageUrl?: string; |
|||
}; |
|||
providerPlanName?: string; |
|||
recipientProjectName?: string; |
|||
description?: string; |
|||
subDescription?: string; |
|||
image?: string; |
|||
timestamp: string; |
|||
} |
@ -0,0 +1,232 @@ |
|||
<template> |
|||
<div class="deep-link-error"> |
|||
<div class="safe-area-spacer"></div> |
|||
<h1>Invalid Deep Link</h1> |
|||
<div class="error-details"> |
|||
<div class="error-message"> |
|||
<h3>Error Details</h3> |
|||
<p>{{ errorMessage }}</p> |
|||
<div v-if="errorCode" class="error-code"> |
|||
Error Code: <span>{{ errorCode }}</span> |
|||
</div> |
|||
</div> |
|||
<div v-if="originalPath" class="original-link"> |
|||
<h3>Attempted Link</h3> |
|||
<code>timesafari://{{ formattedPath }}</code> |
|||
<div class="debug-info"> |
|||
<h4>Parameters:</h4> |
|||
<pre>{{ JSON.stringify(route.params, null, 2) }}</pre> |
|||
<h4>Query:</h4> |
|||
<pre>{{ JSON.stringify(route.query, null, 2) }}</pre> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="actions"> |
|||
<button class="primary-button" @click="goHome">Go to Home</button> |
|||
<button class="secondary-button" @click="reportIssue"> |
|||
Report Issue |
|||
</button> |
|||
</div> |
|||
<div class="supported-links"> |
|||
<h2>Supported Deep Links</h2> |
|||
<ul> |
|||
<li v-for="(routeItem, index) in validRoutes" :key="index"> |
|||
<code>timesafari://{{ routeItem }}/:id</code> |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { computed, onMounted } from "vue"; |
|||
import { useRoute, useRouter } from "vue-router"; |
|||
import { VALID_DEEP_LINK_ROUTES } from "../types/deepLinks"; |
|||
import { logConsoleAndDb } from "../db"; |
|||
import { logger } from "../utils/logger"; |
|||
|
|||
const route = useRoute(); |
|||
const router = useRouter(); |
|||
|
|||
// Extract error information from query params |
|||
const errorCode = computed( |
|||
() => (route.query.errorCode as string) || "UNKNOWN_ERROR", |
|||
); |
|||
const errorMessage = computed( |
|||
() => |
|||
(route.query.message as string) || |
|||
"The deep link you followed is invalid or not supported.", |
|||
); |
|||
const originalPath = computed(() => route.query.originalPath as string); |
|||
const validRoutes = VALID_DEEP_LINK_ROUTES; |
|||
|
|||
// Format the path and include any parameters |
|||
const formattedPath = computed(() => { |
|||
if (!originalPath.value) return ""; |
|||
const path = originalPath.value.replace(/^\/+/, ""); |
|||
|
|||
// Log for debugging |
|||
logger.log("Original Path:", originalPath.value); |
|||
logger.log("Route Params:", route.params); |
|||
logger.log("Route Query:", route.query); |
|||
|
|||
return path; |
|||
}); |
|||
|
|||
// Navigation methods |
|||
const goHome = () => router.replace({ name: "home" }); |
|||
const reportIssue = () => { |
|||
// Open a support form or email |
|||
window.open( |
|||
"mailto:support@timesafari.app?subject=Invalid Deep Link&body=" + |
|||
encodeURIComponent( |
|||
`I encountered an error with a deep link: timesafari://${originalPath.value}\nError: ${errorMessage.value}`, |
|||
), |
|||
); |
|||
}; |
|||
|
|||
// Log the error for analytics |
|||
onMounted(() => { |
|||
logConsoleAndDb( |
|||
`[DeepLink] Error page displayed for path: ${originalPath.value}, code: ${errorCode.value}, params: ${JSON.stringify(route.params)}`, |
|||
true, |
|||
); |
|||
}); |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.deep-link-error { |
|||
padding-top: 60px; |
|||
padding-left: 20px; |
|||
padding-right: 20px; |
|||
max-width: 600px; |
|||
margin: 0 auto; |
|||
} |
|||
|
|||
.safe-area-spacer { |
|||
height: env(safe-area-inset-top); |
|||
} |
|||
|
|||
h1 { |
|||
color: #ff4444; |
|||
margin-bottom: 24px; |
|||
} |
|||
|
|||
h2, |
|||
h3 { |
|||
color: #333; |
|||
margin-bottom: 12px; |
|||
} |
|||
|
|||
.error-details { |
|||
background-color: #f8f8f8; |
|||
border-radius: 8px; |
|||
padding: 20px; |
|||
margin-bottom: 24px; |
|||
} |
|||
|
|||
.error-message { |
|||
margin-bottom: 20px; |
|||
} |
|||
|
|||
.error-message p { |
|||
color: #666; |
|||
line-height: 1.5; |
|||
margin-bottom: 12px; |
|||
} |
|||
|
|||
.error-code { |
|||
font-family: monospace; |
|||
color: #666; |
|||
margin-top: 8px; |
|||
} |
|||
|
|||
.error-code span { |
|||
background-color: #eee; |
|||
padding: 2px 6px; |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
.original-link { |
|||
padding: 12px; |
|||
background-color: #fff; |
|||
border: 1px solid #ddd; |
|||
border-radius: 6px; |
|||
} |
|||
|
|||
.original-link code { |
|||
color: #0066cc; |
|||
font-family: monospace; |
|||
word-break: break-all; |
|||
} |
|||
|
|||
.actions { |
|||
margin: 24px 0; |
|||
display: flex; |
|||
gap: 12px; |
|||
} |
|||
|
|||
.actions button { |
|||
padding: 10px 20px; |
|||
border-radius: 6px; |
|||
border: none; |
|||
font-weight: 500; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.primary-button { |
|||
background-color: #007aff; |
|||
color: white; |
|||
} |
|||
|
|||
.secondary-button { |
|||
background-color: #f2f2f2; |
|||
color: #333; |
|||
} |
|||
|
|||
.supported-links { |
|||
margin-top: 32px; |
|||
} |
|||
|
|||
.supported-links ul { |
|||
list-style: none; |
|||
padding: 0; |
|||
} |
|||
|
|||
.supported-links li { |
|||
padding: 8px 12px; |
|||
background-color: #f8f8f8; |
|||
border-radius: 4px; |
|||
margin-bottom: 8px; |
|||
} |
|||
|
|||
.supported-links code { |
|||
font-family: monospace; |
|||
color: #0066cc; |
|||
} |
|||
|
|||
.debug-info { |
|||
margin-top: 16px; |
|||
padding: 12px; |
|||
background-color: #f0f0f0; |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
.debug-info h4 { |
|||
margin: 8px 0; |
|||
color: #666; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.debug-info pre { |
|||
white-space: pre-wrap; |
|||
word-break: break-all; |
|||
font-family: monospace; |
|||
font-size: 12px; |
|||
color: #333; |
|||
background-color: #fff; |
|||
padding: 8px; |
|||
border-radius: 4px; |
|||
margin: 4px 0; |
|||
} |
|||
</style> |
@ -0,0 +1,98 @@ |
|||
<!-- This is useful in an environment where the download doesn't work. --> |
|||
<template> |
|||
<QuickNav selected="" /> |
|||
<TopMessage /> |
|||
|
|||
<!-- CONTENT --> |
|||
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto"> |
|||
<!-- Back Button --> |
|||
<div class="relative px-7"> |
|||
<h1 |
|||
class="text-lg text-center font-light px-2 py-1 absolute -left-2 -top-1" |
|||
@click="$router.back()" |
|||
> |
|||
<font-awesome icon="chevron-left" class="mr-2" /> |
|||
</h1> |
|||
</div> |
|||
|
|||
<!-- Heading --> |
|||
<h1 id="ViewHeading" class="text-4xl text-center font-light mb-6">Logs</h1> |
|||
|
|||
<!-- Error Message --> |
|||
<div |
|||
v-if="error" |
|||
class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4" |
|||
> |
|||
<span>{{ error }}</span> |
|||
</div> |
|||
|
|||
<!-- Log Content --> |
|||
<div v-if="loading" class="text-center"> |
|||
<font-awesome icon="spinner" class="fa-spin text-slate-400" /> |
|||
Loading logs... |
|||
</div> |
|||
<div v-else-if="!logs.length" class="text-center text-slate-500"> |
|||
No logs found. |
|||
</div> |
|||
<div v-else> |
|||
<div v-for="(log, index) in logs" :key="index" class="mb-8"> |
|||
<h2 class="text-lg font-semibold mb-2">{{ log.date }}</h2> |
|||
<pre |
|||
class="bg-slate-100 p-4 rounded-md overflow-x-auto whitespace-pre-wrap" |
|||
>{{ log.message }}</pre |
|||
> |
|||
</div> |
|||
</div> |
|||
</section> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import { Component, Vue } from "vue-facing-decorator"; |
|||
import { Router } from "vue-router"; |
|||
|
|||
import QuickNav from "../components/QuickNav.vue"; |
|||
import TopMessage from "../components/TopMessage.vue"; |
|||
import { db } from "../db/index"; |
|||
import { Log } from "../db/tables/logs"; |
|||
import { logger } from "../utils/logger"; |
|||
|
|||
@Component({ |
|||
components: { |
|||
QuickNav, |
|||
TopMessage, |
|||
}, |
|||
}) |
|||
export default class LogView extends Vue { |
|||
$router!: Router; |
|||
|
|||
loading = true; |
|||
logs: Log[] = []; |
|||
error: string | null = null; |
|||
|
|||
async mounted() { |
|||
await this.loadLogs(); |
|||
} |
|||
|
|||
async loadLogs() { |
|||
try { |
|||
this.error = null; // Clear any previous errors |
|||
await db.open(); |
|||
// Get all logs and sort by date in reverse chronological order |
|||
const allLogs = await db.logs.toArray(); |
|||
this.logs = allLogs.sort((a, b) => { |
|||
const dateA = new Date(a.date); |
|||
const dateB = new Date(b.date); |
|||
return dateB.getTime() - dateA.getTime(); |
|||
}); |
|||
} catch (error) { |
|||
logger.error("Error loading logs:", error); |
|||
this.error = |
|||
error instanceof Error |
|||
? error.message |
|||
: `An unknown error occurred while loading logs: ${String(error)}`; |
|||
} finally { |
|||
this.loading = false; |
|||
} |
|||
} |
|||
} |
|||
</script> |
@ -1,123 +0,0 @@ |
|||
#!/bin/bash |
|||
|
|||
# Configurable pause duration (in seconds) |
|||
PAUSE_DURATION=2 |
|||
MANUAL_CONTINUE=true |
|||
|
|||
# Function to test deep link |
|||
test_link() { |
|||
echo "----------------------------------------" |
|||
echo "Testing: $1" |
|||
echo "Description: $2" |
|||
echo "----------------------------------------" |
|||
adb shell am start -W -a android.intent.action.VIEW -d "$1" app.timesafari.app |
|||
|
|||
if [ "$MANUAL_CONTINUE" = true ]; then |
|||
read -p "Press Enter to continue to next test..." |
|||
else |
|||
sleep $PAUSE_DURATION |
|||
fi |
|||
} |
|||
|
|||
# Allow command line override of pause settings |
|||
while getopts "t:a" opt; do |
|||
case $opt in |
|||
t) PAUSE_DURATION=$OPTARG ;; |
|||
a) MANUAL_CONTINUE=false ;; |
|||
esac |
|||
done |
|||
|
|||
echo "Starting TimeSafari Deep Link Tests" |
|||
echo "======================================" |
|||
echo "Pause duration: $PAUSE_DURATION seconds" |
|||
echo "Manual continue: $MANUAL_CONTINUE" |
|||
|
|||
# Contact Import Routes |
|||
echo "\nTesting Contact Import Routes:" |
|||
|
|||
# 1. Direct Query Parameter Import (URL-encoded JSON) |
|||
QUERY_CONTACTS='[{ |
|||
"did":"did:ethr:" |
|||
},{ |
|||
"did":"did:ethr:", |
|||
"name":"Jordan", |
|||
"nextPubKeyHashB64":"IBfRZfwdzeKOzqCx8b+WlLpMJHOAT9ZknIDJo7F3rZE=", |
|||
"publicKeyBase64":"A1eIndfaxgMpVwyD5dYe74DgjuIo5SwPZFCcLdOemjf" |
|||
}]' |
|||
ENCODED_CONTACTS=$(echo $QUERY_CONTACTS | jq -c | python3 -c "import urllib.parse; print(urllib.parse.quote(input()))") |
|||
test_link "timesafari://contact-import?contacts=$ENCODED_CONTACTS" "Bulk import via query parameters" |
|||
|
|||
# 2. JWT Path Imports |
|||
# Original JWT with multiple contacts |
|||
BULK_JWT="eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE3NDA3NDA0NTMsImNvbnRhY3RzIjpbeyJkaWQiOiJkaWQ6ZXRocjoweGY5NjlBNURlRTdhNTgwNmQxQzM3ZjRFYzQ5QTU1NUFiOTc5MTEwODkifSx7ImRpZCI6ImRpZDpldGhyOjB4RkVkM2I0MTY5NDZiMjNGM0Y0NzI3OTkwNTMxNDRCNEUzNDE1NUI1YiIsIm5hbWUiOiJKb3JkYW4iLCJuZXh0UHViS2V5SGFzaEI2NCI6IklCZlJaZndkemVLT3pxQ3g4YitXbExwTUpIT0FUOVprbklESm83RjNyWkU9IiwicHVibGljS2V5QmFzZTY0IjoiQTFlSW5kZmF4Z01wVnd5RDVkWWU3NERnanQ5SW81U3dQWkZDY0xkT2VtamYifV0sImlzcyI6ImRpZDpldGhyOjB4RDUzMTE0ODMwRDRhNUQ5MDQxNkI0M0ZjOTlhMjViMGRGOGJiMUJBZCJ9.yKEFounxUGU9-grAMFHA12dif9BKYkftg8F3wAIcFYh0H_k1tevjEYyD1fvAyIxYxK5xR0E8moqMhi78ipJXcg" |
|||
test_link "timesafari://contact-import/$BULK_JWT" "Multiple contacts via JWT" |
|||
|
|||
# 3. Contact Page JWT Redirect |
|||
test_link "timesafari://contacts?contactJwt=$BULK_JWT" "Multiple contacts redirect" |
|||
|
|||
# Contact Management Routes |
|||
test_link "timesafari://contact-edit/did:ethr:" \ |
|||
"Edit first contact" |
|||
|
|||
# Error Cases |
|||
echo "\nTesting Contact Import Error Cases:" |
|||
test_link "timesafari://contact-import/eyJJTlZBTElEIn0" "Invalid JWT format" |
|||
test_link "timesafari://contact-import?contacts=[{invalid:data}]" "Invalid contact data" |
|||
|
|||
# Original Routes (preserved from previous version) |
|||
echo "\nTesting Other Routes:" |
|||
|
|||
# Test claim routes |
|||
echo "\nTesting Claim Routes:" |
|||
test_link "timesafari://claim/01JMAAFZRNSRTQ0EBSD70A8E1H" |
|||
test_link "timesafari://claim-cert/01JMAAFZRNSRTQ0EBSD70A8E1H" |
|||
|
|||
# Test project routes |
|||
echo "\nTesting Project Routes:" |
|||
test_link "timesafari://project/https%3A%2F%2Fendorser.ch%2Fentity%2F01JKW0QZX1XVCVZV85VXAMB31R" |
|||
|
|||
# Test gift routes |
|||
echo "\nTesting Gift Routes:" |
|||
test_link "timesafari://confirm-gift/01JMTC8T961KFPP2N8ZB92ER4K" |
|||
|
|||
# Test offer routes |
|||
echo "\nTesting Offer Routes:" |
|||
test_link "timesafari://offer-details/101" |
|||
|
|||
# Test complex query parameters |
|||
echo "\nTesting Complex Query Parameters:" |
|||
test_link "timesafari://contact-import/jwt?contacts=%5B%7B%22name%22%3A%22Test%22%7D%5D" |
|||
|
|||
# New test cases |
|||
echo "\nTesting DID Routes:" |
|||
test_link "timesafari://did/did:example:123" |
|||
test_link "timesafari://did/did:example:456?view=details" |
|||
|
|||
echo "\nTesting Additional Contact Routes:" |
|||
test_link "timesafari://contact-import/jwt?contacts=%5B%7B%22did%22%3A%22did%3Aexample%3A123%22%7D%5D" |
|||
test_link "timesafari://contact-edit/did:example:123?action=edit" |
|||
|
|||
echo "\nTesting Error Cases:" |
|||
test_link "timesafari://invalid-route/123" |
|||
test_link "timesafari://claim/123?view=invalid" |
|||
test_link "timesafari://did/invalid-did" |
|||
|
|||
# Single invite JWT test |
|||
# Header: {"typ":"JWT","alg":"ES256K"} |
|||
# Payload: { |
|||
# "iat": 1740740453, |
|||
# "contact": { |
|||
# "did": "did:ethr:", |
|||
# "name": "Jordan", |
|||
# "nextPubKeyHashB64": "IBfRZfwdzeKOzqCx8b+WlLpMJHOAT9ZknIDJo7F3rZE=", |
|||
# "publicKeyBase64": "A1eIndfaxgMpVwyD5dYe74DgjuIo5SwPZFCcLdOemjf" |
|||
# }, |
|||
# "iss": "did:ethr:" |
|||
# } |
|||
|
|||
SINGLE_INVITE_JWT="eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE3NDA3NDA0NTMsImNvbnRhY3QiOnsiZGlkIjoiZGlkOmV0aHI6MHhGRWQzYjQxNjk0NmIyM0YzRjQ3Mjc5OTA1MzE0NEI0RTM0MTU1QjViIiwibmFtZSI6IkpvcmRhbiIsIm5leHRQdWJLZXlIYXNoQjY0IjoiSUJmUlpmd2R6ZUtPenFDeDhiK1dsTHBNSkhPQVQ5WmtuSURKbzdGM3JaRT0iLCJwdWJsaWNLZXlCYXNlNjQiOiJBMWVJbmRmYXhnTXBWd3lENWRZZTc0RGdqdUlvNVN3UFpGQ2NMZEtlbWpmIn0sImlzcyI6ImRpZDpldGhyOjB4RDUzMTE0ODMwRDRhNUQ5MDQxNkI0M0ZjOTlhMjViMGRGOGJiMUJBZCJ9.yKEFounxUGU9-grAMFHA12dif9BKYkftg8F3wAIcFYh0H_k1tevjEYyD1fvAyIxYxK5xR0E8moqMhi78ipJXcg" |
|||
|
|||
test_link "timesafari://invite-one-accept/$SINGLE_INVITE_JWT" "Single contact invite via JWT" |
|||
|
|||
echo "\nDeep link testing complete" |
|||
echo "======================================" |
Loading…
Reference in new issue