diff --git a/composer.lock b/composer.lock index af8a6ff29..694fbeccc 100644 --- a/composer.lock +++ b/composer.lock @@ -149,12 +149,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -302,8 +302,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -515,16 +515,16 @@ "packages-dev": [ { "name": "brianium/paratest", - "version": "v7.6.1", + "version": "v7.8.2", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "9ac8eda68f17acda4dad4aa02ecdcc327d7e6675" + "reference": "22c45ae560825291f11b32455878b89e718e758c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/9ac8eda68f17acda4dad4aa02ecdcc327d7e6675", - "reference": "9ac8eda68f17acda4dad4aa02ecdcc327d7e6675", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/22c45ae560825291f11b32455878b89e718e758c", + "reference": "22c45ae560825291f11b32455878b89e718e758c", "shasum": "" }, "require": { @@ -535,24 +535,24 @@ "fidry/cpu-core-counter": "^1.2.0", "jean85/pretty-package-versions": "^2.1.0", "php": "~8.2.0 || ~8.3.0 || ~8.4.0", - "phpunit/php-code-coverage": "^11.0.7", - "phpunit/php-file-iterator": "^5.1.0", - "phpunit/php-timer": "^7.0.1", - "phpunit/phpunit": "^11.4.4", - "sebastian/environment": "^7.2.0", - "symfony/console": "^6.4.14 || ^7.1.7", - "symfony/process": "^6.4.14 || ^7.1.7" + "phpunit/php-code-coverage": "^11.0.8 || ^12", + "phpunit/php-file-iterator": "^5.1.0 || ^6", + "phpunit/php-timer": "^7.0.1 || ^8", + "phpunit/phpunit": "^11.5.7 || ^12.0.1", + "sebastian/environment": "^7.2.0 || ^8", + "symfony/console": "^6.4.17 || ^7.2.1", + "symfony/process": "^6.4.15 || ^7.2.0" }, "require-dev": { "doctrine/coding-standard": "^12.0.0", "ext-pcov": "*", "ext-posix": "*", - "phpstan/phpstan": "^2", - "phpstan/phpstan-deprecation-rules": "^2", - "phpstan/phpstan-phpunit": "^2", - "phpstan/phpstan-strict-rules": "^2", - "squizlabs/php_codesniffer": "^3.11.1", - "symfony/filesystem": "^6.4.13 || ^7.1.6" + "phpstan/phpstan": "^2.1.5", + "phpstan/phpstan-deprecation-rules": "^2.0.1", + "phpstan/phpstan-phpunit": "^2.0.4", + "phpstan/phpstan-strict-rules": "^2.0.3", + "squizlabs/php_codesniffer": "^3.11.3", + "symfony/filesystem": "^6.4.13 || ^7.2.0" }, "bin": [ "bin/paratest", @@ -592,7 +592,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v7.6.1" + "source": "https://github.com/paratestphp/paratest/tree/v7.8.2" }, "funding": [ { @@ -604,7 +604,7 @@ "type": "paypal" } ], - "time": "2024-12-05T10:55:39+00:00" + "time": "2025-02-19T07:26:44+00:00" }, { "name": "fidry/cpu-core-counter", @@ -728,16 +728,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.12.1", + "version": "1.13.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" + "reference": "024473a478be9df5fdaca2c793f2232fe788e414" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", + "reference": "024473a478be9df5fdaca2c793f2232fe788e414", "shasum": "" }, "require": { @@ -776,7 +776,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0" }, "funding": [ { @@ -784,20 +784,20 @@ "type": "tidelift" } ], - "time": "2024-11-08T17:47:46+00:00" + "time": "2025-02-12T12:17:51+00:00" }, { "name": "nikic/php-parser", - "version": "v5.3.1", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" + "reference": "447a020a1f875a434d62f2a401f53b82a396e494" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", + "reference": "447a020a1f875a434d62f2a401f53b82a396e494", "shasum": "" }, "require": { @@ -840,9 +840,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" }, - "time": "2024-10-08T18:51:32+00:00" + "time": "2024-12-30T11:07:19+00:00" }, { "name": "phar-io/manifest", @@ -964,16 +964,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "11.0.7", + "version": "11.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca" + "reference": "418c59fd080954f8c4aa5631d9502ecda2387118" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f7f08030e8811582cc459871d28d6f5a1a4d35ca", - "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/418c59fd080954f8c4aa5631d9502ecda2387118", + "reference": "418c59fd080954f8c4aa5631d9502ecda2387118", "shasum": "" }, "require": { @@ -992,7 +992,7 @@ "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^11.4.1" + "phpunit/phpunit": "^11.5.0" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -1030,7 +1030,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.7" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.8" }, "funding": [ { @@ -1038,7 +1038,7 @@ "type": "github" } ], - "time": "2024-10-09T06:21:38+00:00" + "time": "2024-12-11T12:34:27+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1287,16 +1287,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.0", + "version": "11.5.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0569902506a6c0878930b87ea79ec3b50ea563f7" + "reference": "c9bd61aab12f0fc5e82ecfe621ff518a1d1f1049" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0569902506a6c0878930b87ea79ec3b50ea563f7", - "reference": "0569902506a6c0878930b87ea79ec3b50ea563f7", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c9bd61aab12f0fc5e82ecfe621ff518a1d1f1049", + "reference": "c9bd61aab12f0fc5e82ecfe621ff518a1d1f1049", "shasum": "" }, "require": { @@ -1310,14 +1310,14 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0.7", + "phpunit/php-code-coverage": "^11.0.8", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", "sebastian/cli-parser": "^3.0.2", - "sebastian/code-unit": "^3.0.1", - "sebastian/comparator": "^6.2.1", + "sebastian/code-unit": "^3.0.2", + "sebastian/comparator": "^6.3.0", "sebastian/diff": "^6.0.2", "sebastian/environment": "^7.2.0", "sebastian/exporter": "^6.3.0", @@ -1368,7 +1368,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.0" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.8" }, "funding": [ { @@ -1384,7 +1384,7 @@ "type": "tidelift" } ], - "time": "2024-12-06T05:57:38+00:00" + "time": "2025-02-18T06:26:59+00:00" }, { "name": "psr/container", @@ -1498,23 +1498,23 @@ }, { "name": "sebastian/code-unit", - "version": "3.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "6bb7d09d6623567178cf54126afa9c2310114268" + "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/6bb7d09d6623567178cf54126afa9c2310114268", - "reference": "6bb7d09d6623567178cf54126afa9c2310114268", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca", + "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.5" }, "type": "library", "extra": { @@ -1543,7 +1543,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", "security": "https://github.com/sebastianbergmann/code-unit/security/policy", - "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.2" }, "funding": [ { @@ -1551,7 +1551,7 @@ "type": "github" } ], - "time": "2024-07-03T04:44:28+00:00" + "time": "2024-12-12T09:59:06+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -1611,16 +1611,16 @@ }, { "name": "sebastian/comparator", - "version": "6.2.1", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739" + "reference": "d4e47a769525c4dd38cea90e5dcd435ddbbc7115" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/43d129d6a0f81c78bee378b46688293eb7ea3739", - "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/d4e47a769525c4dd38cea90e5dcd435ddbbc7115", + "reference": "d4e47a769525c4dd38cea90e5dcd435ddbbc7115", "shasum": "" }, "require": { @@ -1633,6 +1633,9 @@ "require-dev": { "phpunit/phpunit": "^11.4" }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, "type": "library", "extra": { "branch-alias": { @@ -1676,7 +1679,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.2.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.0" }, "funding": [ { @@ -1684,7 +1687,7 @@ "type": "github" } ], - "time": "2024-10-31T05:30:08+00:00" + "time": "2025-01-06T10:28:19+00:00" }, { "name": "sebastian/complexity", @@ -2364,16 +2367,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.11.1", + "version": "3.11.3", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "19473c30efe4f7b3cd42522d0b2e6e7f243c6f87" + "reference": "ba05f990e79cbe69b9f35c8c1ac8dca7eecc3a10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/19473c30efe4f7b3cd42522d0b2e6e7f243c6f87", - "reference": "19473c30efe4f7b3cd42522d0b2e6e7f243c6f87", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/ba05f990e79cbe69b9f35c8c1ac8dca7eecc3a10", + "reference": "ba05f990e79cbe69b9f35c8c1ac8dca7eecc3a10", "shasum": "" }, "require": { @@ -2438,9 +2441,13 @@ { "url": "https://opencollective.com/php_codesniffer", "type": "open_collective" + }, + { + "url": "https://thanks.dev/phpcsstandards", + "type": "thanks_dev" } ], - "time": "2024-11-16T12:02:36+00:00" + "time": "2025-01-23T17:04:15+00:00" }, { "name": "staabm/side-effects-detector", @@ -2496,16 +2503,16 @@ }, { "name": "symfony/console", - "version": "v7.2.0", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "23c8aae6d764e2bae02d2a99f7532a7f6ed619cf" + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/23c8aae6d764e2bae02d2a99f7532a7f6ed619cf", - "reference": "23c8aae6d764e2bae02d2a99f7532a7f6ed619cf", + "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", "shasum": "" }, "require": { @@ -2569,7 +2576,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.2.0" + "source": "https://github.com/symfony/console/tree/v7.2.1" }, "funding": [ { @@ -2585,7 +2592,7 @@ "type": "tidelift" } ], - "time": "2024-11-06T14:24:19+00:00" + "time": "2024-12-11T03:49:26+00:00" }, { "name": "symfony/polyfill-intl-grapheme", @@ -2831,12 +2838,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { diff --git a/example.php b/example.php index e8f4e3621..8ddf2e3bd 100644 --- a/example.php +++ b/example.php @@ -3,6 +3,7 @@ include_once 'vendor/autoload.php'; use Appwrite\SDK\Language\GraphQL; +use Appwrite\SDK\Language\KMP; use Appwrite\Spec\Swagger2; use Appwrite\SDK\SDK; use Appwrite\SDK\Language\Web; @@ -464,6 +465,31 @@ function getSSLPage($url) { ; $sdk->generate(__DIR__ . '/examples/android'); + // KMP + + $sdk = new SDK(new KMP(), new Swagger2($spec)); + + $sdk + ->setName('KMP') + ->setNamespace('io appwrite') + ->setDescription('Appwrite is an open-source backend as a service server that abstract and simplify complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way. Use the Flutter SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools. For full API documentation and tutorials go to https://appwrite.io/docs') + ->setShortDescription('Appwrite KMP SDK') + ->setURL('https://example.com') + ->setGitUserName('appwrite') + ->setGitRepoName('sdk-for-kmp') + ->setLogo('https://appwrite.io/v1/images/console.png') + ->setLicenseContent('test test test') + ->setWarning('**This SDK is compatible with Appwrite server version 0.7.x. For older versions, please check previous releases.**') + ->setChangelog('**CHANGELOG**') + ->setVersion('0.0.0-SNAPSHOT') + ->setTwitter('appwrite_io') + ->setDiscord('564160730845151244', 'https://appwrite.io/discord') + ->setDefaultHeaders([ + 'x-appwrite-response-format' => '1.6.0', + ]) + ; + $sdk->generate(__DIR__ . '/examples/kmp'); + // Kotlin $sdk = new SDK(new Kotlin(), new Swagger2($spec)); diff --git a/src/SDK/Language/Android.php b/src/SDK/Language/Android.php index 8f1fdb1f9..22b8af970 100644 --- a/src/SDK/Language/Android.php +++ b/src/SDK/Language/Android.php @@ -339,11 +339,11 @@ public function getFiles(): array ]; } - protected function getReturnType(array $method, array $spec, string $namespace, string $generic = 'T'): string + protected function getReturnType(array $method, array $spec, string $namespace, string $generic = 'T', bool $withGeneric = true): string { if ($method['type'] === 'webAuth') { return 'Bool'; } - return parent::getReturnType($method, $spec, $namespace, $generic); + return parent::getReturnType($method, $spec, $namespace, $generic, $withGeneric); } } diff --git a/src/SDK/Language/KMP.php b/src/SDK/Language/KMP.php new file mode 100644 index 000000000..5c6aae793 --- /dev/null +++ b/src/SDK/Language/KMP.php @@ -0,0 +1,636 @@ +getName() !== 'propertyType'; + }); + + $filters[] = new TwigFilter('propertyType', function (array $property, array $spec, string $generic = 'T', $contextual = false) { + return $this->getPropertyType($property, $spec, $generic, $contextual); + }); + + $filters[] = new TwigFilter('webAuthServices', function (array $spec) { + return $this->getWebAuthServices($spec); + }); + + $filters[] = new TwigFilter('propertySerializerName', function (array $property) { + return $this->getPropertySerializerName($property); + }); + + return $filters; + } + + protected function getReturnType(array $method, array $spec, string $namespace, string $generic = 'T', bool $withGeneric = true): string + { + if ($method['type'] === 'webAuth') { + return 'Bool'; + } + return parent::getReturnType($method, $spec, $namespace, $generic, $withGeneric); + } + + protected function getWebAuthServices(array $spec): array + { + $webAuthServices = []; + foreach ($spec['services'] as $service) { + $webAuthMethods = []; + $hasWebAuth = false; + foreach ($service['methods'] as $method) { + if ($method['type'] === 'webAuth') { + $webAuthMethods[] = [ + 'methodName' => $method['name'], + 'parameters' => $method['parameters'], + 'path' => $method['path'], + 'auth' => $method['auth'] + ]; + $hasWebAuth = true; + } + } + if ($hasWebAuth) { + $webAuthServices[] = [ + 'methods' => $webAuthMethods, + 'className' => $service['name'] + ]; + } + } + return $webAuthServices; + } + + + protected function getPropertyType(array $property, array $spec, string $generic = 'T', bool $contextual = false): string + { + $type = parent::getPropertyType($property, $spec, $generic); + if ($contextual && ($type === 'List' || $type === 'List?')) { + $type = 'List<@Contextual Any>'; + } + return $type; + } + + protected function getPropertySerializerName(array $property): string + { + if (isset($property['enumName'])) { + return 'io.appwrite.enums.' . \ucfirst($property['enumName']) . 'Serializer'; + } + if (!empty($property['enumValues'])) { + return 'io.appwrite.enums.' . \ucfirst($property['name']) . 'Serializer'; + } + if (isset($property['items'])) { + $property['array'] = $property['items']; + } + + $name = match ($property['type']) { + self::TYPE_INTEGER => 'Long.serializer()', + self::TYPE_NUMBER => 'Double.serializer()', + self::TYPE_STRING => 'String.serializer()', + self::TYPE_BOOLEAN => 'Boolean.serializer()', + self::TYPE_ARRAY => (!empty(($property['array'] ?? [])['type']) && !\is_array($property['array']['type'])) + ? 'ListSerializer(' . $this->getPropertySerializerName($property['array']) . ')' + : 'ListSerializer(DynamicLookupSerializer)', + self::TYPE_OBJECT => 'DynamicLookupSerializer', + default => $property['type'] . 'Serializer', + }; + + return $name; + } + + + public function getFiles(): array + { + return [ + // Root project config + [ + 'scope' => 'copy', + 'destination' => '.github/workflows/publish.yml', + 'template' => '/kmp/.github/workflows/publish.yml', + ], +// [ +// 'scope' => 'method', +// 'destination' => 'docs/examples/kotlin/{{service.name | caseLower}}/{{method.name | caseDash}}.md', +// 'template' => '/kmp/docs/kotlin/example.md.twig', +// ], +// [ +// 'scope' => 'method', +// 'destination' => 'docs/examples/java/{{service.name | caseLower}}/{{method.name | caseDash}}.md', +// 'template' => '/kmp/docs/java/example.md.twig', +// ], + + // Gradle files + [ + 'scope' => 'copy', + 'destination' => 'gradle/wrapper/gradle-wrapper.jar', + 'template' => '/kmp/gradle/wrapper/gradle-wrapper.jar', + ], + [ + 'scope' => 'copy', + 'destination' => 'gradle/wrapper/gradle-wrapper.properties', + 'template' => '/kmp/gradle/wrapper/gradle-wrapper.properties', + ], + [ + 'scope' => 'copy', + 'destination' => 'gradle/libs.versions.toml', + 'template' => '/kmp/gradle/libs.versions.toml', + ], + + // Root files + [ + 'scope' => 'copy', + 'destination' => '.gitignore', + 'template' => '/kmp/.gitignore', + ], + [ + 'scope' => 'default', + 'destination' => 'build.gradle.kts', + 'template' => '/kmp/build.gradle.kts.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'settings.gradle.kts', + 'template' => '/kmp/settings.gradle.kts', + ], + [ + 'scope' => 'default', + 'destination' => 'CHANGELOG.md', + 'template' => '/kmp/CHANGELOG.md.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'README.md', + 'template' => '/kmp/README.md.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'LICENSE.md', + 'template' => '/kmp/LICENSE.md.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'gradlew', + 'template' => '/kmp/gradlew', + ], + [ + 'scope' => 'default', + 'destination' => 'gradlew.bat', + 'template' => '/kmp/gradlew.bat', + ], + [ + 'scope' => 'default', + 'destination' => 'gradle.properties', + 'template' => '/kmp/gradle.properties', + ], + + // Shared module + [ + 'scope' => 'default', + 'destination' => 'shared/build.gradle.kts', + 'template' => '/kmp/shared/build.gradle.kts.twig', + ], + + // Common Main + // Common Main root files + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/BaseClient.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/BaseClient.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/Client.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/Client.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/ID.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/ID.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/Permission.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/Permission.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/Query.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/Query.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/Role.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/Role.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/Service.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/Service.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/WebAuthComponent.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/WebAuthComponent.kt.twig', + ], + + + // Coroutines + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/coroutines/Callback.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/coroutines/Callback.kt.twig', + ], + + // Enums + [ + 'scope' => 'enum', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/enums/{{ enum.name | caseUcfirst }}.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/enums/Enum.kt.twig', + ], + + // Exceptions + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/exceptions/Exception.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/exceptions/Exception.kt.twig', + ], + + // Extensions + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/extensions/CollectionExtensions.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/extensions/CollectionExtensions.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/extensions/JsonExtensions.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/extensions/JsonExtensions.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/extensions/TypeExtensions.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/extensions/TypeExtensions.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/extensions/JsonObjectBuilderExtensions.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/extensions/JsonObjectBuilderExtensions.kt.twig', + ], + + // File Operations + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/fileOperations/FileOperations.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/fileOperations/FileOperations.kt.twig', + ], + + // Models + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/models/InputFile.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/models/InputFile.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/models/RealtimeModels.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/models/RealtimeModels.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/models/UploadProgress.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/models/UploadProgress.kt.twig', + ], + + // Serializers + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/serializers/DynamicLookupSerializer.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/serializers/DynamicLookupSerializer.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/serializers/StringCollectionSeriailizer.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/serializers/StringCollectionSeriailizer.kt.twig', + ], + + // Services + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/services/Realtime.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/services/Realtime.kt.twig', + ], + [ + 'scope' => 'service', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/services/{{service.name | caseUcfirst}}.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/services/Service.kt.twig', + ], + + // Web Interface + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/webInterface/ParsedUrl.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/webInterface/ParsedUrl.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/webInterface/UrlParser.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/webInterface/UrlParser.kt.twig', + ], + + + // Android Main + // Android Main root files + [ + 'scope' => 'default', + 'destination' => 'shared/src/androidMain/kotlin/{{ sdk.namespace | caseSlash }}/AllCertsTrustManager.kt', + 'template' => '/kmp/shared/src/androidMain/kotlin/io/package/AllCertsTrustManager.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/androidMain/kotlin/{{ sdk.namespace | caseSlash }}/Client.android.kt', + 'template' => '/kmp/shared/src/androidMain/kotlin/io/package/Client.android.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/androidMain/kotlin/{{ sdk.namespace | caseSlash }}/HttpClientConfig.kt', + 'template' => '/kmp/shared/src/androidMain/kotlin/io/package/HttpClientConfig.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/androidMain/kotlin/{{ sdk.namespace | caseSlash }}/KeepAliveService.kt', + 'template' => '/kmp/shared/src/androidMain/kotlin/io/package/KeepAliveService.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/androidMain/kotlin/{{ sdk.namespace | caseSlash }}/WebAuthComponent.android.kt', + 'template' => '/kmp/shared/src/androidMain/kotlin/io/package/WebAuthComponent.android.kt.twig', + ], + + // Cookies + [ + 'scope' => 'default', + 'destination' => 'shared/src/androidMain/kotlin/{{ sdk.namespace | caseSlash }}/cookies/SerializableCookie.kt', + 'template' => '/kmp/shared/src/androidMain/kotlin/io/package/cookies/SerializableCookie.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/androidMain/kotlin/{{ sdk.namespace | caseSlash }}/cookies/stores/DataStoreManager.kt', + 'template' => '/kmp/shared/src/androidMain/kotlin/io/package/cookies/stores/DataStoreManager.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/androidMain/kotlin/{{ sdk.namespace | caseSlash }}/cookies/stores/DataStoreCookieStorage.kt', + 'template' => '/kmp/shared/src/androidMain/kotlin/io/package/cookies/stores/DataStoreCookieStorage.kt.twig', + ], + + // Extensions + [ + 'scope' => 'default', + 'destination' => 'shared/src/androidMain/kotlin/{{ sdk.namespace | caseSlash }}/extensions/OAuth2Extensions.kt', + 'template' => '/kmp/shared/src/androidMain/kotlin/io/package/extensions/OAuth2Extensions.kt.twig', + ], + + // Models + [ + 'scope' => 'default', + 'destination' => 'shared/src/androidMain/kotlin/{{ sdk.namespace | caseSlash }}/models/InputFile.android.kt', + 'template' => '/kmp/shared/src/androidMain/kotlin/io/package/models/InputFile.android.kt.twig', + ], + + // Views + [ + 'scope' => 'default', + 'destination' => 'shared/src/androidMain/kotlin/{{ sdk.namespace | caseSlash }}/views/CallbackActivity.kt', + 'template' => '/kmp/shared/src/androidMain/kotlin/io/package/views/CallbackActivity.kt.twig', + ], + + // Web Interface + [ + 'scope' => 'default', + 'destination' => 'shared/src/androidMain/kotlin/{{ sdk.namespace | caseSlash }}/webInterface/AndroidParsedUrl.kt', + 'template' => '/kmp/shared/src/androidMain/kotlin/io/package/webInterface/AndroidParsedUrl.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/androidMain/kotlin/{{ sdk.namespace | caseSlash }}/webInterface/UrlParser.android.kt', + 'template' => '/kmp/shared/src/androidMain/kotlin/io/package/webInterface/UrlParser.android.kt.twig', + ], + + // iOS Main + // iOS Main root files + [ + 'scope' => 'default', + 'destination' => 'shared/src/iosMain/kotlin/{{ sdk.namespace | caseSlash }}/Client.ios.kt', + 'template' => '/kmp/shared/src/iosMain/kotlin/io/package/Client.ios.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/iosMain/kotlin/{{ sdk.namespace | caseSlash }}/HttpClientConfig.ios.kt', + 'template' => '/kmp/shared/src/iosMain/kotlin/io/package/HttpClientConfig.ios.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/iosMain/kotlin/{{ sdk.namespace | caseSlash }}/WebAuthComponent.ios.kt', + 'template' => '/kmp/shared/src/iosMain/kotlin/io/package/WebAuthComponent.ios.kt.twig', + ], + + // Cookies + [ + 'scope' => 'default', + 'destination' => 'shared/src/iosMain/kotlin/{{ sdk.namespace | caseSlash }}/cookies/IosCookieStorage.kt', + 'template' => '/kmp/shared/src/iosMain/kotlin/io/package/cookies/IosCookieStorage.kt.twig', + ], + + // Extensions + [ + 'scope' => 'default', + 'destination' => 'shared/src/iosMain/kotlin/{{ sdk.namespace | caseSlash }}/extensions/OAuth2Extensions.kt', + 'template' => '/kmp/shared/src/iosMain/kotlin/io/package/extensions/OAuth2Extensions.kt.twig', + ], + + // Models + [ + 'scope' => 'default', + 'destination' => 'shared/src/iosMain/kotlin/{{ sdk.namespace | caseSlash }}/models/InputFile.ios.kt', + 'template' => '/kmp/shared/src/iosMain/kotlin/io/package/models/InputFile.ios.kt.twig', + ], + + // Web Interface + [ + 'scope' => 'default', + 'destination' => 'shared/src/iosMain/kotlin/{{ sdk.namespace | caseSlash }}/webInterface/IosParsedUrl.kt', + 'template' => '/kmp/shared/src/iosMain/kotlin/io/package/webInterface/IosParsedUrl.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/iosMain/kotlin/{{ sdk.namespace | caseSlash }}/webInterface/UrlParser.ios.kt', + 'template' => '/kmp/shared/src/iosMain/kotlin/io/package/webInterface/UrlParser.ios.kt.twig', + ], + + + // JVM Main + // JVM Main root files + [ + 'scope' => 'default', + 'destination' => 'shared/src/jvmMain/kotlin/{{ sdk.namespace | caseSlash }}/AllCertsTrustManager.kt', + 'template' => '/kmp/shared/src/jvmMain/kotlin/io/package/AllCertsTrustManager.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/jvmMain/kotlin/{{ sdk.namespace | caseSlash }}/Client.jvm.kt', + 'template' => '/kmp/shared/src/jvmMain/kotlin/io/package/Client.jvm.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/jvmMain/kotlin/{{ sdk.namespace | caseSlash }}/HttpClient.kt', + 'template' => '/kmp/shared/src/jvmMain/kotlin/io/package/HttpClient.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/jvmMain/kotlin/{{ sdk.namespace | caseSlash }}/WebAuthComponent.jvm.kt', + 'template' => '/kmp/shared/src/jvmMain/kotlin/io/package/WebAuthComponent.jvm.kt.twig', + ], + + // Extensions + [ + 'scope' => 'default', + 'destination' => 'shared/src/jvmMain/kotlin/{{ sdk.namespace | caseSlash }}/extensions/OAuth2Extensions.kt', + 'template' => '/kmp/shared/src/jvmMain/kotlin/io/package/extensions/OAuth2Extensions.kt.twig', + ], + + // Models + [ + 'scope' => 'default', + 'destination' => 'shared/src/jvmMain/kotlin/{{ sdk.namespace | caseSlash }}/models/InputFile.jvm.kt', + 'template' => '/kmp/shared/src/jvmMain/kotlin/io/package/models/InputFile.jvm.kt.twig', + ], + + // Web Interface + [ + 'scope' => 'default', + 'destination' => 'shared/src/jvmMain/kotlin/{{ sdk.namespace | caseSlash }}/webInterface/JvmParsedUrl.kt', + 'template' => '/kmp/shared/src/jvmMain/kotlin/io/package/webInterface/JvmParsedUrl.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'shared/src/jvmMain/kotlin/{{ sdk.namespace | caseSlash }}/webInterface/UrlParser.jvm.kt', + 'template' => '/kmp/shared/src/jvmMain/kotlin/io/package/webInterface/UrlParser.jvm.kt.twig', + ], + + + // Android App + // Android App root files + [ + 'scope' => 'default', + 'destination' => 'androidApp/src/main/AndroidManifest.xml', + 'template' => '/kmp/androidApp/src/main/AndroidManifest.xml', + ], + +// Java files + [ + 'scope' => 'default', + 'destination' => 'androidApp/src/main/java/{{ sdk.namespace | caseSlash }}/android/MainActivity.kt', + 'template' => '/kmp/androidApp/src/main/java/io/package/android/MainActivity.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'androidApp/src/main/java/{{ sdk.namespace | caseSlash }}/android/services/MessagingService.kt', + 'template' => '/kmp/androidApp/src/main/java/io/package/android/services/MessagingService.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'androidApp/src/main/java/{{ sdk.namespace | caseSlash }}/android/ui/accounts/AccountsFragment.kt', + 'template' => '/kmp/androidApp/src/main/java/io/package/android/ui/accounts/AccountsFragment.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'androidApp/src/main/java/{{ sdk.namespace | caseSlash }}/android/ui/accounts/AccountsViewModel.kt', + 'template' => '/kmp/androidApp/src/main/java/io/package/android/ui/accounts/AccountsViewModel.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'androidApp/src/main/java/{{ sdk.namespace | caseSlash }}/android/utils/Client.kt', + 'template' => '/kmp/androidApp/src/main/java/io/package/android/utils/Client.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'androidApp/src/main/java/{{ sdk.namespace | caseSlash }}/android/utils/Event.kt', + 'template' => '/kmp/androidApp/src/main/java/io/package/android/utils/Event.kt.twig', + ], + +// Resource files + [ + 'scope' => 'copy', + 'destination' => 'androidApp/src/main/res/drawable/ic_launcher_background.xml', + 'template' => '/kmp/androidApp/src/main/res/drawable/ic_launcher_background.xml', + ], + [ + 'scope' => 'copy', + 'destination' => 'androidApp/src/main/res/drawable/ic_launcher_foreground.xml', + 'template' => '/kmp/androidApp/src/main/res/drawable/ic_launcher_foreground.xml', + ], + [ + 'scope' => 'copy', + 'destination' => 'androidApp/src/main/res/layout/activity_main.xml', + 'template' => '/kmp/androidApp/src/main/res/layout/activity_main.xml', + ], + [ + 'scope' => 'copy', + 'destination' => 'androidApp/src/main/res/layout/fragment_account.xml', + 'template' => '/kmp/androidApp/src/main/res/layout/fragment_account.xml', + ], + [ + 'scope' => 'copy', + 'destination' => 'androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml', + 'template' => '/kmp/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml', + ], + [ + 'scope' => 'copy', + 'destination' => 'androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml', + 'template' => '/kmp/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml', + ], + [ + 'scope' => 'copy', + 'destination' => 'androidApp/src/main/res/values/colors.xml', + 'template' => '/kmp/androidApp/src/main/res/values/colors.xml', + ], + [ + 'scope' => 'copy', + 'destination' => 'androidApp/src/main/res/values/strings.xml', + 'template' => '/kmp/androidApp/src/main/res/values/strings.xml', + ], + [ + 'scope' => 'copy', + 'destination' => 'androidApp/src/main/res/values/themes.xml', + 'template' => '/kmp/androidApp/src/main/res/values/themes.xml', + ], + + + // Models, Services, and other common components + [ + 'scope' => 'service', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/services/{{service.name | caseUcfirst}}.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/services/Service.kt.twig', + ], + [ + 'scope' => 'definition', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/models/{{ definition.name | caseUcfirst }}.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/models/Model.kt.twig', + ], + [ + 'scope' => 'enum', + 'destination' => 'shared/src/commonMain/kotlin/{{ sdk.namespace | caseSlash }}/enums/{{ enum.name | caseUcfirst }}.kt', + 'template' => '/kmp/shared/src/commonMain/kotlin/io/package/enums/Enum.kt.twig', + ], + ]; + } +} diff --git a/src/SDK/Language/Kotlin.php b/src/SDK/Language/Kotlin.php index 764949528..7cf53f8ff 100644 --- a/src/SDK/Language/Kotlin.php +++ b/src/SDK/Language/Kotlin.php @@ -426,8 +426,8 @@ public function getFiles(): array public function getFilters(): array { return [ - new TwigFilter('returnType', function (array $method, array $spec, string $namespace, string $generic = 'T') { - return $this->getReturnType($method, $spec, $namespace, $generic); + new TwigFilter('returnType', function (array $method, array $spec, string $namespace, string $generic = 'T', bool $withGeneric = true) { + return $this->getReturnType($method, $spec, $namespace, $generic, $withGeneric); }), new TwigFilter('modelType', function (array $property, array $spec, string $generic = 'T') { return $this->getModelType($property, $spec, $generic); @@ -447,7 +447,7 @@ public function getFilters(): array ]; } - protected function getReturnType(array $method, array $spec, string $namespace, string $generic = 'T'): string + protected function getReturnType(array $method, array $spec, string $namespace, string $generic = 'T', bool $withGeneric = true): string { if ($method['type'] === 'webAuth') { return 'String'; @@ -466,7 +466,7 @@ protected function getReturnType(array $method, array $spec, string $namespace, $ret = $this->toPascalCase($method['responseModel']); - if ($this->hasGenericType($method['responseModel'], $spec)) { + if ($this->hasGenericType($method['responseModel'], $spec) && $withGeneric) { $ret .= '<' . $generic . '>'; } diff --git a/templates/android/library/src/main/java/io/package/WebAuthComponent.kt.twig b/templates/android/library/src/main/java/io/package/WebAuthComponent.kt.twig index a07509ac2..f55fa7123 100644 --- a/templates/android/library/src/main/java/io/package/WebAuthComponent.kt.twig +++ b/templates/android/library/src/main/java/io/package/WebAuthComponent.kt.twig @@ -6,7 +6,6 @@ import androidx.activity.ComponentActivity import androidx.browser.customtabs.CustomTabsIntent import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner -import {{ sdk.namespace | caseDot }}.services.KeepAliveService import kotlinx.coroutines.delay import kotlin.collections.component1 import kotlin.collections.component2 @@ -18,34 +17,31 @@ import kotlin.collections.set * Used to authenticate with external OAuth2 providers. Launches browser windows and handles * suspension until the user completes the process or otherwise returns to the app. */ -internal class WebAuthComponent { +actual class WebAuthComponent(private val activity: ComponentActivity) { - companion object : DefaultLifecycleObserver { + internal companion object : DefaultLifecycleObserver { private var suspended = false - private val callbacks = mutableMapOf) -> Unit)?)>() + private val callbacks = mutableMapOf) -> Unit)?>() override fun onResume(owner: LifecycleOwner) { + // When the activity resumes, end the suspension so that the caller can continue. suspended = false } /** - * Authenticate Session with OAuth2 + * Authenticate session using OAuth2. * - * Launches a chrome custom tab from the given activity and directs to the given url, - * suspending until the user returns to the app, at which point the given [onComplete] callback - * will run, passing the callback url from the intent used to launch the [CallbackActivity], - * or an [IllegalStateException] in the case the user closed the window or returned to the - * app without passing through the [CallbackActivity]. + * Launches a Chrome Custom Tab from the provided activity to open the given URL. + * Once the user returns to the app (resuming the activity), the provided callback is invoked. * - * - * @param activity The activity to launch the browser from and observe the lifecycle of - * @param url The url to launch - * @param callbackUrlScheme The callback url scheme used to key the given callback - * @param onComplete The callback to run when a result (success or failure) is received + * @param activity The activity used to launch the browser and observe lifecycle events. + * @param url The URL to open. + * @param callbackUrlScheme The URL scheme to match for the authentication callback. + * @param onComplete The callback to run with the authentication result. */ - suspend fun authenticate( + internal suspend fun authenticate( activity: ComponentActivity, - url: Uri, + url: String, callbackUrlScheme: String, onComplete: ((Result) -> Unit)? ) { @@ -56,13 +52,14 @@ internal class WebAuthComponent { intent.intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) intent.intent.putExtra("android.support.customtabs.extra.KEEP_ALIVE", keepAliveIntent) - intent.launchUrl(activity, url) + intent.launchUrl(activity, Uri.parse(url)) + // Add this as a lifecycle observer so that we know when the user returns to the app. activity.runOnUiThread { activity.lifecycle.addObserver(this) } - // Need to dirty poll block so execution doesn't continue at the callsite of this function + // Poll until the authentication has been resumed. suspended = true while (suspended) { delay(200) @@ -71,30 +68,49 @@ internal class WebAuthComponent { } /** - * Trigger a web auth callback + * Invoke the callback for the provided scheme. * - * Attempts to find a callback for the given [scheme] and if found, invokes it, passing the - * given [url]. Calling this method stops auth suspension, so any calls to [authenticate] - * will continue execution from their suspension points immediately after this method - * is called. + * This method ends the suspension, allowing any waiting coroutines to resume. * - * @param scheme The scheme to match to a callback's key - * @param url The url received through intent data from the [CallbackActivity] + * @param scheme The callback scheme key. + * @param url The URL provided in the callback. */ - fun onCallback(scheme: String, url: Uri) { - callbacks.remove(scheme)?.invoke( - Result.success(url.toString()) - ) + internal fun onCallback(scheme: String, url: String) { + callbacks.remove(scheme)?.invoke(Result.success(url)) suspended = false } private fun cleanUp() { - callbacks.forEach { (_, danglingResultCallback) -> - danglingResultCallback?.invoke( - Result.failure(IllegalStateException("User cancelled login")) - ) + callbacks.forEach { (_, callback) -> + callback?.invoke(Result.failure(IllegalStateException("User cancelled login"))) } callbacks.clear() } } + + /** + * Suspend function to perform authentication. + * + * @param url The URL to launch. + * @param callbackUrlScheme The callback URL scheme. + * @param onComplete The callback to invoke with the result. + */ + @Throws(Throwable::class) + internal actual suspend fun authenticate( + url: String, + callbackUrlScheme: String, + onComplete: ((Result) -> Unit)? + ) { + authenticate(activity, url, callbackUrlScheme, onComplete) + } + + /** + * Called when the external callback URL is received. + * + * @param scheme The scheme received. + * @param url The full URL for the callback. + */ + internal actual fun onCallback(scheme: String, url: String) { + WebAuthComponent.onCallback(scheme, url) + } } \ No newline at end of file diff --git a/templates/cli/scoop/appwrite.json.twig b/templates/cli/scoop/appwrite.json.twig index c4ccbaef8..6a007b421 100644 --- a/templates/cli/scoop/appwrite.json.twig +++ b/templates/cli/scoop/appwrite.json.twig @@ -1,30 +1,30 @@ -{ - "$schema": "https://raw.githubusercontent.com/ScoopInstaller/Scoop/master/schema.json", - "version": "{{ sdk.version }}", - "description": "The Appwrite CLI is a command-line application that allows you to interact with Appwrite and perform server-side tasks using your terminal.", - "homepage": "https://github.com/{{ sdk.gitUserName|url_encode }}/{{ sdk.gitRepoName|url_encode }}", - "license": "BSD-3-Clause", - "architecture": { - "64bit": { - "url": "https://github.com/{{ sdk.gitUserName }}/{{ sdk.gitRepoName | caseDash }}/releases/download/{{ sdk.version }}/{{ language.params.executableName }}-cli-win-x64.exe", - "bin": [ - [ - "{{ language.params.executableName }}-cli-win-x64.exe", - "{{ language.params.executableName|caseLower }}" - ] - ] - }, - "arm64": { - "url": "https://github.com/{{ sdk.gitUserName }}/{{ sdk.gitRepoName | caseDash }}/releases/download/{{ sdk.version }}/{{ language.params.executableName }}-cli-win-arm64.exe", - "bin": [ - [ - "{{ language.params.executableName }}-cli-win-arm64.exe", - "{{ language.params.executableName|caseLower }}" - ] - ] - } - }, - "checkver": { - "github": "https://github.com/{{ sdk.gitUserName|url_encode }}/{{ sdk.gitRepoName|url_encode }}" - } +{ + "$schema": "https://raw.githubusercontent.com/ScoopInstaller/Scoop/master/schema.json", + "version": "{{ sdk.version }}", + "description": "The Appwrite CLI is a command-line application that allows you to interact with Appwrite and perform server-side tasks using your terminal.", + "homepage": "https://github.com/{{ sdk.gitUserName|url_encode }}/{{ sdk.gitRepoName|url_encode }}", + "license": "BSD-3-Clause", + "architecture": { + "64bit": { + "url": "https://github.com/{{ sdk.gitUserName }}/{{ sdk.gitRepoName | caseDash }}/releases/download/{{ sdk.version }}/{{ language.params.executableName }}-cli-win-x64.exe", + "bin": [ + [ + "{{ language.params.executableName }}-cli-win-x64.exe", + "{{ language.params.executableName|caseLower }}" + ] + ] + }, + "arm64": { + "url": "https://github.com/{{ sdk.gitUserName }}/{{ sdk.gitRepoName | caseDash }}/releases/download/{{ sdk.version }}/{{ language.params.executableName }}-cli-win-arm64.exe", + "bin": [ + [ + "{{ language.params.executableName }}-cli-win-arm64.exe", + "{{ language.params.executableName|caseLower }}" + ] + ] + } + }, + "checkver": { + "github": "https://github.com/{{ sdk.gitUserName|url_encode }}/{{ sdk.gitRepoName|url_encode }}" + } } \ No newline at end of file diff --git a/templates/dotnet/Package/Extensions/Extensions.cs.twig b/templates/dotnet/Package/Extensions/Extensions.cs.twig index 10b2b5035..b2b81cd51 100644 --- a/templates/dotnet/Package/Extensions/Extensions.cs.twig +++ b/templates/dotnet/Package/Extensions/Extensions.cs.twig @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using System; using System.Collections; using System.Collections.Generic; diff --git a/templates/kmp/.github/workflows/autoclose.yml b/templates/kmp/.github/workflows/autoclose.yml new file mode 100644 index 000000000..3e2b3cbce --- /dev/null +++ b/templates/kmp/.github/workflows/autoclose.yml @@ -0,0 +1,11 @@ +name: Auto-close External Pull Requests + +on: + pull_request_target: + types: [opened, reopened] + +jobs: + auto_close: + uses: appwrite/.github/.github/workflows/autoclose.yml@main + secrets: + GH_AUTO_CLOSE_PR_TOKEN: ${{ secrets.GH_AUTO_CLOSE_PR_TOKEN }} diff --git a/templates/kmp/.github/workflows/publish.yml b/templates/kmp/.github/workflows/publish.yml new file mode 100644 index 000000000..de6a6b8d3 --- /dev/null +++ b/templates/kmp/.github/workflows/publish.yml @@ -0,0 +1,52 @@ +name: Publish to Maven Central + +on: + release: + types: [released] + +jobs: + publish: + name: Release build and publish + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: 17 + distribution: 'temurin' + + - name: Prepare environment + env: + GPG_KEY_CONTENTS: ${{ secrets.GPG_KEY_CONTENTS }} + SIGNING_SECRET_KEY_RING_FILE: ${{ secrets.SIGNING_SECRET_KEY_RING_FILE }} + run: | + git fetch --unshallow + sudo bash -c "echo '$GPG_KEY_CONTENTS' | base64 -d > '$SIGNING_SECRET_KEY_RING_FILE'" + chmod +x ./gradlew + + - name: Build Release Artifacts + run: ./gradlew assemble + + - name: Generate Documentation + run: ./gradlew dokkaHtml + + - name: Publish to Maven Central + run: | + if ${{ contains(github.event.release.tag_name, '-rc') }}; then + echo "Publishing Snapshot Version ${{ github.event.release.tag_name}}" + ./gradlew publishAllPublicationsToSonatypeRepository + else + echo "Publishing Release Version ${{ github.event.release.tag_name}}" + ./gradlew publishAllPublicationsToSonatypeRepository closeAndReleaseSonatypeStagingRepository + fi + env: + OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} + OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} + SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} + SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} + SIGNING_SECRET_KEY_RING_FILE: ${{ secrets.SIGNING_SECRET_KEY_RING_FILE }} + SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }} + SDK_VERSION: ${{ github.event.release.tag_name }} diff --git a/templates/kmp/.gitignore b/templates/kmp/.gitignore new file mode 100644 index 000000000..f7a9fde78 --- /dev/null +++ b/templates/kmp/.gitignore @@ -0,0 +1,10 @@ +*.iml +.gradle +.idea +.DS_Store +build +captures +.externalNativeBuild +.cxx +local.properties +xcuserdata diff --git a/templates/kmp/CHANGELOG.md.twig b/templates/kmp/CHANGELOG.md.twig new file mode 100644 index 000000000..e87fcf8f2 --- /dev/null +++ b/templates/kmp/CHANGELOG.md.twig @@ -0,0 +1 @@ +{{sdk.changelog}} diff --git a/templates/kmp/LICENSE.md.twig b/templates/kmp/LICENSE.md.twig new file mode 100644 index 000000000..ce6435c38 --- /dev/null +++ b/templates/kmp/LICENSE.md.twig @@ -0,0 +1 @@ +{{sdk.licenseContent | raw}} diff --git a/templates/kmp/README.md.twig b/templates/kmp/README.md.twig new file mode 100644 index 000000000..9e3ddc69c --- /dev/null +++ b/templates/kmp/README.md.twig @@ -0,0 +1,76 @@ +# {{ spec.title }} {{sdk.name}} SDK + +![Maven Central](https://img.shields.io/maven-central/v/{{ sdk.namespace | caseDot }}/{{ sdk.gitRepoName | caseDash }}.svg?color=green&style=flat-square) +![License](https://img.shields.io/github/license/{{ sdk.gitUserName|url_encode }}/{{ sdk.gitRepoName|url_encode }}.svg?style=flat-square) +![Version](https://img.shields.io/badge/api%20version-{{ spec.version|url_encode }}-blue.svg?style=flat-square) +[![Build Status](https://img.shields.io/travis/com/appwrite/sdk-generator?style=flat-square)](https://travis-ci.com/appwrite/sdk-generator) +{% if sdk.twitterHandle %} +[![Twitter Account](https://img.shields.io/twitter/follow/{{ sdk.twitterHandle }}?color=00acee&label=twitter&style=flat-square)](https://twitter.com/{{ sdk.twitterHandle }}) +{% endif %} +{% if sdk.discordChannel %} +[![Discord](https://img.shields.io/discord/{{ sdk.discordChannel }}?label=discord&style=flat-square)]({{ sdk.discordUrl }}) +{% endif %} +{% if sdk.warning %} + +{{ sdk.warning|raw }} +{% endif %} + +{{ sdk.description }} + +{% if sdk.logo %} +![{{ spec.title }}]({{ sdk.logo }}) +{% endif %} + +## Installation + +### Gradle + +Appwrite's Android SDK is hosted on Maven Central. In order to fetch the Appwrite SDK, add this to your root level `build.gradle(.kts)` file: + +```groovy +repositories { + mavenCentral() +} +``` + +If you would like to fetch our SNAPSHOT releases, you need to add the SNAPSHOT maven repository to your `build.gradle(.kts)`: + +```groovy +repositories { + maven { + url "https://s01.oss.sonatype.org/content/repositories/snapshots/" + } +} +``` + +Next, add the dependency to your project's `build.gradle(.kts)` file: + +```groovy +implementation("{{ sdk.namespace | caseDot }}:{{ sdk.gitRepoName | caseDash }}:{{ sdk.version }}") +``` + +### Maven +Add this to your project's `pom.xml` file: + +```xml + + + {{ sdk.namespace | caseDot }} + {{ sdk.gitRepoName | caseDash }} + {{sdk.version}} + + +``` + +{% if sdk.gettingStarted %} + +{{ sdk.gettingStarted|raw }} +{% endif %} + +## Contribution + +This library is auto-generated by Appwrite custom [SDK Generator](https://github.com/appwrite/sdk-generator). To learn more about how you can help us improve this SDK, please check the [contribution guide](https://github.com/appwrite/sdk-generator/blob/master/CONTRIBUTING.md) before sending a pull-request. + +## License + +Please see the [{{spec.licenseName}} license]({{spec.licenseURL}}) file for more information. diff --git a/templates/kmp/androidApp/build.gradle.kts b/templates/kmp/androidApp/build.gradle.kts new file mode 100644 index 000000000..3d6abb877 --- /dev/null +++ b/templates/kmp/androidApp/build.gradle.kts @@ -0,0 +1,47 @@ +plugins { + alias(libs.plugins.androidApplication) + alias(libs.plugins.kotlinAndroid) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.kotlinx.serialization) +} + +android { + namespace = "io.appwrite.android" + compileSdk = 35 + defaultConfig { + applicationId = "io.appwrite.android" + minSdk = 21 + targetSdk = 35 + versionCode = 1 + versionName = "1.0" + } + buildFeatures { + compose = true + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + buildTypes { + getByName("release") { + isMinifyEnabled = false + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation(projects.shared) + implementation(libs.compose.ui) + implementation(libs.compose.ui.tooling.preview) + implementation(libs.compose.material3) + implementation(libs.androidx.activity.compose) + debugImplementation(libs.compose.ui.tooling) +} diff --git a/templates/kmp/androidApp/src/main/AndroidManifest.xml b/templates/kmp/androidApp/src/main/AndroidManifest.xml new file mode 100644 index 000000000..2559fdb2d --- /dev/null +++ b/templates/kmp/androidApp/src/main/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/kmp/androidApp/src/main/java/io/package/android/MainActivity.kt.twig b/templates/kmp/androidApp/src/main/java/io/package/android/MainActivity.kt.twig new file mode 100644 index 000000000..1dc2a057c --- /dev/null +++ b/templates/kmp/androidApp/src/main/java/io/package/android/MainActivity.kt.twig @@ -0,0 +1,23 @@ +package {{ sdk.namespace | caseDot }}.android + +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import androidx.fragment.app.add +import androidx.fragment.app.commit +import {{ sdk.namespace | caseDot }}.android.ui.accounts.AccountsFragment +import {{ sdk.namespace | caseDot }}.android.utils.Client + +class MainActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + Client.create(applicationContext) + + if (savedInstanceState == null) { + supportFragmentManager.commit { + setReorderingAllowed(true) + add(R.id.fragment_container_view) + } + } + } +} diff --git a/templates/kmp/androidApp/src/main/java/io/package/android/services/MessagingService.kt.twig b/templates/kmp/androidApp/src/main/java/io/package/android/services/MessagingService.kt.twig new file mode 100644 index 000000000..c4248f96a --- /dev/null +++ b/templates/kmp/androidApp/src/main/java/io/package/android/services/MessagingService.kt.twig @@ -0,0 +1,37 @@ +package {{ sdk.namespace | caseDot }}.android.services + +import com.google.firebase.messaging.FirebaseMessagingService +import {{ sdk.namespace | caseDot }}.ID +import {{ sdk.namespace | caseDot }}.services.Account +import kotlinx.coroutines.runBlocking + +class MessagingService : FirebaseMessagingService() { + + companion object { + var account: Account? = null + } + + override fun onNewToken(token: String) { + super.onNewToken(token) + + val prefs = getSharedPreferences("example", MODE_PRIVATE) + + prefs.edit().putString("fcmToken", token).apply() + + if (account == null) { + return + } + + val targetId = prefs.getString("targetId", null) + + runBlocking { + if (targetId == null) { + val target = account!!.createPushTarget(ID.unique(), token) + + prefs.edit().putString("targetId", target.id).apply() + } else { + account!!.updatePushTarget(targetId, token) + } + } + } +} diff --git a/templates/kmp/androidApp/src/main/java/io/package/android/ui/accounts/AccountsFragment.kt.twig b/templates/kmp/androidApp/src/main/java/io/package/android/ui/accounts/AccountsFragment.kt.twig new file mode 100644 index 000000000..ab08a813d --- /dev/null +++ b/templates/kmp/androidApp/src/main/java/io/package/android/ui/accounts/AccountsFragment.kt.twig @@ -0,0 +1,85 @@ +package {{ sdk.namespace | caseDot }}.android.ui.accounts + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.databinding.DataBindingUtil +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import {{ sdk.namespace | caseDot }}.android.R +import {{ sdk.namespace | caseDot }}.android.databinding.FragmentAccountBinding + + +class AccountsFragment : Fragment() { + + private lateinit var binding: FragmentAccountBinding + private val viewModel: AccountsViewModel by viewModels() + + override fun onCreateView( + inflater: LayoutInflater , + container: ViewGroup? , + savedInstanceState: Bundle? + ): View { + binding = DataBindingUtil.inflate( + inflater, + R.layout.fragment_account, + container, + false + ) + binding.lifecycleOwner = viewLifecycleOwner + + binding.login.setOnClickListener{ + viewModel.onLogin( + binding.email.text.toString(), + binding.password.text.toString(), + context + ?.getSharedPreferences("example", Context.MODE_PRIVATE) + ?.getString("fcmToken", null) ?: "" + ) + } + binding.signup.setOnClickListener{ + viewModel.onSignup( + binding.email.text.toString(), + binding.password.text.toString(), + binding.name.text.toString() + ) + } + binding.getUser.setOnClickListener{ + viewModel.getUser() + } + binding.oAuth.setOnClickListener{ + viewModel.oAuthLogin(activity as ComponentActivity) + } + binding.logout.setOnClickListener{ + viewModel.logout() + } + + viewModel.error.observe(viewLifecycleOwner) { event -> + event?.getContentIfNotHandled()?.let { + Toast.makeText(requireContext(), it.message, Toast.LENGTH_SHORT).show() + } + } + + viewModel.response.observe(viewLifecycleOwner) { event -> + event?.getContentIfNotHandled()?.let { + binding.responseTV.setText(it) + } + } + + viewModel.target.observe(viewLifecycleOwner) { event -> + event?.getContentIfNotHandled()?.let { + context + ?.getSharedPreferences("example", Context.MODE_PRIVATE) + ?.edit() + ?.putString("targetId", it.id) + ?.apply() + } + } + + return binding.root + } +} diff --git a/templates/kmp/androidApp/src/main/java/io/package/android/ui/accounts/AccountsViewModel.kt.twig b/templates/kmp/androidApp/src/main/java/io/package/android/ui/accounts/AccountsViewModel.kt.twig new file mode 100644 index 000000000..b841b6636 --- /dev/null +++ b/templates/kmp/androidApp/src/main/java/io/package/android/ui/accounts/AccountsViewModel.kt.twig @@ -0,0 +1,116 @@ +package {{ sdk.namespace | caseDot }}.android.ui.accounts + +import androidx.activity.ComponentActivity +import androidx.lifecycle.* +import {{ sdk.namespace | caseDot }}.ID +import {{ sdk.namespace | caseDot }}.android.services.MessagingService +import {{ sdk.namespace | caseDot }}.android.utils.Client.client +import {{ sdk.namespace | caseDot }}.android.utils.Event +import {{ sdk.namespace | caseDot }}.enums.OAuthProvider +import {{ sdk.namespace | caseDot }}.exceptions.{{ spec.title | caseUcfirst }}Exception +import {{ sdk.namespace | caseDot }}.extensions.toJson +import {{ sdk.namespace | caseDot }}.models.Target +import {{ sdk.namespace | caseDot }}.services.Account +import kotlinx.coroutines.launch + +class AccountsViewModel : ViewModel() { + + private val _error = MutableLiveData>().apply { value = null } + val error: LiveData> = _error + + private val _response = MutableLiveData>().apply { value = null } + val response: LiveData> = _response + + private val _target = MutableLiveData>().apply { value = null } + val target: LiveData> = _target + + private val account by lazy { + val account = Account(client) + + MessagingService.account = account + + account + } + + fun onLogin( + email: String, + password: String, + token: String?, + ) { + viewModelScope.launch { + try { + val session = account.createEmailPasswordSession( + email, + password + ) + + if (token != null) { + val target = account.createPushTarget(ID.unique(), token) + + _target.postValue(Event(target)) + } + + _response.postValue(Event(session.toJson())) + } catch (e: AppwriteException) { + _error.postValue(Event(e)) + } + } + + } + + fun onSignup(email: String, password: String, name: String) { + viewModelScope.launch { + try { + val user = account.create( + ID.unique(), + email, + password, + name + ) + _response.postValue(Event(user.toJson())) + } catch (e: AppwriteException) { + _error.postValue(Event(e)) + } + } + + } + + fun oAuthLogin(activity: ComponentActivity) { + viewModelScope.launch { + try { + account.createOAuth2Session( + activity, + OAuthProvider.FACEBOOK, + "appwrite-callback-6070749e6acd4://demo.appwrite.io/auth/oauth2/success", + "appwrite-callback-6070749e6acd4://demo.appwrite.io/auth/oauth2/failure" + ) + } catch (e: Exception) { + _error.postValue(Event(e)) + } catch (e: AppwriteException) { + _error.postValue(Event(e)) + } + } + } + + fun getUser() { + viewModelScope.launch { + try { + val user = account.get() + _response.postValue(Event(user.toJson())) + } catch (e: AppwriteException) { + _error.postValue(Event(e)) + } + } + } + + fun logout() { + viewModelScope.launch { + try { + val result = account.deleteSession("current") + _response.postValue(Event(result.toJson())) + } catch (e: AppwriteException) { + _error.postValue(Event(e)) + } + } + } +} diff --git a/templates/kmp/androidApp/src/main/java/io/package/android/utils/Client.kt.twig b/templates/kmp/androidApp/src/main/java/io/package/android/utils/Client.kt.twig new file mode 100644 index 000000000..f8ebf4180 --- /dev/null +++ b/templates/kmp/androidApp/src/main/java/io/package/android/utils/Client.kt.twig @@ -0,0 +1,15 @@ +package {{ sdk.namespace | caseDot }}.android.utils + +import android.content.Context +import io.appwrite.Client + +object Client { + lateinit var client : Client + + fun create(context: Context) { + client = Client(context) + .setEndpoint("http://192.168.4.24/v1") + .setProject("65a8e2b4632c04b1f5da") + .setSelfSigned(true) + } +} diff --git a/templates/kmp/androidApp/src/main/java/io/package/android/utils/Event.kt.twig b/templates/kmp/androidApp/src/main/java/io/package/android/utils/Event.kt.twig new file mode 100644 index 000000000..a43a58886 --- /dev/null +++ b/templates/kmp/androidApp/src/main/java/io/package/android/utils/Event.kt.twig @@ -0,0 +1,27 @@ +package {{ sdk.namespace | caseDot }}.android.utils + +/** + * Used as a wrapper for data that is exposed via a LiveData that represents an event. + */ +open class Event(private val content: T) { + + var hasBeenHandled = false + private set // Allow external read but not write + + /** + * Returns the content and prevents its use again. + */ + fun getContentIfNotHandled(): T? { + return if (hasBeenHandled) { + null + } else { + hasBeenHandled = true + content + } + } + + /** + * Returns the content, even if it's already been handled. + */ + fun peekContent(): T = content +} diff --git a/templates/kmp/androidApp/src/main/res/drawable/ic_launcher_background.xml b/templates/kmp/androidApp/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000..07d5da9cb --- /dev/null +++ b/templates/kmp/androidApp/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/kmp/androidApp/src/main/res/drawable/ic_launcher_foreground.xml b/templates/kmp/androidApp/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 000000000..7706ab9e6 --- /dev/null +++ b/templates/kmp/androidApp/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + diff --git a/templates/kmp/androidApp/src/main/res/layout/activity_main.xml b/templates/kmp/androidApp/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..00cef8106 --- /dev/null +++ b/templates/kmp/androidApp/src/main/res/layout/activity_main.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/templates/kmp/androidApp/src/main/res/layout/fragment_account.xml b/templates/kmp/androidApp/src/main/res/layout/fragment_account.xml new file mode 100644 index 000000000..4173be134 --- /dev/null +++ b/templates/kmp/androidApp/src/main/res/layout/fragment_account.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + +