feat: estructura base OpenSpec + Skills + arquitectura limpia Capacitor+Dextie
43
README.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# ToldoConjunto
|
||||
|
||||
Sistema para el Circuito de Economía Solidaria de Támesis (CESTA) para gestionar inventario, ventas, gastos y cuadre de caja en las ventas dominicales.
|
||||
|
||||
## Arquitectura
|
||||
|
||||
- **Apps**: `apps/toldo-app` (Vite + Vue 3 + Capacitor)
|
||||
- **Packages**: Arquitectura limpia con `core`, `data`, `services`, `ui`
|
||||
- **Storage**: Dexie.js sobre IndexedDB (offline-first)
|
||||
- **Packaging**: Capacitor para generar APK Android
|
||||
- **Testing**: Vitest + TDD
|
||||
|
||||
## Características
|
||||
|
||||
- Inventario con diferencias vs día anterior
|
||||
- Ventas en efectivo y QR
|
||||
- Productos diferenciados por productor
|
||||
- Gastos
|
||||
- Cuadre de caja con descuadres
|
||||
- Ajustes con motivo obligatorio (trazables)
|
||||
- Reportes por productor y consolidados
|
||||
- Ingresos entre semana
|
||||
- Exportar/Importar JSON versionado
|
||||
- Funciona offline, empaquetable como APK
|
||||
|
||||
## Desarrollo
|
||||
|
||||
```bash
|
||||
cd apps/toldo-app
|
||||
npm install
|
||||
npm run dev
|
||||
npm test
|
||||
npm run build
|
||||
npx cap sync android
|
||||
```
|
||||
|
||||
## OpenSpec & Skills
|
||||
|
||||
Ver `openspec/changes/add-toldo-poc-capacitor/` y `skills/` para especificaciones y guías arquitectónicas.
|
||||
|
||||
## Licencia
|
||||
|
||||
Software Libre
|
||||
1
ToldoConjuntoPOC/README.md~
Normal file
@@ -0,0 +1 @@
|
||||
# Toldo Conjunto POC (Prueba de Concepto)
|
||||
24
apps/toldo-app/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
3
apps/toldo-app/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
||||
5
apps/toldo-app/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Vue 3 + TypeScript + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
|
||||
101
apps/toldo-app/android/.gitignore
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
|
||||
|
||||
# Built application files
|
||||
*.apk
|
||||
*.aar
|
||||
*.ap_
|
||||
*.aab
|
||||
|
||||
# Files for the ART/Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
bin/
|
||||
gen/
|
||||
out/
|
||||
# Uncomment the following line in case you need and you don't have the release build type files in your app
|
||||
# release/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
# Android Studio Navigation editor temp files
|
||||
.navigation/
|
||||
|
||||
# Android Studio captures folder
|
||||
captures/
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea/workspace.xml
|
||||
.idea/tasks.xml
|
||||
.idea/gradle.xml
|
||||
.idea/assetWizardSettings.xml
|
||||
.idea/dictionaries
|
||||
.idea/libraries
|
||||
# Android Studio 3 in .gitignore file.
|
||||
.idea/caches
|
||||
.idea/modules.xml
|
||||
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
||||
.idea/navEditor.xml
|
||||
|
||||
# Keystore files
|
||||
# Uncomment the following lines if you do not want to check your keystore files in.
|
||||
#*.jks
|
||||
#*.keystore
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
.cxx/
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
# google-services.json
|
||||
|
||||
# Freeline
|
||||
freeline.py
|
||||
freeline/
|
||||
freeline_project_description.json
|
||||
|
||||
# fastlane
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots
|
||||
fastlane/test_output
|
||||
fastlane/readme.md
|
||||
|
||||
# Version control
|
||||
vcs.xml
|
||||
|
||||
# lint
|
||||
lint/intermediates/
|
||||
lint/generated/
|
||||
lint/outputs/
|
||||
lint/tmp/
|
||||
# lint/reports/
|
||||
|
||||
# Android Profiling
|
||||
*.hprof
|
||||
|
||||
# Cordova plugins for Capacitor
|
||||
capacitor-cordova-android-plugins
|
||||
|
||||
# Copied web assets
|
||||
app/src/main/assets/public
|
||||
|
||||
# Generated Config files
|
||||
app/src/main/assets/capacitor.config.json
|
||||
app/src/main/assets/capacitor.plugins.json
|
||||
app/src/main/res/xml/config.xml
|
||||
2
apps/toldo-app/android/app/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/build/*
|
||||
!/build/.npmkeep
|
||||
54
apps/toldo-app/android/app/build.gradle
Normal file
@@ -0,0 +1,54 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
namespace "com.ilusion.toldoconjunto"
|
||||
compileSdk rootProject.ext.compileSdkVersion
|
||||
defaultConfig {
|
||||
applicationId "com.ilusion.toldoconjunto"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
|
||||
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
flatDir{
|
||||
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
|
||||
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
|
||||
implementation project(':capacitor-android')
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
||||
implementation project(':capacitor-cordova-android-plugins')
|
||||
}
|
||||
|
||||
apply from: 'capacitor.build.gradle'
|
||||
|
||||
try {
|
||||
def servicesJSON = file('google-services.json')
|
||||
if (servicesJSON.text) {
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
}
|
||||
} catch(Exception e) {
|
||||
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
|
||||
}
|
||||
21
apps/toldo-app/android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.getcapacitor.myapp;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
|
||||
@Test
|
||||
public void useAppContext() throws Exception {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
|
||||
assertEquals("com.getcapacitor.app", appContext.getPackageName());
|
||||
}
|
||||
}
|
||||
41
apps/toldo-app/android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<activity
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/title_activity_main"
|
||||
android:theme="@style/AppTheme.NoActionBarLaunch"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths"></meta-data>
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
<!-- Permissions -->
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
</manifest>
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.ilusion.toldoconjunto;
|
||||
|
||||
import com.getcapacitor.BridgeActivity;
|
||||
|
||||
public class MainActivity extends BridgeActivity {}
|
||||
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,34 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="78.5885"
|
||||
android:endY="90.9159"
|
||||
android:startX="48.7653"
|
||||
android:startY="61.0927"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1" />
|
||||
</vector>
|
||||
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillColor="#26A69A"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
</vector>
|
||||
BIN
apps/toldo-app/android/app/src/main/res/drawable/splash.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<WebView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FFFFFF</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<string name="app_name">ToldoConjunto</string>
|
||||
<string name="title_activity_main">ToldoConjunto</string>
|
||||
<string name="package_name">com.ilusion.toldoconjunto</string>
|
||||
<string name="custom_url_scheme">com.ilusion.toldoconjunto</string>
|
||||
</resources>
|
||||
22
apps/toldo-app/android/app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="android:background">@null</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
|
||||
<item name="android:background">@drawable/splash</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<external-path name="my_images" path="." />
|
||||
<cache-path name="my_cache_images" path="." />
|
||||
</paths>
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.getcapacitor.myapp;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
|
||||
@Test
|
||||
public void addition_isCorrect() throws Exception {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
||||
29
apps/toldo-app/android/build.gradle
Normal file
@@ -0,0 +1,29 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.2.1'
|
||||
classpath 'com.google.gms:google-services:4.4.0'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "variables.gradle"
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
22
apps/toldo-app/android/gradle.properties
Normal file
@@ -0,0 +1,22 @@
|
||||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
BIN
apps/toldo-app/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
7
apps/toldo-app/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
248
apps/toldo-app/android/gradlew
vendored
Executable file
@@ -0,0 +1,248 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
92
apps/toldo-app/android/gradlew.bat
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
5
apps/toldo-app/android/settings.gradle
Normal file
@@ -0,0 +1,5 @@
|
||||
include ':app'
|
||||
include ':capacitor-cordova-android-plugins'
|
||||
project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
|
||||
|
||||
apply from: 'capacitor.settings.gradle'
|
||||
16
apps/toldo-app/android/variables.gradle
Normal file
@@ -0,0 +1,16 @@
|
||||
ext {
|
||||
minSdkVersion = 22
|
||||
compileSdkVersion = 34
|
||||
targetSdkVersion = 34
|
||||
androidxActivityVersion = '1.8.0'
|
||||
androidxAppCompatVersion = '1.6.1'
|
||||
androidxCoordinatorLayoutVersion = '1.2.0'
|
||||
androidxCoreVersion = '1.12.0'
|
||||
androidxFragmentVersion = '1.6.2'
|
||||
coreSplashScreenVersion = '1.0.1'
|
||||
androidxWebkitVersion = '1.9.0'
|
||||
junitVersion = '4.13.2'
|
||||
androidxJunitVersion = '1.1.5'
|
||||
androidxEspressoCoreVersion = '3.5.1'
|
||||
cordovaAndroidVersion = '10.1.1'
|
||||
}
|
||||
9
apps/toldo-app/capacitor.config.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { CapacitorConfig } from '@capacitor/cli';
|
||||
|
||||
const config: CapacitorConfig = {
|
||||
appId: 'com.ilusion.toldoconjunto',
|
||||
appName: 'ToldoConjunto',
|
||||
webDir: 'dist'
|
||||
};
|
||||
|
||||
export default config;
|
||||
13
apps/toldo-app/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>toldo-app</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
3003
apps/toldo-app/package-lock.json
generated
Normal file
37
apps/toldo-app/package.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "toldo-app",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc -b && vite build",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest",
|
||||
"test:run": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@capacitor/android": "^6.2.1",
|
||||
"@capacitor/core": "^6.2.1",
|
||||
"@toldo/core": "workspace:*",
|
||||
"@toldo/data": "workspace:*",
|
||||
"@toldo/services": "workspace:*",
|
||||
"@toldo/ui": "workspace:*",
|
||||
"dexie": "^4.0.10",
|
||||
"vue": "^3.5.44"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/cli": "^6.2.1",
|
||||
"@testing-library/vue": "^8.1.0",
|
||||
"@types/node": "^24.19.2",
|
||||
"@vitejs/plugin-vue": "^6.0.10",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"@vue/tsconfig": "^0.9.1",
|
||||
"fake-indexeddb": "^5.0.2",
|
||||
"jsdom": "^25.0.1",
|
||||
"typescript": "~6.0.3",
|
||||
"vite": "^8.3.4",
|
||||
"vitest": "^2.1.8",
|
||||
"vue-tsc": "^3.3.13"
|
||||
}
|
||||
}
|
||||
1
apps/toldo-app/public/favicon.svg
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
24
apps/toldo-app/public/icons.svg
Normal file
@@ -0,0 +1,24 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<symbol id="bluesky-icon" viewBox="0 0 16 17">
|
||||
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
|
||||
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
|
||||
</symbol>
|
||||
<symbol id="discord-icon" viewBox="0 0 20 19">
|
||||
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
|
||||
</symbol>
|
||||
<symbol id="documentation-icon" viewBox="0 0 21 20">
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
|
||||
</symbol>
|
||||
<symbol id="github-icon" viewBox="0 0 19 19">
|
||||
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
|
||||
</symbol>
|
||||
<symbol id="social-icon" viewBox="0 0 20 20">
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
|
||||
</symbol>
|
||||
<symbol id="x-icon" viewBox="0 0 19 19">
|
||||
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
7
apps/toldo-app/src/App.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import HelloWorld from './components/HelloWorld.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<HelloWorld />
|
||||
</template>
|
||||
BIN
apps/toldo-app/src/assets/hero.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
1
apps/toldo-app/src/assets/vite.svg
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
1
apps/toldo-app/src/assets/vue.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 496 B |
95
apps/toldo-app/src/components/HelloWorld.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import heroImg from '../assets/hero.png'
|
||||
import viteLogo from '../assets/vite.svg'
|
||||
import vueLogo from '../assets/vue.svg'
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section id="center">
|
||||
<div class="hero">
|
||||
<img :src="heroImg" class="base" width="170" height="179" alt="" />
|
||||
<img :src="vueLogo" class="framework" alt="Vue logo" />
|
||||
<img :src="viteLogo" class="vite" alt="Vite logo" />
|
||||
</div>
|
||||
<div>
|
||||
<h1>Get started</h1>
|
||||
<p>Edit <code>src/App.vue</code> and save to test <code>HMR</code></p>
|
||||
</div>
|
||||
<button type="button" class="counter" @click="count++">
|
||||
Count is {{ count }}
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<div class="ticks"></div>
|
||||
|
||||
<section id="next-steps">
|
||||
<div id="docs">
|
||||
<svg class="icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#documentation-icon"></use>
|
||||
</svg>
|
||||
<h2>Documentation</h2>
|
||||
<p>Your questions, answered</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://vite.dev/" target="_blank">
|
||||
<img class="logo" :src="viteLogo" alt="" />
|
||||
Explore Vite
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://vuejs.org/" target="_blank">
|
||||
<img class="button-icon" :src="vueLogo" alt="" />
|
||||
Learn more
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="social">
|
||||
<svg class="icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#social-icon"></use>
|
||||
</svg>
|
||||
<h2>Connect with us</h2>
|
||||
<p>Join the Vite community</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://github.com/vitejs/vite" target="_blank">
|
||||
<svg class="button-icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#github-icon"></use>
|
||||
</svg>
|
||||
GitHub
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://chat.vite.dev/" target="_blank">
|
||||
<svg class="button-icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#discord-icon"></use>
|
||||
</svg>
|
||||
Discord
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://x.com/vite_js" target="_blank">
|
||||
<svg class="button-icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#x-icon"></use>
|
||||
</svg>
|
||||
X.com
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://bsky.app/profile/vite.dev" target="_blank">
|
||||
<svg class="button-icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#bluesky-icon"></use>
|
||||
</svg>
|
||||
Bluesky
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="ticks"></div>
|
||||
<section id="spacer"></section>
|
||||
</template>
|
||||
5
apps/toldo-app/src/main.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { createApp } from 'vue'
|
||||
import './style.css'
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
||||
296
apps/toldo-app/src/style.css
Normal file
@@ -0,0 +1,296 @@
|
||||
:root {
|
||||
--text: #6b6375;
|
||||
--text-h: #08060d;
|
||||
--bg: #fff;
|
||||
--border: #e5e4e7;
|
||||
--code-bg: #f4f3ec;
|
||||
--accent: #aa3bff;
|
||||
--accent-bg: rgba(170, 59, 255, 0.1);
|
||||
--accent-border: rgba(170, 59, 255, 0.5);
|
||||
--social-bg: rgba(244, 243, 236, 0.5);
|
||||
--shadow:
|
||||
rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
|
||||
|
||||
--sans: system-ui, 'Segoe UI', Roboto, sans-serif;
|
||||
--heading: system-ui, 'Segoe UI', Roboto, sans-serif;
|
||||
--mono: ui-monospace, Consolas, monospace;
|
||||
|
||||
font: 18px/145% var(--sans);
|
||||
letter-spacing: 0.18px;
|
||||
color-scheme: light dark;
|
||||
color: var(--text);
|
||||
background: var(--bg);
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--text: #9ca3af;
|
||||
--text-h: #f3f4f6;
|
||||
--bg: #16171d;
|
||||
--border: #2e303a;
|
||||
--code-bg: #1f2028;
|
||||
--accent: #c084fc;
|
||||
--accent-bg: rgba(192, 132, 252, 0.15);
|
||||
--accent-border: rgba(192, 132, 252, 0.5);
|
||||
--social-bg: rgba(47, 48, 58, 0.5);
|
||||
--shadow:
|
||||
rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
|
||||
}
|
||||
|
||||
#social .button-icon {
|
||||
filter: invert(1) brightness(2);
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
font-family: var(--heading);
|
||||
font-weight: 500;
|
||||
color: var(--text-h);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 56px;
|
||||
letter-spacing: -1.68px;
|
||||
margin: 32px 0;
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 36px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
}
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
line-height: 118%;
|
||||
letter-spacing: -0.24px;
|
||||
margin: 0 0 8px;
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
code,
|
||||
.counter {
|
||||
font-family: var(--mono);
|
||||
display: inline-flex;
|
||||
border-radius: 4px;
|
||||
color: var(--text-h);
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 15px;
|
||||
line-height: 135%;
|
||||
padding: 4px 8px;
|
||||
background: var(--code-bg);
|
||||
}
|
||||
|
||||
.counter {
|
||||
font-size: 16px;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
color: var(--accent);
|
||||
background: var(--accent-bg);
|
||||
border: 2px solid transparent;
|
||||
transition: border-color 0.3s;
|
||||
margin-bottom: 24px;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--accent-border);
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.hero {
|
||||
position: relative;
|
||||
|
||||
.base,
|
||||
.framework,
|
||||
.vite {
|
||||
inset-inline: 0;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.base {
|
||||
width: 170px;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.framework,
|
||||
.vite {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.framework {
|
||||
z-index: 1;
|
||||
top: 34px;
|
||||
height: 28px;
|
||||
transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
|
||||
scale(1.4);
|
||||
}
|
||||
|
||||
.vite {
|
||||
z-index: 0;
|
||||
top: 107px;
|
||||
height: 26px;
|
||||
width: auto;
|
||||
transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
|
||||
scale(0.8);
|
||||
}
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 1126px;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
border-inline: 1px solid var(--border);
|
||||
min-height: 100svh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 25px;
|
||||
place-content: center;
|
||||
place-items: center;
|
||||
flex-grow: 1;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
padding: 32px 20px 24px;
|
||||
gap: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
#next-steps {
|
||||
display: flex;
|
||||
border-top: 1px solid var(--border);
|
||||
text-align: left;
|
||||
|
||||
& > div {
|
||||
flex: 1 1 0;
|
||||
padding: 32px;
|
||||
@media (max-width: 1024px) {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-bottom: 16px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
#docs {
|
||||
border-right: 1px solid var(--border);
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
}
|
||||
|
||||
#next-steps ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin: 32px 0 0;
|
||||
|
||||
.logo {
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--text-h);
|
||||
font-size: 16px;
|
||||
border-radius: 6px;
|
||||
background: var(--social-bg);
|
||||
display: flex;
|
||||
padding: 6px 12px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-decoration: none;
|
||||
transition: box-shadow 0.3s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.button-icon {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
margin-top: 20px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
|
||||
li {
|
||||
flex: 1 1 calc(50% - 8px);
|
||||
}
|
||||
|
||||
a {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#spacer {
|
||||
height: 88px;
|
||||
border-top: 1px solid var(--border);
|
||||
@media (max-width: 1024px) {
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.ticks {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -4.5px;
|
||||
border: 5px solid transparent;
|
||||
}
|
||||
|
||||
&::before {
|
||||
left: 0;
|
||||
border-left-color: var(--border);
|
||||
}
|
||||
&::after {
|
||||
right: 0;
|
||||
border-right-color: var(--border);
|
||||
}
|
||||
}
|
||||
1
apps/toldo-app/tests/setup.ts
Normal file
@@ -0,0 +1 @@
|
||||
import 'fake-indexeddb/auto';
|
||||
15
apps/toldo-app/tsconfig.app.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"types": ["vite/client"],
|
||||
"allowArbitraryExtensions": true,
|
||||
|
||||
/* Linting */
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||
}
|
||||
7
apps/toldo-app/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
23
apps/toldo-app/tsconfig.node.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "es2023",
|
||||
"lib": ["ES2023"],
|
||||
"types": ["node"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"module": "nodenext",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
15
apps/toldo-app/vite.config.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import path from 'path'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@core': path.resolve(__dirname, '../../packages/core/src'),
|
||||
'@data': path.resolve(__dirname, '../../packages/data/src'),
|
||||
'@services': path.resolve(__dirname, '../../packages/services/src'),
|
||||
'@ui': path.resolve(__dirname, '../../packages/ui/src'),
|
||||
},
|
||||
},
|
||||
})
|
||||
20
apps/toldo-app/vitest.config.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import path from 'path';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
setupFiles: ['./tests/setup.ts'],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@core': path.resolve(__dirname, '../../packages/core/src'),
|
||||
'@data': path.resolve(__dirname, '../../packages/data/src'),
|
||||
'@services': path.resolve(__dirname, '../../packages/services/src'),
|
||||
'@ui': path.resolve(__dirname, '../../packages/ui/src'),
|
||||
},
|
||||
},
|
||||
});
|
||||
102
docs/adr/0001-capacitor-dexie-architecture.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# ADR 0001: Arquitectura Capacitor + Vite + TypeScript + Dexie
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-10-24
|
||||
**Deciders:** Development team
|
||||
|
||||
## Context
|
||||
|
||||
The ToldoConjunto project needs a POC for CESTA (Circuito de Economía Solidaria de Támesis) to manage inventory, sales, expenses, cash reconciliation, and reports for Sunday markets.
|
||||
|
||||
Constraints:
|
||||
- Must work well offline (poor internet at park)
|
||||
- Only lightweight HTTP server for static files OR APK
|
||||
- Data in browser storage if web-based
|
||||
- No authentication required initially
|
||||
- Must export/import data between devices
|
||||
- Must be Free/Libre Software
|
||||
- Single git repo, can add more folders later
|
||||
- Tablet Android available for use
|
||||
|
||||
Alternatives considered:
|
||||
1. **Dexie.js + Vite + PWA (web only)** - Simpler, no native build complexity
|
||||
2. **PouchDB + CouchDB sync + Capacitor** - Over-engineered, sync not needed
|
||||
3. **sql.js (SQLite WASM)** - Heavier bundle, more complex
|
||||
4. **Existing FOSS POS apps** - Hard to customize to specific CESTA flows
|
||||
|
||||
## Decision
|
||||
|
||||
Use **Capacitor + Vite + TypeScript + Dexie.js** with clean layered architecture.
|
||||
|
||||
### Stack
|
||||
- **Build**: Vite + TypeScript (web-first)
|
||||
- **Packaging**: Capacitor for Android APK
|
||||
- **Storage**: Dexie.js over IndexedDB
|
||||
- **Testing**: Vitest + Testing Library + fake-indexeddb
|
||||
- **Architecture**: packages/core (domain), packages/data (infra), packages/services (app), packages/ui (presentation), apps/toldo-app (composition)
|
||||
|
||||
### Rationale
|
||||
|
||||
**Why Capacitor:**
|
||||
- Better UX on Android tablet (app icon, no browser UI)
|
||||
- All assets bundled in APK - truly offline, no server needed
|
||||
- Easy distribution via USB/OTG
|
||||
- Can access native APIs in future if needed
|
||||
- Still maintains web-first approach
|
||||
|
||||
**Why Dexie over PouchDB:**
|
||||
- Lighter weight, simpler API
|
||||
- No revision/conflict complexity not needed
|
||||
- Manual export/import is sufficient for device-to-device transfer
|
||||
- Easier to test and reason about
|
||||
- Better fit for relational domain model
|
||||
|
||||
**Why Clean Architecture:**
|
||||
- Core business logic (cuadre, productos por productor, ajustes con motivo) is complex and must be correct
|
||||
- TDD easier with pure domain layer
|
||||
- Easy to evolve without tight coupling
|
||||
- Components stay small and reusable
|
||||
- Can extract to separate modules later in same repo
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
- APK provides great UX for non-technical users
|
||||
- Works completely offline
|
||||
- Business logic is highly testable
|
||||
- Clear separation of concerns
|
||||
- Easy to add new features/modules
|
||||
- Export/import JSON is simple and reliable
|
||||
- Skills enforce good practices
|
||||
|
||||
### Negative
|
||||
- More complex build process than pure web (need Android SDK for APK)
|
||||
- Need to mock Capacitor plugins in tests
|
||||
- More boilerplate with packages structure
|
||||
- Slightly larger app size with Capacitor runtime
|
||||
|
||||
### Mitigations
|
||||
- Keep web-first - can still serve dist/ statically
|
||||
- Mock all @capacitor/* in unit tests
|
||||
- Start simple, add packages as needed
|
||||
- Document build process clearly
|
||||
- Use skills to maintain discipline
|
||||
|
||||
## Compliance
|
||||
|
||||
- ✅ Works offline
|
||||
- ✅ Can serve as static files or APK
|
||||
- ✅ Data in IndexedDB (browser storage)
|
||||
- ✅ No auth required
|
||||
- ✅ Export/import capability
|
||||
- ✅ Free/Libre Software (all dependencies MIT/Apache/BSD compatible)
|
||||
- ✅ Single git repo, extensible structure
|
||||
- ✅ TDD capable
|
||||
- ✅ Follows SOLID, separation of concerns
|
||||
|
||||
## References
|
||||
- [Capacitor Docs](https://capacitorjs.com/)
|
||||
- [Dexie.js](https://dexie.org/)
|
||||
- [Vite](https://vitejs.dev/)
|
||||
- [OpenSpec](https://openspec.dev/)
|
||||
- [Agent Skills](https://agentskills.io/)
|
||||
1
node_modules/.bin/cap
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../@capacitor/cli/bin/capacitor
|
||||
1
node_modules/.bin/capacitor
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../@capacitor/cli/bin/capacitor
|
||||
1
node_modules/.bin/is-docker
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../is-docker/cli.js
|
||||
1
node_modules/.bin/mkdirp
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../mkdirp/bin/cmd.js
|
||||
1
node_modules/.bin/native-run
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../native-run/bin/native-run
|
||||
1
node_modules/.bin/node-which
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../which/bin/node-which
|
||||
1
node_modules/.bin/rimraf
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../rimraf/dist/cjs/src/bin.js
|
||||
1
node_modules/.bin/semver
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../semver/bin/semver.js
|
||||
1
node_modules/.bin/tree-kill
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../tree-kill/cli.js
|
||||
1052
node_modules/.package-lock.json
generated
vendored
Normal file
21
node_modules/@capacitor/android/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-present Drifty Co.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
96
node_modules/@capacitor/android/capacitor/build.gradle
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
ext {
|
||||
androidxActivityVersion = project.hasProperty('androidxActivityVersion') ? rootProject.ext.androidxActivityVersion : '1.8.0'
|
||||
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.6.1'
|
||||
androidxCoordinatorLayoutVersion = project.hasProperty('androidxCoordinatorLayoutVersion') ? rootProject.ext.androidxCoordinatorLayoutVersion : '1.2.0'
|
||||
androidxCoreVersion = project.hasProperty('androidxCoreVersion') ? rootProject.ext.androidxCoreVersion : '1.12.0'
|
||||
androidxFragmentVersion = project.hasProperty('androidxFragmentVersion') ? rootProject.ext.androidxFragmentVersion : '1.6.2'
|
||||
androidxWebkitVersion = project.hasProperty('androidxWebkitVersion') ? rootProject.ext.androidxWebkitVersion : '1.9.0'
|
||||
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
|
||||
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.1.5'
|
||||
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.5.1'
|
||||
cordovaAndroidVersion = project.hasProperty('cordovaAndroidVersion') ? rootProject.ext.cordovaAndroidVersion : '10.1.1'
|
||||
}
|
||||
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = project.hasProperty("kotlin_version") ? rootProject.ext.kotlin_version : '1.9.10'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://plugins.gradle.org/m2/"
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.2.1'
|
||||
|
||||
if (System.getenv("CAP_PUBLISH") == "true") {
|
||||
classpath 'io.github.gradle-nexus:publish-plugin:1.3.0'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(Javadoc).all { enabled = false }
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
if (System.getenv("CAP_PUBLISH") == "true") {
|
||||
apply plugin: 'io.github.gradle-nexus.publish-plugin'
|
||||
apply from: file('../scripts/publish-root.gradle')
|
||||
apply from: file('../scripts/publish-module.gradle')
|
||||
}
|
||||
|
||||
android {
|
||||
namespace "com.getcapacitor.android"
|
||||
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"
|
||||
consumerProguardFiles 'proguard-rules.pro'
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
lintOptions {
|
||||
baseline file("lint-baseline.xml")
|
||||
abortOnError true
|
||||
warningsAsErrors true
|
||||
lintConfig file('lint.xml')
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
publishing {
|
||||
singleVariant("release")
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation platform("org.jetbrains.kotlin:kotlin-bom:$kotlin_version")
|
||||
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
||||
implementation "androidx.core:core:$androidxCoreVersion"
|
||||
implementation "androidx.activity:activity:$androidxActivityVersion"
|
||||
implementation "androidx.fragment:fragment:$androidxFragmentVersion"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
|
||||
implementation "androidx.webkit:webkit:$androidxWebkitVersion"
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
||||
implementation "org.apache.cordova:framework:$cordovaAndroidVersion"
|
||||
testImplementation 'org.json:json:20231013'
|
||||
testImplementation 'org.mockito:mockito-inline:5.2.0'
|
||||
}
|
||||
|
||||
136
node_modules/@capacitor/android/capacitor/lint-baseline.xml
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<issues format="5" by="lint 4.1.1" client="gradle" variant="all" version="4.1.1">
|
||||
|
||||
<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="474"
|
||||
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=" switch (spinnerStyle.toLowerCase()) {"
|
||||
errorLine2=" ~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/getcapacitor/Splash.java"
|
||||
line="127"
|
||||
column="38"/>
|
||||
</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="327"
|
||||
column="89"/>
|
||||
</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="511"
|
||||
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="SetJavaScriptEnabled"
|
||||
message="Using `setJavaScriptEnabled` can introduce XSS vulnerabilities into your application, review carefully"
|
||||
errorLine1=" settings.setJavaScriptEnabled(true);"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/getcapacitor/Bridge.java"
|
||||
line="384"
|
||||
column="9"/>
|
||||
</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="84"
|
||||
column="32"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="StaticFieldLeak"
|
||||
message="Do not place Android context classes in static fields; this is a memory leak"
|
||||
errorLine1=" private static ImageView splashImage;"
|
||||
errorLine2=" ~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/getcapacitor/Splash.java"
|
||||
line="41"
|
||||
column="13"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="StaticFieldLeak"
|
||||
message="Do not place Android context classes in static fields; this is a memory leak"
|
||||
errorLine1=" private static ProgressBar spinnerBar;"
|
||||
errorLine2=" ~~~~~~">
|
||||
<location
|
||||
file="src/main/java/com/getcapacitor/Splash.java"
|
||||
line="42"
|
||||
column="13"/>
|
||||
</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>
|
||||
|
||||
</issues>
|
||||
9
node_modules/@capacitor/android/capacitor/lint.xml
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<lint>
|
||||
<issue id="GradleDependency" severity="ignore" />
|
||||
<issue id="AndroidGradlePluginVersion" severity="ignore" />
|
||||
<issue id="DiscouragedApi">
|
||||
<ignore path="src/main/java/com/getcapacitor/plugin/util/AssetUtil.java" />
|
||||
</issue>
|
||||
<issue id="ObsoleteSdkInt" severity="informational" />
|
||||
</lint>
|
||||
28
node_modules/@capacitor/android/capacitor/proguard-rules.pro
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Rules for Capacitor v3 plugins and annotations
|
||||
-keep @com.getcapacitor.annotation.CapacitorPlugin public class * {
|
||||
@com.getcapacitor.annotation.PermissionCallback <methods>;
|
||||
@com.getcapacitor.annotation.ActivityCallback <methods>;
|
||||
@com.getcapacitor.annotation.Permission <methods>;
|
||||
@com.getcapacitor.PluginMethod public <methods>;
|
||||
}
|
||||
|
||||
-keep public class * extends com.getcapacitor.Plugin { *; }
|
||||
|
||||
# Rules for Capacitor v2 plugins and annotations
|
||||
# These are deprecated but can still be used with Capacitor for now
|
||||
-keep @com.getcapacitor.NativePlugin public class * {
|
||||
@com.getcapacitor.PluginMethod public <methods>;
|
||||
}
|
||||
|
||||
# Rules for Cordova plugins
|
||||
-keep public class * extends org.apache.cordova.* {
|
||||
public <methods>;
|
||||
public <fields>;
|
||||
}
|
||||
3
node_modules/@capacitor/android/capacitor/src/main/AndroidManifest.xml
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
</manifest>
|
||||
1034
node_modules/@capacitor/android/capacitor/src/main/assets/native-bridge.js
generated
vendored
Normal file
94
node_modules/@capacitor/android/capacitor/src/main/java/com/getcapacitor/AndroidProtocolHandler.java
generated
vendored
Executable file
@@ -0,0 +1,94 @@
|
||||
// Copyright 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package com.getcapacitor;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.net.Uri;
|
||||
import android.util.TypedValue;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
public class AndroidProtocolHandler {
|
||||
|
||||
private Context context;
|
||||
|
||||
public AndroidProtocolHandler(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public InputStream openAsset(String path) throws IOException {
|
||||
return context.getAssets().open(path, AssetManager.ACCESS_STREAMING);
|
||||
}
|
||||
|
||||
public InputStream openResource(Uri uri) {
|
||||
assert uri.getPath() != null;
|
||||
// The path must be of the form ".../asset_type/asset_name.ext".
|
||||
List<String> pathSegments = uri.getPathSegments();
|
||||
String assetType = pathSegments.get(pathSegments.size() - 2);
|
||||
String assetName = pathSegments.get(pathSegments.size() - 1);
|
||||
|
||||
// Drop the file extension.
|
||||
assetName = assetName.split("\\.")[0];
|
||||
try {
|
||||
// Use the application context for resolving the resource package name so that we do
|
||||
// not use the browser's own resources. Note that if 'context' here belongs to the
|
||||
// test suite, it does not have a separate application context. In that case we use
|
||||
// the original context object directly.
|
||||
if (context.getApplicationContext() != null) {
|
||||
context = context.getApplicationContext();
|
||||
}
|
||||
int fieldId = getFieldId(context, assetType, assetName);
|
||||
int valueType = getValueType(context, fieldId);
|
||||
if (valueType == TypedValue.TYPE_STRING) {
|
||||
return context.getResources().openRawResource(fieldId);
|
||||
} else {
|
||||
Logger.error("Asset not of type string: " + uri);
|
||||
}
|
||||
} catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException e) {
|
||||
Logger.error("Unable to open resource URL: " + uri, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int getFieldId(Context context, String assetType, String assetName)
|
||||
throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
|
||||
Class<?> d = context.getClassLoader().loadClass(context.getPackageName() + ".R$" + assetType);
|
||||
java.lang.reflect.Field field = d.getField(assetName);
|
||||
return field.getInt(null);
|
||||
}
|
||||
|
||||
public InputStream openFile(String filePath) throws IOException {
|
||||
String realPath = filePath.replace(Bridge.CAPACITOR_FILE_START, "");
|
||||
File localFile = new File(realPath);
|
||||
return new FileInputStream(localFile);
|
||||
}
|
||||
|
||||
public InputStream openContentUrl(Uri uri) throws IOException {
|
||||
Integer port = uri.getPort();
|
||||
String baseUrl = uri.getScheme() + "://" + uri.getHost();
|
||||
if (port != -1) {
|
||||
baseUrl += ":" + port;
|
||||
}
|
||||
String realPath = uri.toString().replace(baseUrl + Bridge.CAPACITOR_CONTENT_START, "content:/");
|
||||
|
||||
InputStream stream = null;
|
||||
try {
|
||||
stream = context.getContentResolver().openInputStream(Uri.parse(realPath));
|
||||
} catch (SecurityException e) {
|
||||
Logger.error("Unable to open content URL: " + uri, e);
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
private static int getValueType(Context context, int fieldId) {
|
||||
TypedValue value = new TypedValue();
|
||||
context.getResources().getValue(fieldId, value, true);
|
||||
return value.type;
|
||||
}
|
||||
}
|
||||
61
node_modules/@capacitor/android/capacitor/src/main/java/com/getcapacitor/App.java
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
package com.getcapacitor;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class App {
|
||||
|
||||
/**
|
||||
* Interface for callbacks when app status changes.
|
||||
*/
|
||||
public interface AppStatusChangeListener {
|
||||
void onAppStatusChanged(Boolean isActive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for callbacks when app is restored with pending plugin call.
|
||||
*/
|
||||
public interface AppRestoredListener {
|
||||
void onAppRestored(PluginResult result);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private AppStatusChangeListener statusChangeListener;
|
||||
|
||||
@Nullable
|
||||
private AppRestoredListener appRestoredListener;
|
||||
|
||||
private boolean isActive = false;
|
||||
|
||||
public boolean isActive() {
|
||||
return isActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the object to receive callbacks.
|
||||
* @param listener
|
||||
*/
|
||||
public void setStatusChangeListener(@Nullable AppStatusChangeListener listener) {
|
||||
this.statusChangeListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the object to receive callbacks.
|
||||
* @param listener
|
||||
*/
|
||||
public void setAppRestoredListener(@Nullable AppRestoredListener listener) {
|
||||
this.appRestoredListener = listener;
|
||||
}
|
||||
|
||||
protected void fireRestoredResult(PluginResult result) {
|
||||
if (appRestoredListener != null) {
|
||||
appRestoredListener.onAppRestored(result);
|
||||
}
|
||||
}
|
||||
|
||||
public void fireStatusChange(boolean isActive) {
|
||||
this.isActive = isActive;
|
||||
if (statusChangeListener != null) {
|
||||
statusChangeListener.onAppStatusChanged(isActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
65
node_modules/@capacitor/android/capacitor/src/main/java/com/getcapacitor/AppUUID.java
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
package com.getcapacitor;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class AppUUID {
|
||||
|
||||
private static final String KEY = "CapacitorAppUUID";
|
||||
|
||||
public static String getAppUUID(AppCompatActivity activity) throws Exception {
|
||||
assertAppUUID(activity);
|
||||
return readUUID(activity);
|
||||
}
|
||||
|
||||
public static void regenerateAppUUID(AppCompatActivity activity) throws Exception {
|
||||
try {
|
||||
String uuid = generateUUID();
|
||||
writeUUID(activity, uuid);
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
throw new Exception("Capacitor App UUID could not be generated.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertAppUUID(AppCompatActivity activity) throws Exception {
|
||||
String uuid = readUUID(activity);
|
||||
if (uuid.equals("")) {
|
||||
regenerateAppUUID(activity);
|
||||
}
|
||||
}
|
||||
|
||||
private static String generateUUID() throws NoSuchAlgorithmException {
|
||||
MessageDigest salt = MessageDigest.getInstance("SHA-256");
|
||||
salt.update(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8));
|
||||
return bytesToHex(salt.digest());
|
||||
}
|
||||
|
||||
private static String readUUID(AppCompatActivity activity) {
|
||||
SharedPreferences sharedPref = activity.getPreferences(Context.MODE_PRIVATE);
|
||||
return sharedPref.getString(KEY, "");
|
||||
}
|
||||
|
||||
private static void writeUUID(AppCompatActivity activity, String uuid) {
|
||||
SharedPreferences sharedPref = activity.getPreferences(Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = sharedPref.edit();
|
||||
editor.putString(KEY, uuid);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
private static String bytesToHex(byte[] bytes) {
|
||||
byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
|
||||
byte[] hexChars = new byte[bytes.length * 2];
|
||||
for (int j = 0; j < bytes.length; j++) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
|
||||
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
1595
node_modules/@capacitor/android/capacitor/src/main/java/com/getcapacitor/Bridge.java
generated
vendored
Normal file
212
node_modules/@capacitor/android/capacitor/src/main/java/com/getcapacitor/BridgeActivity.java
generated
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
package com.getcapacitor;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import com.getcapacitor.android.R;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class BridgeActivity extends AppCompatActivity {
|
||||
|
||||
protected Bridge bridge;
|
||||
protected boolean keepRunning = true;
|
||||
protected CapConfig config;
|
||||
|
||||
protected int activityDepth = 0;
|
||||
protected List<Class<? extends Plugin>> initialPlugins = new ArrayList<>();
|
||||
protected final Bridge.Builder bridgeBuilder = new Bridge.Builder(this);
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
bridgeBuilder.setInstanceState(savedInstanceState);
|
||||
getApplication().setTheme(R.style.AppTheme_NoActionBar);
|
||||
setTheme(R.style.AppTheme_NoActionBar);
|
||||
try {
|
||||
setContentView(R.layout.bridge_layout_main);
|
||||
} catch (Exception ex) {
|
||||
setContentView(R.layout.no_webview);
|
||||
return;
|
||||
}
|
||||
|
||||
PluginManager loader = new PluginManager(getAssets());
|
||||
|
||||
try {
|
||||
bridgeBuilder.addPlugins(loader.loadPluginClasses());
|
||||
} catch (PluginLoadException ex) {
|
||||
Logger.error("Error loading plugins.", ex);
|
||||
}
|
||||
|
||||
this.load();
|
||||
}
|
||||
|
||||
protected void load() {
|
||||
Logger.debug("Starting BridgeActivity");
|
||||
|
||||
bridge = bridgeBuilder.addPlugins(initialPlugins).setConfig(config).create();
|
||||
|
||||
this.keepRunning = bridge.shouldKeepRunning();
|
||||
this.onNewIntent(getIntent());
|
||||
}
|
||||
|
||||
public void registerPlugin(Class<? extends Plugin> plugin) {
|
||||
bridgeBuilder.addPlugin(plugin);
|
||||
}
|
||||
|
||||
public void registerPlugins(List<Class<? extends Plugin>> plugins) {
|
||||
bridgeBuilder.addPlugins(plugins);
|
||||
}
|
||||
|
||||
public Bridge getBridge() {
|
||||
return this.bridge;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
bridge.saveInstanceState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
activityDepth++;
|
||||
if (this.bridge != null) {
|
||||
this.bridge.onStart();
|
||||
Logger.debug("App started");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestart() {
|
||||
super.onRestart();
|
||||
this.bridge.onRestart();
|
||||
Logger.debug("App restarted");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (bridge != null) {
|
||||
bridge.getApp().fireStatusChange(true);
|
||||
this.bridge.onResume();
|
||||
Logger.debug("App resumed");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (bridge != null) {
|
||||
this.bridge.onPause();
|
||||
Logger.debug("App paused");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
if (bridge != null) {
|
||||
activityDepth = Math.max(0, activityDepth - 1);
|
||||
if (activityDepth == 0) {
|
||||
bridge.getApp().fireStatusChange(false);
|
||||
}
|
||||
|
||||
this.bridge.onStop();
|
||||
Logger.debug("App stopped");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (this.bridge != null) {
|
||||
this.bridge.onDestroy();
|
||||
Logger.debug("App destroyed");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
this.bridge.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles permission request results.
|
||||
*
|
||||
* Capacitor is backwards compatible such that plugins using legacy permission request codes
|
||||
* may coexist with plugins using the AndroidX Activity v1.2 permission callback flow introduced
|
||||
* in Capacitor 3.0.
|
||||
*
|
||||
* In this method, plugins are checked first for ownership of the legacy permission request code.
|
||||
* If the {@link Bridge#onRequestPermissionsResult(int, String[], int[])} method indicates it has
|
||||
* handled the permission, then the permission callback will be considered complete. Otherwise,
|
||||
* the permission will be handled using the AndroidX Activity flow.
|
||||
*
|
||||
* @param requestCode the request code associated with the permission request
|
||||
* @param permissions the Android permission strings requested
|
||||
* @param grantResults the status result of the permission request
|
||||
*/
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
if (this.bridge == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bridge.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles activity results.
|
||||
*
|
||||
* Capacitor is backwards compatible such that plugins using legacy activity result codes
|
||||
* may coexist with plugins using the AndroidX Activity v1.2 activity callback flow introduced
|
||||
* in Capacitor 3.0.
|
||||
*
|
||||
* In this method, plugins are checked first for ownership of the legacy request code. If the
|
||||
* {@link Bridge#onActivityResult(int, int, Intent)} method indicates it has handled the activity
|
||||
* result, then the callback will be considered complete. Otherwise, the result will be handled
|
||||
* using the AndroidX Activiy flow.
|
||||
*
|
||||
* @param requestCode the request code associated with the activity result
|
||||
* @param resultCode the result code
|
||||
* @param data any data included with the activity result
|
||||
*/
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (this.bridge == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bridge.onActivityResult(requestCode, resultCode, data)) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
|
||||
if (this.bridge == null || intent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.bridge.onNewIntent(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
if (this.bridge == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.bridge.onConfigurationChanged(newConfig);
|
||||
}
|
||||
}
|
||||
134
node_modules/@capacitor/android/capacitor/src/main/java/com/getcapacitor/BridgeFragment.java
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
package com.getcapacitor;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import com.getcapacitor.android.R;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A simple {@link Fragment} subclass.
|
||||
* Use the {@link BridgeFragment#newInstance} factory method to
|
||||
* create an instance of this fragment.
|
||||
*/
|
||||
public class BridgeFragment extends Fragment {
|
||||
|
||||
private static final String ARG_START_DIR = "startDir";
|
||||
|
||||
protected Bridge bridge;
|
||||
protected boolean keepRunning = true;
|
||||
|
||||
private final List<Class<? extends Plugin>> initialPlugins = new ArrayList<>();
|
||||
private CapConfig config = null;
|
||||
|
||||
private final List<WebViewListener> webViewListeners = new ArrayList<>();
|
||||
|
||||
public BridgeFragment() {
|
||||
// Required empty public constructor
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this factory method to create a new instance of
|
||||
* this fragment using the provided parameters.
|
||||
*
|
||||
* @param startDir the directory to serve content from
|
||||
* @return A new instance of fragment BridgeFragment.
|
||||
*/
|
||||
public static BridgeFragment newInstance(String startDir) {
|
||||
BridgeFragment fragment = new BridgeFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_START_DIR, startDir);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public void addPlugin(Class<? extends Plugin> plugin) {
|
||||
this.initialPlugins.add(plugin);
|
||||
}
|
||||
|
||||
public void setConfig(CapConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public Bridge getBridge() {
|
||||
return bridge;
|
||||
}
|
||||
|
||||
public void addWebViewListener(WebViewListener webViewListener) {
|
||||
webViewListeners.add(webViewListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the WebView and create the Bridge
|
||||
*/
|
||||
protected void load(Bundle savedInstanceState) {
|
||||
Logger.debug("Loading Bridge with BridgeFragment");
|
||||
|
||||
Bundle args = getArguments();
|
||||
String startDir = null;
|
||||
|
||||
if (args != null) {
|
||||
startDir = getArguments().getString(ARG_START_DIR);
|
||||
}
|
||||
|
||||
bridge =
|
||||
new Bridge.Builder(this)
|
||||
.setInstanceState(savedInstanceState)
|
||||
.setPlugins(initialPlugins)
|
||||
.setConfig(config)
|
||||
.addWebViewListeners(webViewListeners)
|
||||
.create();
|
||||
|
||||
if (startDir != null) {
|
||||
bridge.setServerAssetPath(startDir);
|
||||
}
|
||||
|
||||
this.keepRunning = bridge.shouldKeepRunning();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) {
|
||||
super.onInflate(context, attrs, savedInstanceState);
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.bridge_fragment);
|
||||
CharSequence c = a.getString(R.styleable.bridge_fragment_start_dir);
|
||||
|
||||
if (c != null) {
|
||||
String startDir = c.toString();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_START_DIR, startDir);
|
||||
setArguments(args);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
// Inflate the layout for this fragment
|
||||
return inflater.inflate(R.layout.fragment_bridge, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
this.load(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (this.bridge != null) {
|
||||
this.bridge.onDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
510
node_modules/@capacitor/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java
generated
vendored
Normal file
@@ -0,0 +1,510 @@
|
||||
package com.getcapacitor;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
import android.view.View;
|
||||
import android.webkit.ConsoleMessage;
|
||||
import android.webkit.GeolocationPermissions;
|
||||
import android.webkit.JsPromptResult;
|
||||
import android.webkit.JsResult;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.webkit.PermissionRequest;
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.EditText;
|
||||
import androidx.activity.result.ActivityResult;
|
||||
import androidx.activity.result.ActivityResultCallback;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.core.content.FileProvider;
|
||||
import com.getcapacitor.util.PermissionHelper;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Custom WebChromeClient handler, required for showing dialogs, confirms, etc. in our
|
||||
* WebView instance.
|
||||
*/
|
||||
public class BridgeWebChromeClient extends WebChromeClient {
|
||||
|
||||
private interface PermissionListener {
|
||||
void onPermissionSelect(Boolean isGranted);
|
||||
}
|
||||
|
||||
private interface ActivityResultListener {
|
||||
void onActivityResult(ActivityResult result);
|
||||
}
|
||||
|
||||
private ActivityResultLauncher permissionLauncher;
|
||||
private ActivityResultLauncher activityLauncher;
|
||||
private PermissionListener permissionListener;
|
||||
private ActivityResultListener activityListener;
|
||||
|
||||
private Bridge bridge;
|
||||
|
||||
public BridgeWebChromeClient(Bridge bridge) {
|
||||
this.bridge = bridge;
|
||||
|
||||
ActivityResultCallback<Map<String, Boolean>> permissionCallback = (Map<String, Boolean> isGranted) -> {
|
||||
if (permissionListener != null) {
|
||||
boolean granted = true;
|
||||
for (Map.Entry<String, Boolean> permission : isGranted.entrySet()) {
|
||||
if (!permission.getValue()) granted = false;
|
||||
}
|
||||
permissionListener.onPermissionSelect(granted);
|
||||
}
|
||||
};
|
||||
|
||||
permissionLauncher = bridge.registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), permissionCallback);
|
||||
activityLauncher =
|
||||
bridge.registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
if (activityListener != null) {
|
||||
activityListener.onActivityResult(result);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render web content in `view`.
|
||||
*
|
||||
* Both this method and {@link #onHideCustomView()} are required for
|
||||
* rendering web content in full screen.
|
||||
*
|
||||
* @see <a href="https://developer.android.com/reference/android/webkit/WebChromeClient#onShowCustomView(android.view.View,%20android.webkit.WebChromeClient.CustomViewCallback)">onShowCustomView() docs</a>
|
||||
*/
|
||||
@Override
|
||||
public void onShowCustomView(View view, CustomViewCallback callback) {
|
||||
callback.onCustomViewHidden();
|
||||
super.onShowCustomView(view, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render web content in the original Web View again.
|
||||
*
|
||||
* Do not remove this method--@see #onShowCustomView(View, CustomViewCallback).
|
||||
*/
|
||||
@Override
|
||||
public void onHideCustomView() {
|
||||
super.onHideCustomView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionRequest(final PermissionRequest request) {
|
||||
boolean isRequestPermissionRequired = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M;
|
||||
|
||||
List<String> permissionList = new ArrayList<>();
|
||||
if (Arrays.asList(request.getResources()).contains("android.webkit.resource.VIDEO_CAPTURE")) {
|
||||
permissionList.add(Manifest.permission.CAMERA);
|
||||
}
|
||||
if (Arrays.asList(request.getResources()).contains("android.webkit.resource.AUDIO_CAPTURE")) {
|
||||
permissionList.add(Manifest.permission.MODIFY_AUDIO_SETTINGS);
|
||||
permissionList.add(Manifest.permission.RECORD_AUDIO);
|
||||
}
|
||||
if (!permissionList.isEmpty() && isRequestPermissionRequired) {
|
||||
String[] permissions = permissionList.toArray(new String[0]);
|
||||
permissionListener =
|
||||
isGranted -> {
|
||||
if (isGranted) {
|
||||
request.grant(request.getResources());
|
||||
} else {
|
||||
request.deny();
|
||||
}
|
||||
};
|
||||
permissionLauncher.launch(permissions);
|
||||
} else {
|
||||
request.grant(request.getResources());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the browser alert modal
|
||||
* @param view
|
||||
* @param url
|
||||
* @param message
|
||||
* @param result
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
|
||||
if (bridge.getActivity().isFinishing()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());
|
||||
builder
|
||||
.setMessage(message)
|
||||
.setPositiveButton(
|
||||
"OK",
|
||||
(dialog, buttonIndex) -> {
|
||||
dialog.dismiss();
|
||||
result.confirm();
|
||||
}
|
||||
)
|
||||
.setOnCancelListener(
|
||||
dialog -> {
|
||||
dialog.dismiss();
|
||||
result.cancel();
|
||||
}
|
||||
);
|
||||
|
||||
AlertDialog dialog = builder.create();
|
||||
|
||||
dialog.show();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the browser confirm modal
|
||||
* @param view
|
||||
* @param url
|
||||
* @param message
|
||||
* @param result
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
|
||||
if (bridge.getActivity().isFinishing()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());
|
||||
|
||||
builder
|
||||
.setMessage(message)
|
||||
.setPositiveButton(
|
||||
"OK",
|
||||
(dialog, buttonIndex) -> {
|
||||
dialog.dismiss();
|
||||
result.confirm();
|
||||
}
|
||||
)
|
||||
.setNegativeButton(
|
||||
"Cancel",
|
||||
(dialog, buttonIndex) -> {
|
||||
dialog.dismiss();
|
||||
result.cancel();
|
||||
}
|
||||
)
|
||||
.setOnCancelListener(
|
||||
dialog -> {
|
||||
dialog.dismiss();
|
||||
result.cancel();
|
||||
}
|
||||
);
|
||||
|
||||
AlertDialog dialog = builder.create();
|
||||
|
||||
dialog.show();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the browser prompt modal
|
||||
* @param view
|
||||
* @param url
|
||||
* @param message
|
||||
* @param defaultValue
|
||||
* @param result
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, final JsPromptResult result) {
|
||||
if (bridge.getActivity().isFinishing()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());
|
||||
final EditText input = new EditText(view.getContext());
|
||||
|
||||
builder
|
||||
.setMessage(message)
|
||||
.setView(input)
|
||||
.setPositiveButton(
|
||||
"OK",
|
||||
(dialog, buttonIndex) -> {
|
||||
dialog.dismiss();
|
||||
|
||||
String inputText1 = input.getText().toString().trim();
|
||||
result.confirm(inputText1);
|
||||
}
|
||||
)
|
||||
.setNegativeButton(
|
||||
"Cancel",
|
||||
(dialog, buttonIndex) -> {
|
||||
dialog.dismiss();
|
||||
result.cancel();
|
||||
}
|
||||
)
|
||||
.setOnCancelListener(
|
||||
dialog -> {
|
||||
dialog.dismiss();
|
||||
result.cancel();
|
||||
}
|
||||
);
|
||||
|
||||
AlertDialog dialog = builder.create();
|
||||
|
||||
dialog.show();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the browser geolocation permission prompt
|
||||
* @param origin
|
||||
* @param callback
|
||||
*/
|
||||
@Override
|
||||
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
|
||||
super.onGeolocationPermissionsShowPrompt(origin, callback);
|
||||
Logger.debug("onGeolocationPermissionsShowPrompt: DOING IT HERE FOR ORIGIN: " + origin);
|
||||
final String[] geoPermissions = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION };
|
||||
|
||||
if (!PermissionHelper.hasPermissions(bridge.getContext(), geoPermissions)) {
|
||||
permissionListener =
|
||||
isGranted -> {
|
||||
if (isGranted) {
|
||||
callback.invoke(origin, true, false);
|
||||
} else {
|
||||
final String[] coarsePermission = { Manifest.permission.ACCESS_COARSE_LOCATION };
|
||||
if (
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
|
||||
PermissionHelper.hasPermissions(bridge.getContext(), coarsePermission)
|
||||
) {
|
||||
callback.invoke(origin, true, false);
|
||||
} else {
|
||||
callback.invoke(origin, false, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
permissionLauncher.launch(geoPermissions);
|
||||
} else {
|
||||
// permission is already granted
|
||||
callback.invoke(origin, true, false);
|
||||
Logger.debug("onGeolocationPermissionsShowPrompt: has required permission");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShowFileChooser(
|
||||
WebView webView,
|
||||
final ValueCallback<Uri[]> filePathCallback,
|
||||
final FileChooserParams fileChooserParams
|
||||
) {
|
||||
List<String> acceptTypes = Arrays.asList(fileChooserParams.getAcceptTypes());
|
||||
boolean captureEnabled = fileChooserParams.isCaptureEnabled();
|
||||
boolean capturePhoto = captureEnabled && acceptTypes.contains("image/*");
|
||||
final boolean captureVideo = captureEnabled && acceptTypes.contains("video/*");
|
||||
if ((capturePhoto || captureVideo)) {
|
||||
if (isMediaCaptureSupported()) {
|
||||
showMediaCaptureOrFilePicker(filePathCallback, fileChooserParams, captureVideo);
|
||||
} else {
|
||||
permissionListener =
|
||||
isGranted -> {
|
||||
if (isGranted) {
|
||||
showMediaCaptureOrFilePicker(filePathCallback, fileChooserParams, captureVideo);
|
||||
} else {
|
||||
Logger.warn(Logger.tags("FileChooser"), "Camera permission not granted");
|
||||
filePathCallback.onReceiveValue(null);
|
||||
}
|
||||
};
|
||||
final String[] camPermission = { Manifest.permission.CAMERA };
|
||||
permissionLauncher.launch(camPermission);
|
||||
}
|
||||
} else {
|
||||
showFilePicker(filePathCallback, fileChooserParams);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isMediaCaptureSupported() {
|
||||
String[] permissions = { Manifest.permission.CAMERA };
|
||||
return (
|
||||
PermissionHelper.hasPermissions(bridge.getContext(), permissions) ||
|
||||
!PermissionHelper.hasDefinedPermission(bridge.getContext(), Manifest.permission.CAMERA)
|
||||
);
|
||||
}
|
||||
|
||||
private void showMediaCaptureOrFilePicker(ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams, boolean isVideo) {
|
||||
// TODO: add support for video capture on Android M and older
|
||||
// On Android M and lower the VIDEO_CAPTURE_INTENT (e.g.: intent.getData())
|
||||
// returns a file:// URI instead of the expected content:// URI.
|
||||
// So we disable it for now because it requires a bit more work
|
||||
boolean isVideoCaptureSupported = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N;
|
||||
boolean shown = false;
|
||||
if (isVideo && isVideoCaptureSupported) {
|
||||
shown = showVideoCapturePicker(filePathCallback);
|
||||
} else {
|
||||
shown = showImageCapturePicker(filePathCallback);
|
||||
}
|
||||
if (!shown) {
|
||||
Logger.warn(Logger.tags("FileChooser"), "Media capture intent could not be launched. Falling back to default file picker.");
|
||||
showFilePicker(filePathCallback, fileChooserParams);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("QueryPermissionsNeeded")
|
||||
private boolean showImageCapturePicker(final ValueCallback<Uri[]> filePathCallback) {
|
||||
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
if (takePictureIntent.resolveActivity(bridge.getActivity().getPackageManager()) == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Uri imageFileUri;
|
||||
try {
|
||||
imageFileUri = createImageFileUri();
|
||||
} catch (Exception ex) {
|
||||
Logger.error("Unable to create temporary media capture file: " + ex.getMessage());
|
||||
return false;
|
||||
}
|
||||
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageFileUri);
|
||||
activityListener =
|
||||
activityResult -> {
|
||||
Uri[] result = null;
|
||||
if (activityResult.getResultCode() == Activity.RESULT_OK) {
|
||||
result = new Uri[] { imageFileUri };
|
||||
}
|
||||
filePathCallback.onReceiveValue(result);
|
||||
};
|
||||
activityLauncher.launch(takePictureIntent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressLint("QueryPermissionsNeeded")
|
||||
private boolean showVideoCapturePicker(final ValueCallback<Uri[]> filePathCallback) {
|
||||
Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
|
||||
if (takeVideoIntent.resolveActivity(bridge.getActivity().getPackageManager()) == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
activityListener =
|
||||
activityResult -> {
|
||||
Uri[] result = null;
|
||||
if (activityResult.getResultCode() == Activity.RESULT_OK) {
|
||||
result = new Uri[] { activityResult.getData().getData() };
|
||||
}
|
||||
filePathCallback.onReceiveValue(result);
|
||||
};
|
||||
activityLauncher.launch(takeVideoIntent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void showFilePicker(final ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
|
||||
Intent intent = fileChooserParams.createIntent();
|
||||
if (fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE) {
|
||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
|
||||
}
|
||||
if (fileChooserParams.getAcceptTypes().length > 1 || intent.getType().startsWith(".")) {
|
||||
String[] validTypes = getValidTypes(fileChooserParams.getAcceptTypes());
|
||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, validTypes);
|
||||
if (intent.getType().startsWith(".")) {
|
||||
intent.setType(validTypes[0]);
|
||||
}
|
||||
}
|
||||
try {
|
||||
activityListener =
|
||||
activityResult -> {
|
||||
Uri[] result;
|
||||
Intent resultIntent = activityResult.getData();
|
||||
if (activityResult.getResultCode() == Activity.RESULT_OK && resultIntent.getClipData() != null) {
|
||||
final int numFiles = resultIntent.getClipData().getItemCount();
|
||||
result = new Uri[numFiles];
|
||||
for (int i = 0; i < numFiles; i++) {
|
||||
result[i] = resultIntent.getClipData().getItemAt(i).getUri();
|
||||
}
|
||||
} else {
|
||||
result = WebChromeClient.FileChooserParams.parseResult(activityResult.getResultCode(), resultIntent);
|
||||
}
|
||||
filePathCallback.onReceiveValue(result);
|
||||
};
|
||||
activityLauncher.launch(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
filePathCallback.onReceiveValue(null);
|
||||
}
|
||||
}
|
||||
|
||||
private String[] getValidTypes(String[] currentTypes) {
|
||||
List<String> validTypes = new ArrayList<>();
|
||||
MimeTypeMap mtm = MimeTypeMap.getSingleton();
|
||||
for (String mime : currentTypes) {
|
||||
if (mime.startsWith(".")) {
|
||||
String extension = mime.substring(1);
|
||||
String extensionMime = mtm.getMimeTypeFromExtension(extension);
|
||||
if (extensionMime != null && !validTypes.contains(extensionMime)) {
|
||||
validTypes.add(extensionMime);
|
||||
}
|
||||
} else if (!validTypes.contains(mime)) {
|
||||
validTypes.add(mime);
|
||||
}
|
||||
}
|
||||
Object[] validObj = validTypes.toArray();
|
||||
return Arrays.copyOf(validObj, validObj.length, String[].class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
|
||||
String tag = Logger.tags("Console");
|
||||
if (consoleMessage.message() != null && isValidMsg(consoleMessage.message())) {
|
||||
String msg = String.format(
|
||||
"File: %s - Line %d - Msg: %s",
|
||||
consoleMessage.sourceId(),
|
||||
consoleMessage.lineNumber(),
|
||||
consoleMessage.message()
|
||||
);
|
||||
String level = consoleMessage.messageLevel().name();
|
||||
if ("ERROR".equalsIgnoreCase(level)) {
|
||||
Logger.error(tag, msg, null);
|
||||
} else if ("WARNING".equalsIgnoreCase(level)) {
|
||||
Logger.warn(tag, msg);
|
||||
} else if ("TIP".equalsIgnoreCase(level)) {
|
||||
Logger.debug(tag, msg);
|
||||
} else {
|
||||
Logger.info(tag, msg);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isValidMsg(String msg) {
|
||||
return !(
|
||||
msg.contains("%cresult %c") ||
|
||||
(msg.contains("%cnative %c")) ||
|
||||
msg.equalsIgnoreCase("[object Object]") ||
|
||||
msg.equalsIgnoreCase("console.groupEnd")
|
||||
);
|
||||
}
|
||||
|
||||
private Uri createImageFileUri() throws IOException {
|
||||
Activity activity = bridge.getActivity();
|
||||
File photoFile = createImageFile(activity);
|
||||
return FileProvider.getUriForFile(activity, bridge.getContext().getPackageName() + ".fileprovider", photoFile);
|
||||
}
|
||||
|
||||
private File createImageFile(Activity activity) throws IOException {
|
||||
// Create an image file name
|
||||
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
|
||||
String imageFileName = "JPEG_" + timeStamp + "_";
|
||||
File storageDir = activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
|
||||
|
||||
return File.createTempFile(imageFileName, ".jpg", storageDir);
|
||||
}
|
||||
}
|
||||