diff --git a/.eslintrc.js b/.eslintrc.js index 1e4329e9..b6e9a372 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,6 +1,7 @@ -const ignoreWarnings = new Set(['a11y-no-onchange', 'a11y-label-has-associated-control', 'a11y-autofocus']); +const ignoreWarnings = ['a11y-no-onchange', 'a11y-label-has-associated-control', 'illegal-attribute-character']; module.exports = { - extends: ['plugin:prettier/recommended'], + globals: { gVars: true, SENTRY_DSN: true, SENTRY_PREFIX: true, PRODUCTION: true, OWM_KEY: true, __ANDROID__: true, __IOS__: true, LatLonKeys: true, DEV_LOG: true }, + extends: ['plugin:prettier/recommended', 'plugin:@typescript-eslint/recommended-type-checked', 'plugin:svelte/recommended'], env: { es6: true, node: true @@ -9,32 +10,61 @@ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { - ecmaVersion: 2019, + ecmaVersion: 2021, sourceType: 'module', - project: ['tsconfig.eslint.json'], - // extraFileExtensions: ['.svelte'], - warnOnUnsupportedTypeScriptVersion: false, + project: 'tsconfig.eslint.json', + extraFileExtensions: ['.svelte'], tsconfigRootDir: __dirname }, - globals: { gVars: true, SENTRY_DSN: true, SENTRY_PREFIX: true, PRODUCTION: true, OWM_KEY: true }, - plugins: ['prettier', 'svelte3', '@typescript-eslint'], + plugins: ['prettier', '@typescript-eslint'], overrides: [ - { files: ['*.svelte'], processor: 'svelte3/svelte3' }, + { + files: ['*.svelte'], + parser: 'svelte-eslint-parser', + parserOptions: { + sourceType: 'module', + ecmaVersion: 2021, + parser: '@typescript-eslint/parser' + }, + rules: { + 'no-undef': 'off', + 'svelte/sort-attributes': 'warn', + 'svelte/no-inner-declarations': 'off', + 'svelte/valid-compile': [ + 'error', + { + ignoreWarnings: true + } + ] + } + }, { files: '*.ts', rules: { - 'eslint-plugin-svelte3/parse-error': 'off', + // 'eslint-plugin-svelte/parse-error': 'off', 'no-undef': 'off' } } ], settings: { - 'svelte3/ignore-warnings': (w) => ignoreWarnings.has(w && w.code), - 'svelte3/typescript': true // load TypeScript as peer dependency + svelte: { + ignoreWarnings + } }, rules: { - 'prettier/prettier': 'warn', - 'eslint-plugin-svelte3/invalid-binding': 'off', + 'svelte/no-inner-declarations': 'off', + 'prettier/prettier': [ + 'warn', + { + // parser: 'typescript' + } + ], + '@typescript-eslint/unbound-method': 'off', + 'no-duplicate-imports': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', '@typescript-eslint/adjacent-overload-signatures': 'off', '@typescript-eslint/array-type': 'error', '@typescript-eslint/await-thenable': 'error', @@ -48,7 +78,12 @@ module.exports = { accessibility: 'explicit' } ], + '@typescript-eslint/no-misused-promises': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-call': 'off', '@typescript-eslint/indent': 'off', + '@typescript-eslint/require-await': 'off', + '@typescript-eslint/ban-ts-comment': 'warn', '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/member-delimiter-style': 'error', '@typescript-eslint/member-ordering': 'off', @@ -146,7 +181,6 @@ module.exports = { 'no-constant-condition': 'error', 'no-control-regex': 'off', 'no-debugger': 'error', - 'no-duplicate-imports': 'error', 'no-empty': 'off', 'no-eval': 'off', 'no-extra-semi': 'off', diff --git a/.gitignore b/.gitignore index 110e3022..9074357a 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ yarn-error.log* .idea .cloud .project +.yarn tmp/ typings/ diff --git a/.prettierrc b/.prettierrc index 23b087c5..7e92978a 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,5 +3,13 @@ "semi": true, "tabWidth": 4, "singleQuote": true, - "trailingComma": "none" + "parser": "typescript", + "trailingComma": "none", + "plugins": [ + "prettier-plugin-svelte" + ], + "svelteSortOrder": "options-styles-scripts-markup", + "svelteStrictMode": false, + "svelteBracketNewLine": false, + "svelteIndentScriptAndStyle": true } \ No newline at end of file diff --git a/App_Resources/Android/app.gradle b/App_Resources/Android/app.gradle index 9e87d9a4..eb30acdc 100644 --- a/App_Resources/Android/app.gradle +++ b/App_Resources/Android/app.gradle @@ -2,6 +2,9 @@ project.ext.abiCodes = ['armeabi':1, 'armeabi-v7a':2, 'arm64-v8a':3, 'x86':4, 'x86_64':5] def BUILD_TOOLS_PATH = "$rootDir/build-tools" +apply plugin: "androidx.baselineprofile" +apply plugin: "org.jetbrains.kotlin.android" + android { // we only enable split if specified as an arg or if in debug and abiFilters is used(through cli) def splitEnabled = (gradle.startParameter.taskNames.contains("assembleDebug") && project.hasProperty('abiFilters')) || project.hasProperty('splitEnabled'); @@ -41,13 +44,13 @@ android { path "src/main/cpp/CMakeLists.txt" } } + kotlin { + jvmToolchain(17) + } compileOptions { - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } - // kotlinOptions { - // jvmTarget = JavaVersion.VERSION_11 - // } lintOptions { // disable("LintError") checkReleaseBuilds false @@ -82,11 +85,11 @@ android { release { // Enables code shrinking, obfuscation, and optimization for only // your project's release build type. - // minifyEnabled true + minifyEnabled true // Enables resource shrinking, which is performed by the // Android Gradle plugin. - // shrinkResources true + shrinkResources true // Disables PNG crunching for the release build type. crunchPngs false @@ -95,10 +98,10 @@ android { // Includes the default ProGuard rules files that are packaged with // the Android Gradle plugin. To learn more, go to the section about // R8 configuration files. - // def absolutePathToAppResources = getAppResourcesPath() - // proguardFiles getDefaultProguardFile( - // 'proguard-android-optimize.txt'), - // "$BUILD_TOOLS_PATH/proguard-rules.pro" + def absolutePathToAppResources = getAppResourcesPath() + proguardFiles getDefaultProguardFile( + 'proguard-android-optimize.txt'), + "$BUILD_TOOLS_PATH/proguard-rules.pro" debuggable false jniDebuggable false @@ -107,6 +110,13 @@ android { debug { multiDexEnabled true; } + benchmark { + initWith release + proguardFiles("../benchmark/benchmark-rules.pro") + signingConfig signingConfigs.debug + matchingFallbacks = ['release'] + debuggable false + } } packagingOptions { pickFirst 'lib/armeabi-v7a/libc++_shared.so' @@ -128,23 +138,23 @@ android { // } } -// task createProguard { -// dependsOn 'buildMetadata' -// doLast { -// exec { -// workingDir "$USER_PROJECT_ROOT" -// commandLine 'node', 'scripts/createProguard.js' -// } -// } -// } -// tasks.whenTaskAdded({ DefaultTask currentTask -> -// if (currentTask =~ /buildMetadata/) { -// currentTask.finalizedBy(createProguard) -// } -// if (currentTask =~ /minify.*WithR8/) { -// currentTask.dependsOn(createProguard) -// } -// }) +task createProguard { + dependsOn 'buildMetadata' + doLast { + exec { + workingDir "$USER_PROJECT_ROOT" + commandLine 'node', 'scripts/createProguard.js' + } + } +} +project.tasks.configureEach { + if (name =~ /buildMetadata/) { + it.finalizedBy(createProguard) + } + if (name =~ /minify(Debug|Release)WithR8/) { + it.dependsOn(createProguard) + } +} android.applicationVariants.all { variant -> if (project.hasProperty('splitEnabled')) { @@ -160,16 +170,19 @@ android.applicationVariants.all { variant -> } } dependencies { + def computeKotlinVersion = { -> project.hasProperty("kotlinVersion") ? kotlinVersion : "${ns_default_kotlin_version}" } + def kotlinVersion = computeKotlinVersion() def androidxVersion = project.hasProperty("androidxVersion") ? project.androidxVersion : "1.2.0" implementation "androidx.core:core-ktx:$androidxVersion" - implementation 'com.otaliastudios:cameraview:2.7.2' - implementation 'androidx.core:core-splashscreen:1.0.0' + implementation 'androidx.core:core-splashscreen:1.0.1' constraints { - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.10") { + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion") { because("kotlin-stdlib-jdk7 is now a part of kotlin-stdlib") } - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.10") { + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion") { because("kotlin-stdlib-jdk8 is now a part of kotlin-stdlib") } } + implementation "androidx.profileinstaller:profileinstaller:1.3.1" + baselineProfile project(':benchmark') } diff --git a/App_Resources/Android/before-plugins.gradle b/App_Resources/Android/before-plugins.gradle index 411b78e1..2dc6e176 100644 --- a/App_Resources/Android/before-plugins.gradle +++ b/App_Resources/Android/before-plugins.gradle @@ -1,9 +1,10 @@ ext { compileSdk = 34 targetSdk = 34 - androidBuildToolsVersion = "7.3.0" - buildToolsVersion = "33.0.1" - androidxVersion = "1.12.0" + kotlinVersion = "1.9.10" + androidBuildToolsVersion = "8.2.0-beta06" + buildToolsVersion = "34.0.0" + androidxVersion = "1.11.0-beta02" androidXAppCompat = "1.6.1" androidXAppCompatVersion = "1.6.1" androidXFragment = "1.6.1" @@ -11,7 +12,7 @@ ext { androidXRecyclerViewVersion = "1.3.1" androidxViewPager2Version = "1.1.0-beta02" androidXSwipeRefreshLayoutViewVersion = "1.2.0-alpha01" - androidXMaterial = "1.9.0" + androidXMaterial = "1.10.0" androidXBrowser = "1.6.0" androidXPreferenceVersion = "1.2.1" okHttpVersion = "4.10.0" diff --git a/App_Resources/Android/benchmark/.gitignore b/App_Resources/Android/benchmark/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/App_Resources/Android/benchmark/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/App_Resources/Android/benchmark/benchmark-rules.pro b/App_Resources/Android/benchmark/benchmark-rules.pro new file mode 100644 index 00000000..e9caf635 --- /dev/null +++ b/App_Resources/Android/benchmark/benchmark-rules.pro @@ -0,0 +1 @@ +-dontobfuscate diff --git a/App_Resources/Android/benchmark/build.gradle b/App_Resources/Android/benchmark/build.gradle new file mode 100644 index 00000000..a0b49820 --- /dev/null +++ b/App_Resources/Android/benchmark/build.gradle @@ -0,0 +1,101 @@ +import com.android.build.api.dsl.ManagedVirtualDevice + + +plugins { + id 'com.android.test' + id 'org.jetbrains.kotlin.android' +} + +android { + namespace 'com.akylas.weather.benchmark' + compileSdk 34 + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + defaultConfig { + minSdk 24 + targetSdk 34 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + kotlin { + jvmToolchain(17) + } + + buildTypes { + // This benchmark buildType is used for benchmarking, and should function like your + // release build (for example, with minification on). It's signed with a debug key + // for easy local/CI testing. + benchmark { + debuggable = true + signingConfig = debug.signingConfig + matchingFallbacks = ["debug"] + } + } + + testOptions { + managedDevices { + devices { + pixel2Api31(ManagedVirtualDevice) { + device = "Pixel 2" + apiLevel = 31 + systemImageSource = "aosp" + } + } + } + } + + targetProjectPath = ":app" + experimentalProperties["android.experimental.self-instrumenting"] = true +} + +dependencies { + implementation 'androidx.test.ext:junit:1.1.5' + implementation 'androidx.test.espresso:espresso-core:3.5.1' + implementation 'androidx.test.uiautomator:uiautomator:2.2.0' + implementation 'androidx.benchmark:benchmark-macro-junit4:1.2.0-rc02' +} + +androidComponents { + beforeVariants(selector().all()) { + enable = buildType == "benchmark" + } +} + + +// duplicated from app. should be refactored +def externalRuntimeExists = !findProject(':runtime').is(null) +def pluginDependencies + +repositories { + // used for local *.AAR files + pluginDependencies = nativescriptDependencies.collect { + "$rootDir/${it.directory}/$PLATFORMS_ANDROID" + } + + // some plugins may have their android dependencies in a /libs subdirectory + pluginDependencies.addAll(nativescriptDependencies.collect { + "$rootDir/${it.directory}/$PLATFORMS_ANDROID/libs" + }) + + if (!externalRuntimeExists) { + pluginDependencies.add("$rootDir/app/libs/runtime-libs") + } + + def appResourcesPath = getAppResourcesPath() + def localAppResourcesLibraries = "$appResourcesPath/Android/libs" + + pluginDependencies.add(localAppResourcesLibraries) + + if (pluginDependencies.size() > 0) { + + flatDir { + dirs pluginDependencies + } + } + + mavenCentral() +} \ No newline at end of file diff --git a/App_Resources/Android/benchmark/src/main/AndroidManifest.xml b/App_Resources/Android/benchmark/src/main/AndroidManifest.xml new file mode 100644 index 00000000..227314ee --- /dev/null +++ b/App_Resources/Android/benchmark/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/App_Resources/Android/benchmark/src/main/java/com/akylas/weather/benchmark/BaselineProfileGenerator.kt b/App_Resources/Android/benchmark/src/main/java/com/akylas/weather/benchmark/BaselineProfileGenerator.kt new file mode 100644 index 00000000..66dfd7c2 --- /dev/null +++ b/App_Resources/Android/benchmark/src/main/java/com/akylas/weather/benchmark/BaselineProfileGenerator.kt @@ -0,0 +1,70 @@ +package com.akylas.weather.benchmark + +import android.graphics.Point +import android.os.Build +import android.widget.TextView +import androidx.annotation.RequiresApi +import androidx.benchmark.macro.junit4.BaselineProfileRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Direction +import androidx.test.uiautomator.Until +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@LargeTest +class BaselineProfileGenerator { + @RequiresApi(Build.VERSION_CODES.P) + @get:Rule + val baselineProfileRule = BaselineProfileRule() + + @Before + fun grantPhonePermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + InstrumentationRegistry.getInstrumentation().uiAutomation.executeShellCommand( + "pm grant $PACKAGE_NAME android.permission.READ_EXTERNAL_STORAGE" + ) + InstrumentationRegistry.getInstrumentation().uiAutomation.executeShellCommand( + "pm grant $PACKAGE_NAME android.permission.WRITE_EXTERNAL_STORAGE" + ) + } + } + + @RequiresApi(Build.VERSION_CODES.P) + @Test + fun startup() = baselineProfileRule.collect( + packageName = PACKAGE_NAME, + profileBlock = { + startActivityAndWait() + // access file permission on android 12+ + device.wait(Until.hasObject(By.checkable(true)), 1_000) + val switch = device.findObject(By.checkable(true)); + if (switch != null) { + switch.click() + device.pressBack() + device.waitForIdle() + } + + // Waits for content to be visible, which represents time to fully drawn. + val input = By.desc("cartoMap") + device.wait(Until.hasObject(input), 2_000) + val scrollable = device.findObject(input) + // Setting a gesture margin is important otherwise gesture nav is triggered. +// scrollable.setGestureMargin(device.displayWidth / 5) + + // From center we scroll 2/3 of it which is 1/3 of the screen. + scrollable.drag(Point(0, scrollable.visibleCenter.y / 3)) + device.waitForIdle() + device.pressBack() + } + ) + + companion object { + private const val PACKAGE_NAME = "akylas.alpi.maps" + } +} \ No newline at end of file diff --git a/App_Resources/Android/benchmark/src/main/java/com/akylas/weather/benchmark/ExampleStartupBenchmark.kt b/App_Resources/Android/benchmark/src/main/java/com/akylas/weather/benchmark/ExampleStartupBenchmark.kt new file mode 100644 index 00000000..e52ee1be --- /dev/null +++ b/App_Resources/Android/benchmark/src/main/java/com/akylas/weather/benchmark/ExampleStartupBenchmark.kt @@ -0,0 +1,92 @@ +package com.akylas.weather.benchmark + +import android.content.Intent +import android.graphics.Point +import androidx.benchmark.macro.BaselineProfileMode +import androidx.benchmark.macro.CompilationMode +import androidx.benchmark.macro.MacrobenchmarkScope +import androidx.benchmark.macro.StartupMode +import androidx.benchmark.macro.StartupTimingMetric +import androidx.benchmark.macro.junit4.MacrobenchmarkRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +/** + * This is an example startup benchmark. + * + * + * It navigates to the device's home screen, and launches the default activity. + * + * + * Before running this benchmark: + * 1) switch your app's active build variant in the Studio (affects Studio runs only) + * 2) add `` to your app's manifest, within the `` tag + * + * + * Run this benchmark from Studio to see startup measurements, and captured system traces + * for investigating your app's performance. + */ +@RunWith(AndroidJUnit4::class) +@LargeTest +class ExampleStartupBenchmark { + @get:Rule + val mBenchmarkRule = MacrobenchmarkRule() + @Test + fun startupNoCompilation() = startup(CompilationMode.None()) + + @Test + fun startupPartialWithBaselineProfiles() = + startup(CompilationMode.Partial(baselineProfileMode = BaselineProfileMode.Require)) + + @Test + fun startupPartialCompilation() = startup( + CompilationMode.Partial( + baselineProfileMode = BaselineProfileMode.Disable, + warmupIterations = 3 + ) + ) + + @Test + fun startupFullCompilation() = startup(CompilationMode.Full()) + + private fun startup(compilationMode: CompilationMode) = mBenchmarkRule.measureRepeated( + packageName = PACKAGE_NAME, + metrics = listOf(StartupTimingMetric()), + compilationMode = compilationMode, + iterations = 10, + startupMode = StartupMode.COLD, + setupBlock = { + pressHome() + } + ) { + // Waits for the first rendered frame, which represents time to initial display. + val intent = Intent() + intent.setPackage(PACKAGE_NAME) + startActivityAndWait(intent) + + // Waits for content to be visible, which represents time to fully drawn. + device.wait(Until.hasObject(By.scrollable(true)), 5_000) + val recycler = device.findObject(By.scrollable(true)) + // Setting a gesture margin is important otherwise gesture nav is triggered. + recycler.setGestureMargin(device.displayWidth / 5) + + // From center we scroll 2/3 up to trigger a refresh + recycler.drag(Point(0, -recycler.visibleCenter.y / 3)) + + // From center we scroll 2/3 of it which is 1/3 of the screen. + recycler.drag(Point(0, recycler.visibleCenter.y / 3)) + device.waitForIdle() + device.pressBack() + } + + + companion object { + private const val PACKAGE_NAME = "akylas.alpi.maps" + + } +} \ No newline at end of file diff --git a/App_Resources/Android/benchmark/src/main/java/com/akylas/weather/benchmark/StartupProfileGenerator.kt b/App_Resources/Android/benchmark/src/main/java/com/akylas/weather/benchmark/StartupProfileGenerator.kt new file mode 100644 index 00000000..ff7f7400 --- /dev/null +++ b/App_Resources/Android/benchmark/src/main/java/com/akylas/weather/benchmark/StartupProfileGenerator.kt @@ -0,0 +1,69 @@ +package com.akylas.weather.benchmark + +import android.graphics.Point +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.benchmark.macro.junit4.BaselineProfileRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class StartupProfileGenerator { + @RequiresApi(Build.VERSION_CODES.P) + @get:Rule + val baselineProfileRule = BaselineProfileRule() + + @Before + fun grantPhonePermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + InstrumentationRegistry.getInstrumentation().uiAutomation.executeShellCommand( + "pm grant ${StartupProfileGenerator.PACKAGE_NAME} android.permission.READ_EXTERNAL_STORAGE" + ) + InstrumentationRegistry.getInstrumentation().uiAutomation.executeShellCommand( + "pm grant ${StartupProfileGenerator.PACKAGE_NAME} android.permission.WRITE_EXTERNAL_STORAGE" + ) + } + } + + @RequiresApi(Build.VERSION_CODES.P) + @Test + fun startup() = + baselineProfileRule.collect( + packageName = PACKAGE_NAME, + includeInStartupProfile = true + ) { + startActivityAndWait() + // access file permission on android 12+ + device.wait(Until.hasObject(By.checkable(true)), 1_000) + val switch = device.findObject(By.checkable(true)); + if (switch != null) { + switch.click() + device.pressBack() + device.waitForIdle() + } + + // Waits for content to be visible, which represents time to fully drawn. + val input = By.desc("cartoMap") + device.wait(Until.hasObject(input), 2_000) + val scrollable = device.findObject(input) + // Setting a gesture margin is important otherwise gesture nav is triggered. +// scrollable.setGestureMargin(device.displayWidth / 5) + + // From center we scroll 2/3 of it which is 1/3 of the screen. + scrollable.drag(Point(0, scrollable.visibleCenter.y / 3)) + device.waitForIdle() + device.pressBack() + } + + + companion object { + private const val PACKAGE_NAME = "akylas.alpi.maps" + + } +} \ No newline at end of file diff --git a/App_Resources/Android/buildscript.gradle b/App_Resources/Android/buildscript.gradle new file mode 100644 index 00000000..961391fd --- /dev/null +++ b/App_Resources/Android/buildscript.gradle @@ -0,0 +1,17 @@ + +repositories { + google() + mavenCentral() +} +// dependencies { +// classpath 'io.sentry:sentry-android-gradle-plugin:3.0.1' +// } +// sentry { +// includeProguardMapping = false +// autoUploadProguardMapping = false +// experimentalGuardsquareSupport = false +// uploadNativeSymbols = false +// tracingInstrumentation { +// enabled = false +// } +// } \ No newline at end of file diff --git a/App_Resources/Android/native-api-usage.json b/App_Resources/Android/native-api-usage.json new file mode 100644 index 00000000..1fa6f844 --- /dev/null +++ b/App_Resources/Android/native-api-usage.json @@ -0,0 +1,52 @@ +{ + "whitelist-plugins-usages": true, + "whitelist": [ + "com.akylas.documentscanner:CropView", + "com.akylas.documentscanner:CustomImageAnalysisCallback", + "com.akylas.documentscanner:CustomImageAnalysisCallback.Companion", + "androidx.appcompat.app:AppCompatDelegate", + "androidx.core.os:LocaleListCompat", + "android.graphics.pdf:PdfDocument", + "android.graphics.pdf:PdfDocument.PageInfo", + "android.graphics.pdf:PdfDocument.PageInfo.Builder", + "java.io:FileOutputStream", + "androidx.core.widget:NestedScrollView", + "android.text.format:DateFormat", + "android.graphics:BitmapShader" + ], + "blacklist": [ + "org.ow2.asmdex*:*", + "org.nativescript.widgets:TabItemSpec*", + "org.nativescript.widgets:TabsBar*", + "org.nativescript.widgets:SegmentedBar*", + "org.nativescript.widgets:TabViewPager*", + "org.nativescript.widgets:TabLayout*", + "org.nativescript.widgets:BottomNavigationBar*", + "com.google.android.material.chip*:*", + "com.google.android.material.appbar*:*", + "com.google.android.material.floatingactionbutton*:*", + "com.google.android.material.bottomappbar*:*", + "com.google.android.material.internal*:*", + "com.google.android.material.transformation*:*", + "com.google.android.material.theme*:*", + "*:*BuildConfig", + "*:R.*", + "*:R", + "*:R$" + ], + "proguard_blacklist": [ + "org.nativescript.widgets:TabItemSpec*", + "org.nativescript.widgets:TabsBar*", + "org.nativescript.widgets:SegmentedBar*", + "org.nativescript.widgets:TabViewPager*", + "org.nativescript.widgets:TabLayout*", + "org.nativescript.widgets:BottomNavigationBar*", + "com.google.android.material.chip*:*", + "com.google.android.material.appbar*:*", + "com.google.android.material.floatingactionbutton*:*", + "com.google.android.material.bottomappbar*:*", + "com.google.android.material.internal*:*", + "com.google.android.material.transformation*:*", + "com.google.android.material.theme*:*" + ] +} \ No newline at end of file diff --git a/App_Resources/Android/rootbuildscript.gradle b/App_Resources/Android/rootbuildscript.gradle new file mode 100644 index 00000000..d80485e4 --- /dev/null +++ b/App_Resources/Android/rootbuildscript.gradle @@ -0,0 +1,7 @@ +repositories { + google() + mavenCentral() +} +dependencies { + classpath 'androidx.baselineprofile:androidx.baselineprofile.gradle.plugin:1.2.0-alpha16' +} \ No newline at end of file diff --git a/App_Resources/Android/settings.gradle b/App_Resources/Android/settings.gradle new file mode 100644 index 00000000..ea761ba3 --- /dev/null +++ b/App_Resources/Android/settings.gradle @@ -0,0 +1,5 @@ +// if (hasProperty('debugCarto')) { +// include ':carto-mobile-sdk' +// project(':carto-mobile-sdk').projectDir = new File("/home/mguillon/dev/carto/mobile-sdk/scripts/android-dev/carto_mobile_sdk") +// } +include ':benchmark' diff --git a/App_Resources/Android/src/main/AndroidManifest.xml b/App_Resources/Android/src/main/AndroidManifest.xml index 736a7516..5a1657e0 100644 --- a/App_Resources/Android/src/main/AndroidManifest.xml +++ b/App_Resources/Android/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..36306872 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 Martin Guillon + +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. diff --git a/app.webpack.config.js b/app.webpack.config.js index 988cff5d..fe10cbf0 100644 --- a/app.webpack.config.js +++ b/app.webpack.config.js @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/unbound-method */ const webpackConfig = require('./webpack.config.js'); const webpack = require('webpack'); const { readFileSync, readdirSync } = require('fs'); @@ -5,11 +6,11 @@ const { dirname, join, relative, resolve } = require('path'); const nsWebpack = require('@nativescript/webpack'); const CopyPlugin = require('copy-webpack-plugin'); const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); -const { sentryWebpackPlugin } = require('@sentry/webpack-plugin'); +const SentryCliPlugin = require('@sentry/webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin'); const IgnoreNotFoundExportPlugin = require('./scripts/IgnoreNotFoundExportPlugin'); -const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); const Fontmin = require('@akylas/fontmin'); +const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); function fixedFromCharCode(codePt) { if (codePt > 0xffff) { @@ -31,13 +32,13 @@ module.exports = (env, params = {}) => { env = Object.assign( {}, { - production: env.production !== false, + production: true, sentry: true, uploadSentry: true, testlog: true, noconsole: false, sourceMap: true, - uglify: env.production !== false + uglify: true }, env ); @@ -46,11 +47,12 @@ module.exports = (env, params = {}) => { {}, { production: true, - noconsole: true, sentry: false, uploadSentry: false, + noconsole: true, apiKeys: true, sourceMap: false, + testlog: false, uglify: true }, env @@ -74,32 +76,45 @@ module.exports = (env, params = {}) => { const { appPath = nconfig.appPath, appResourcesPath = nconfig.appResourcesPath, - production, - sourceMap, - hiddenSourceMap, - inlineSourceMap, - sentry, + hmr, // --env.hmr + production, // --env.production + sourceMap, // --env.sourceMap + hiddenSourceMap, // --env.hiddenSourceMap + inlineSourceMap, // --env.inlineSourceMap + sentry, // --env.sentry uploadSentry, - uglify, - profile, - noconsole, - timeline, - cartoLicense = false, - devlog, - testlog, - fork = true, - startOnCam = false, + verbose, // --env.verbose + uglify, // --env.uglify + noconsole, // --env.noconsole + devlog, // --env.devlog + testlog, // --env.testlog + fakeall, // --env.fakeall + profile, // --env.profile + fork = true, // --env.fakeall + accessibility = false, // --env.adhoc + adhoc, // --env.adhoc + timeline, // --env.timeline + locale = 'auto', // --env.locale + theme = 'auto', // --env.theme keep_classnames_functionnames = false, - locale = 'auto', - theme = 'auto', - adhoc + startOnCam = false } = env; console.log('env', env); env.appPath = appPath; env.appResourcesPath = appResourcesPath; env.appComponents = env.appComponents || []; + const ignoredSvelteWarnings = new Set(['a11y-no-onchange', 'a11y-label-has-associated-control', 'a11y-autofocus', 'illegal-attribute-character']); nsWebpack.chainWebpack((config, env) => { + config.module + .rule('svelte') + .use('svelte-loader') + .tap((options) => { + options.onwarn = function (warning, onwarn) { + return ignoredSvelteWarnings.has(warning.code) || onwarn(warning); + }; + return options; + }); config.when(env.production, (config) => { config.module .rule('svelte') @@ -107,7 +122,7 @@ module.exports = (env, params = {}) => { .loader('string-replace-loader') .before('svelte-loader') .options({ - search: 'createElementNS\\("https:\\/\\/svelte.dev\\/docs#template-syntax-svelte-options"', + search: 'createElementNS\\("https:\\/\\/svelte.dev\\/docs\\/special-elements#svelte-options"', replace: 'createElementNS(svN', flags: 'gm' }) @@ -139,13 +154,19 @@ module.exports = (env, params = {}) => { // config.stats = { preset: 'minimal', chunkModules: true, modules: true, usedExports: true }; } + const supportedLocales = readdirSync(join(projectRoot, appPath, 'i18n')) + .filter((s) => s.endsWith('.json')) + .map((s) => s.replace('.json', '')); config.externals.push('~/licenses.json'); config.externals.push(function ({ context, request }, cb) { if (/i18n$/i.test(context)) { - return cb(null, './i18n/' + request.slice(2)); + return cb(null, join('~/i18n/', request)); } cb(); }); + supportedLocales.forEach((l) => { + config.externals.push(`~/i18n/${l}.json`); + }); const coreModulesPackageName = fork ? '@akylas/nativescript' : '@nativescript/core'; if (fork) { @@ -155,6 +176,9 @@ module.exports = (env, params = {}) => { 'tns-core-modules': `${coreModulesPackageName}` }); } + Object.assign(config.resolve.alias, { + 'kiss-orm': '@akylas/kiss-orm' + }); let appVersion; let buildNumber; if (platform === 'android') { @@ -169,55 +193,10 @@ module.exports = (env, params = {}) => { buildNumber = plistData.match(/CFBundleVersion<\/key>[\s\n]*([0-9]*)<\/string>/)[1]; } - Object.assign(config.resolve.alias, { - '../driver/oracle/OracleDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - './oracle/OracleDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - '../driver/cockroachdb/CockroachDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - './cockroachdb/CockroachDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - '../driver/cordova/CordovaDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - './cordova/CordovaDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - './react-native/ReactNativeDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - '../driver/react-native/ReactNativeDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - './nativescript/NativescriptDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - '../driver/nativescript/NativescriptDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - './mysql/MysqlDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - '../driver/mysql/MysqlDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - './postgres/PostgresDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - '../driver/postgres/PostgresDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - './expo/ExpoDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - './aurora-data-api/AuroraDataApiDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - '../driver/aurora-data-api/AuroraDataApiDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - './sqlite/SqliteDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - '../driver/sqljs/SqljsDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - './sqljs/SqljsDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - '../driver/sqlserver/SqlServerDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - './sqlserver/SqlServerDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - './mongodb/MongoDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - '../driver/mongodb/MongoDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - './sap/SapDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - '../driver/sap/SapDriver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - './better-sqlite3/BetterSqlite3Driver': '@nativescript-community/sqlite/typeorm/NativescriptDriver', - '../driver/better-sqlite3/BetterSqlite3Driver': '@nativescript-community/sqlite/typeorm/NativescriptDriver' - }); - - config.externalsPresets = { node: false }; - config.resolve.fallback = config.resolve.fallback || {}; - // config.resolve.fallback.buffer = require.resolve('buffer/'); - config.resolve.fallback.buffer = false; - config.resolve.fallback.util = require.resolve('util/'); - config.resolve.fallback.path = false; - config.resolve.fallback.fs = false; - config.resolve.fallback.assert = false; - config.resolve.fallback.tty = false; - config.resolve.fallback.os = false; - const package = require('./package.json'); const isIOS = platform === 'ios'; const isAndroid = platform === 'android'; const APP_STORE_ID = process.env.IOS_APP_ID; - const supportedLocales = readdirSync(join(projectRoot, appPath, 'i18n')) - .filter((s) => s.endsWith('.json')) - .map((s) => s.replace('.json', '')); const defines = { PRODUCTION: !!production, process: 'global.process', @@ -235,7 +214,6 @@ module.exports = (env, params = {}) => { __APP_ID__: `"${nconfig.id}"`, __APP_VERSION__: `"${appVersion}"`, __APP_BUILD_NUMBER__: `"${buildNumber}"`, - __CARTO_PACKAGESERVICE__: cartoLicense, SUPPORTED_LOCALES: JSON.stringify(supportedLocales), DEFAULT_LOCALE: `"${locale}"`, DEFAULT_THEME: `"${theme}"`, @@ -245,7 +223,7 @@ module.exports = (env, params = {}) => { SENTRY_DSN: `"${process.env.SENTRY_DSN}"`, SENTRY_PREFIX: `"${!!sentry ? process.env.SENTRY_PREFIX : ''}"`, GIT_URL: `"${package.repository}"`, - // SUPPORT_URL: `"${package.bugs.url}"`, + SUPPORT_URL: `"${package.bugs.url}"`, STORE_LINK: `"${isAndroid ? `https://play.google.com/store/apps/details?id=${nconfig.id}` : `https://itunes.apple.com/app/id${APP_STORE_ID}`}"`, STORE_REVIEW_LINK: `"${ isIOS @@ -345,6 +323,14 @@ module.exports = (env, params = {}) => { }, flags: 'g' } + }, + { + loader: 'string-replace-loader', + options: { + search: '__PACKAGE__', + replace: nconfig.id, + flags: 'g' + } } ] }); @@ -402,7 +388,6 @@ module.exports = (env, params = {}) => { clearTimeout: [require.resolve(coreModulesPackageName + '/timer/index.' + platform), 'clearTimeout'], setInterval: [require.resolve(coreModulesPackageName + '/timer/index.' + platform), 'setInterval'], clearInterval: [require.resolve(coreModulesPackageName + '/timer/index.' + platform), 'clearInterval'], - // FormData: [require.resolve(coreModulesPackageName + '/polyfills/formdata'), 'FormData'], requestAnimationFrame: [require.resolve(coreModulesPackageName + '/animation-frame'), 'requestAnimationFrame'], cancelAnimationFrame: [require.resolve(coreModulesPackageName + '/animation-frame'), 'cancelAnimationFrame'] }) @@ -421,13 +406,15 @@ module.exports = (env, params = {}) => { }) ); if (fork) { - config.plugins.push( - new webpack.NormalModuleReplacementPlugin(/accessibility$/, (resource) => { - if (resource.context.match(nativescriptReplace)) { - resource.request = '~/shims/accessibility'; - } - }) - ); + if (!accessibility) { + config.plugins.push( + new webpack.NormalModuleReplacementPlugin(/accessibility$/, (resource) => { + if (resource.context.match(nativescriptReplace)) { + resource.request = '~/shims/accessibility'; + } + }) + ); + } config.plugins.push( new webpack.NormalModuleReplacementPlugin(/action-bar$/, (resource) => { if (resource.context.match(nativescriptReplace)) { @@ -505,6 +492,7 @@ module.exports = (env, params = {}) => { } ); } + if (!!production) { config.plugins.push( new ForkTsCheckerWebpackPlugin({ @@ -523,15 +511,14 @@ module.exports = (env, params = {}) => { }) ); config.plugins.push( - new sentryWebpackPlugin({ - release: { - name: `${nconfig.id}@${appVersion}+${buildNumber}`, - dist: `${buildNumber}.${platform}` - }, - sourcemaps: { - assets: [dist, join(dist, process.env.SOURCEMAP_REL_DIR)], - ignore: ['tns-java-classes', 'hot-update'] - } + new SentryCliPlugin({ + release: appVersion, + urlPrefix: 'app:///', + rewrite: true, + release: `${nconfig.id}@${appVersion}+${buildNumber}`, + dist: `${buildNumber}.${platform}`, + ignoreFile: '.sentrycliignore', + include: [dist, join(dist, process.env.SOURCEMAP_REL_DIR)] }) ); } else { @@ -540,17 +527,18 @@ module.exports = (env, params = {}) => { } else { config.devtool = false; } - - config.experiments = { - topLevelAwait: true - }; - config.externalsPresets = { node: false }; config.resolve.fallback = config.resolve.fallback || {}; // config.resolve.fallback.timers = require.resolve('timers/'); config.resolve.fallback.stream = false; config.resolve.fallback.timers = false; - // config.optimization.usedExports = true; + config.resolve.fallback.buffer = false; + config.resolve.fallback.util = require.resolve('util/'); + config.resolve.fallback.path = false; + config.resolve.fallback.fs = false; + config.resolve.fallback.assert = false; + config.resolve.fallback.tty = false; + config.resolve.fallback.os = false; config.optimization.minimize = uglify !== undefined ? !!uglify : production; const isAnySourceMapEnabled = !!sourceMap || !!hiddenSourceMap || !!inlineSourceMap; const actual_keep_classnames_functionnames = keep_classnames_functionnames || platform !== 'android'; @@ -559,7 +547,7 @@ module.exports = (env, params = {}) => { new TerserPlugin({ parallel: true, terserOptions: { - ecma: isAndroid ? 2020 : 2020, + ecma: 2020, module: true, toplevel: false, keep_classnames: actual_keep_classnames_functionnames, diff --git a/app/CropView.ts b/app/CropView.ts index 6f0c2e35..9d86fa98 100644 --- a/app/CropView.ts +++ b/app/CropView.ts @@ -7,10 +7,8 @@ export const fillAlphaProperty = new Property({ name: 'fillAlp export class CropView extends View { createNativeView() { if (__ANDROID__) { - //@ts-ignore return new com.akylas.documentscanner.CropView(this._context); } else if (__IOS__) { - //@ts-ignore return NSCropView.alloc().init(); } return null; diff --git a/app/components/About.svelte b/app/components/About.svelte index dac2d757..22c75cda 100644 --- a/app/components/About.svelte +++ b/app/components/About.svelte @@ -5,7 +5,7 @@ import { openLink } from '~/utils/ui'; import CActionBar from './CActionBar.svelte'; import SettingLabelIcon from './SettingLabelIcon.svelte'; - import { showBottomSheet } from '@nativescript-community/ui-material-bottomsheet/svelte'; + import { showBottomSheet } from '~/utils/svelte/bottomsheet'; import { openUrl } from '@nativescript/core/utils'; const appVersion = __APP_VERSION__ + '.' + __APP_ID__; @@ -44,14 +44,14 @@ - - - onTap('share')} /> - onTap('github')} /> - onTap('third_party')} /> - onTap('share')} /> - onTap('review')} /> - + + + onTap('share')} /> + onTap('github')} /> + onTap('third_party')} /> + onTap('share')} /> + onTap('review')} /> + diff --git a/app/components/ActionSheet.svelte b/app/components/ActionSheet.svelte index 32005c75..ebcffd48 100644 --- a/app/components/ActionSheet.svelte +++ b/app/components/ActionSheet.svelte @@ -11,11 +11,11 @@ {#if title} -