From fc9b57e93742292a545cc44be98cf9b602c776cb Mon Sep 17 00:00:00 2001 From: ildysilva Date: Sat, 6 Jun 2026 21:49:35 +0100 Subject: [PATCH 1/8] chore: expand gitignore for generated files --- .gitignore | 44 ++++++++++++++++++ packages/nhost_flutter_auth/.metadata | 10 ----- packages/nhost_flutter_auth/example/.metadata | 45 ------------------- packages/nhost_flutter_graphql/.metadata | 10 ----- .../nhost_flutter_graphql/example/.metadata | 45 ------------------- 5 files changed, 44 insertions(+), 110 deletions(-) delete mode 100644 packages/nhost_flutter_auth/.metadata delete mode 100644 packages/nhost_flutter_auth/example/.metadata delete mode 100644 packages/nhost_flutter_graphql/.metadata delete mode 100644 packages/nhost_flutter_graphql/example/.metadata diff --git a/.gitignore b/.gitignore index 18bd7c47..c356c480 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,67 @@ .DS_Store .atom/ .idea/ +.vscode/ .packages .pub/ .dart_tool/ pubspec.lock +pubspec_overrides.local.yaml flutter_export_environment.sh coverage +coverage/ *.iml +*.ipr +*.iws .flutter-plugins .flutter-plugins-dependencies +devtools_options.yaml .project .classpath .settings .last_build_id +.metadata + +# Local planning notes +backlog.md +task-list.md +todo.md +todos.md + +# Dart / Flutter generated docs and build output +doc/api/ +build/ +.dart_tool/ + +# Flutter example generated platform files +packages/**/example/.metadata +packages/**/example/.flutter-plugins +packages/**/example/.flutter-plugins-dependencies +packages/**/example/devtools_options.yaml +packages/**/example/build/ +packages/**/example/ios/Flutter/.last_build_id +packages/**/example/ios/Flutter/Generated.xcconfig +packages/**/example/ios/Flutter/flutter_export_environment.sh +packages/**/example/ios/Flutter/ephemeral/ +packages/**/example/ios/Flutter/Flutter.framework +packages/**/example/ios/Flutter/Flutter.podspec +packages/**/example/ios/Flutter/App.framework +packages/**/example/ios/Flutter/flutter_assets/ +packages/**/example/ios/Pods/ +packages/**/example/ios/.symlinks/ +packages/**/example/ios/Runner/GeneratedPluginRegistrant.* +packages/**/example/ios/Podfile.lock +packages/**/example/android/.gradle/ +packages/**/example/android/local.properties +packages/**/example/android/app/debug/ +packages/**/example/android/app/profile/ +packages/**/example/android/app/release/ +packages/**/example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java +packages/**/example/android/app/src/main/kotlin/io/flutter/plugins/GeneratedPluginRegistrant.kt # Misc .env.local diff --git a/packages/nhost_flutter_auth/.metadata b/packages/nhost_flutter_auth/.metadata deleted file mode 100644 index 933d7a89..00000000 --- a/packages/nhost_flutter_auth/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: b7d4806243a4e906bf061f79a0e314ba28111aa6 - channel: dev - -project_type: package diff --git a/packages/nhost_flutter_auth/example/.metadata b/packages/nhost_flutter_auth/example/.metadata deleted file mode 100644 index 3fdf5328..00000000 --- a/packages/nhost_flutter_auth/example/.metadata +++ /dev/null @@ -1,45 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled. - -version: - revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - channel: stable - -project_type: app - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - - platform: android - create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - - platform: ios - create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - - platform: linux - create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - - platform: macos - create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - - platform: web - create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - - platform: windows - create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/nhost_flutter_graphql/.metadata b/packages/nhost_flutter_graphql/.metadata deleted file mode 100644 index 933d7a89..00000000 --- a/packages/nhost_flutter_graphql/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: b7d4806243a4e906bf061f79a0e314ba28111aa6 - channel: dev - -project_type: package diff --git a/packages/nhost_flutter_graphql/example/.metadata b/packages/nhost_flutter_graphql/example/.metadata deleted file mode 100644 index 9e70ade3..00000000 --- a/packages/nhost_flutter_graphql/example/.metadata +++ /dev/null @@ -1,45 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled. - -version: - revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 - channel: stable - -project_type: app - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 - base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 - - platform: android - create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 - base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 - - platform: ios - create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 - base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 - - platform: linux - create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 - base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 - - platform: macos - create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 - base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 - - platform: web - create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 - base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 - - platform: windows - create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 - base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' From 04c2f09b0a964fab9d4a5fbecb65ad1b9c6a9b21 Mon Sep 17 00:00:00 2001 From: ildysilva Date: Sat, 6 Jun 2026 22:11:13 +0100 Subject: [PATCH 2/8] docs: fix nhost_dart readme sample --- packages/nhost_dart/README.md | 55 +++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/packages/nhost_dart/README.md b/packages/nhost_dart/README.md index ce620383..2a4fe8d0 100644 --- a/packages/nhost_dart/README.md +++ b/packages/nhost_dart/README.md @@ -23,37 +23,42 @@ import 'package:nhost_dart/nhost_dart.dart'; void main() async { final nhost = NhostClient( subdomain: Subdomain( - region: 'eu-central-1', - subdomain: 'backend-5e69d1d7', - ), - ); - - - // for self host or local host you may use ServiceUrls - /* - final nhost = NhostClient( - serviceUrls: ServiceUrls( - authUrl: '', - storageUrl: '', - functionsUrl: '', - graphqlUrl: '', - ), - ); - */ + region: 'eu-central-1', + subdomain: 'backend-5e69d1d7', + ), + ); + + // For self-hosted or local projects, use ServiceUrls instead. + /* + final nhost = NhostClient( + serviceUrls: ServiceUrls( + authUrl: 'http://localhost:1337/v1/auth', + storageUrl: 'http://localhost:1337/v1/storage', + functionsUrl: 'http://localhost:1337/v1/functions', + graphqlUrl: 'http://localhost:1337/v1/graphql', + ), + ); + */ // User registration - await nhost.auth.register(email: 'new-user@gmail.com', password: 'xxxxx'); + final authResponse = await nhost.auth.signUp( + email: 'new-user@gmail.com', + password: 'password-1', + ); // Upload a file - final currentUser = nhost.auth.currentUser; + final currentUser = authResponse.user ?? nhost.auth.currentUser; await nhost.storage.uploadBytes( - filePath: '/users/${currentUser.id}/image.jpg', - bytes: [/* ... */], - contentType: 'image/jpeg', - ), + fileName: '/users/${currentUser!.id}/image.jpg', + fileContents: [/* ... */], + mimeType: 'image/jpeg', + ); // Log out - await nhost.auth.logout(); + await nhost.auth.signOut(); + + // Release resources + nhost.close(); } ``` @@ -63,7 +68,7 @@ void main() async { ```yaml dependencies: - nhost_dart: ^1.0.1 + nhost_dart: ^2.2.0 ``` ## 🔥 More Dart & Flutter packages from Nhost From b82b6c967b1ffd319744474411baecb0a7dc4e3f Mon Sep 17 00:00:00 2001 From: ildysilva Date: Sat, 6 Jun 2026 22:24:02 +0100 Subject: [PATCH 3/8] fix: redact auth tokens in string output --- .../nhost_auth_dart/lib/src/auth_client.dart | 5 +- .../test/auth_client_test.dart | 46 +++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 packages/nhost_auth_dart/test/auth_client_test.dart diff --git a/packages/nhost_auth_dart/lib/src/auth_client.dart b/packages/nhost_auth_dart/lib/src/auth_client.dart index 4f80b9bd..392c494e 100644 --- a/packages/nhost_auth_dart/lib/src/auth_client.dart +++ b/packages/nhost_auth_dart/lib/src/auth_client.dart @@ -963,8 +963,9 @@ class NhostAuthClient implements HasuraAuthClient { @override String toString() { return { - 'accessToken': accessToken, - 'refreshToken': _session.session?.refreshToken, + 'accessToken': accessToken == null ? null : '', + 'refreshToken': + _session.session?.refreshToken == null ? null : '', 'accessTokenExpiresIn': _session.session?.accessTokenExpiresIn, 'userEmail': _session.session?.user?.email, }.toString(); diff --git a/packages/nhost_auth_dart/test/auth_client_test.dart b/packages/nhost_auth_dart/test/auth_client_test.dart new file mode 100644 index 00000000..dd4e6756 --- /dev/null +++ b/packages/nhost_auth_dart/test/auth_client_test.dart @@ -0,0 +1,46 @@ +import 'package:nhost_auth_dart/nhost_auth_dart.dart'; +import 'package:nhost_sdk/nhost_sdk.dart'; +import 'package:test/test.dart'; + +const _accessToken = 'eyJhbGciOiJIUzI1NiJ9.' + 'eyJodHRwczovL2hhc3VyYS5pby9qd3QvY2xhaW1zIjp7IngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsidXNlciIsIm1lIl0sIngtaGFzdXJhLWRlZmF1bHQtcm9sZSI6InVzZXIiLCJ4LWhhc3VyYS11c2VyLWlkIjoiNzEwYTgyNjMtNTgyNi00NTYzLWE4YTUtNGUyNzJkNDQxYWVkIiwieC1oYXN1cmEtdXNlci1pc0Fub255bW91cyI6ImZhbHNlIn0sInN1YiI6IjcxMGE4MjYzLTU4MjYtNDU2My1hOGE1LTRlMjcyZDQ0MWFlZCIsImlzcyI6Imhhc3VyYS1hdXRoIiwiaWF0IjoxNjQzMzQ3NzgwLCJleHAiOjE2NDMzNDg2ODB9.' + 'xzsBH0p34ynPwaHnNs97gVL5tdrccFOrxosuqBra1iw'; +const _refreshToken = 'refresh-token-should-not-be-logged'; + +void main() { + group('NhostAuthClient', () { + test('does not expose session tokens in string output', () async { + final auth = NhostAuthClient(url: 'http://localhost'); + addTearDown(auth.close); + + await auth.setSession( + Session( + accessToken: _accessToken, + accessTokenExpiresIn: Duration(seconds: 900), + refreshToken: _refreshToken, + user: User( + id: '710a8263-5826-4563-a8a5-4e272d441aed', + displayName: 'Test User', + locale: 'en', + createdAt: DateTime.utc(2024), + isAnonymous: false, + defaultRole: 'user', + roles: const ['user'], + emailVerified: true, + phoneNumber: '', + phoneNumberVerified: false, + email: 'test@example.com', + ), + ), + ); + + final output = auth.toString(); + + expect(output, isNot(contains(_accessToken))); + expect(output, isNot(contains(_refreshToken))); + expect(output, contains('accessToken: ')); + expect(output, contains('refreshToken: ')); + expect(output, contains('test@example.com')); + }); + }); +} From d55d79fba40c7c737d58b879b083165de0f8fecf Mon Sep 17 00:00:00 2001 From: ildysilva Date: Sat, 6 Jun 2026 22:29:12 +0100 Subject: [PATCH 4/8] chore: fix package repository links --- packages/nhost_auth_dart/pubspec.yaml | 2 +- packages/nhost_dart/pubspec.yaml | 2 +- packages/nhost_functions_dart/pubspec.yaml | 2 +- packages/nhost_storage_dart/pubspec.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/nhost_auth_dart/pubspec.yaml b/packages/nhost_auth_dart/pubspec.yaml index 19badb3d..b2b8f80e 100644 --- a/packages/nhost_auth_dart/pubspec.yaml +++ b/packages/nhost_auth_dart/pubspec.yaml @@ -2,7 +2,7 @@ name: nhost_auth_dart description: Nhost Dart Auth Service SDK version: 2.6.1 homepage: https://nhost.io -repository: https://github.com/nhost/nhost-dart/tree/main/packages/nhost_sdk +repository: https://github.com/nhost/nhost-dart/tree/main/packages/nhost_auth_dart issue_tracker: https://github.com/nhost/nhost-dart/issues environment: diff --git a/packages/nhost_dart/pubspec.yaml b/packages/nhost_dart/pubspec.yaml index 388f66c0..c687733d 100644 --- a/packages/nhost_dart/pubspec.yaml +++ b/packages/nhost_dart/pubspec.yaml @@ -2,7 +2,7 @@ name: nhost_dart description: Nhost Dart SDK version: 2.2.0 homepage: https://nhost.io -repository: https://github.com/nhost/nhost-dart/tree/main/packages/nhost_sdk +repository: https://github.com/nhost/nhost-dart/tree/main/packages/nhost_dart issue_tracker: https://github.com/nhost/nhost-dart/issues environment: diff --git a/packages/nhost_functions_dart/pubspec.yaml b/packages/nhost_functions_dart/pubspec.yaml index fb85b2f9..db9c9e07 100644 --- a/packages/nhost_functions_dart/pubspec.yaml +++ b/packages/nhost_functions_dart/pubspec.yaml @@ -2,7 +2,7 @@ name: nhost_functions_dart description: Nhost Dart Functions Service SDK version: 2.0.10 homepage: https://nhost.io -repository: https://github.com/nhost/nhost-dart/tree/main/packages/nhost_sdk +repository: https://github.com/nhost/nhost-dart/tree/main/packages/nhost_functions_dart issue_tracker: https://github.com/nhost/nhost-dart/issues environment: diff --git a/packages/nhost_storage_dart/pubspec.yaml b/packages/nhost_storage_dart/pubspec.yaml index b757f26b..9fc283ce 100644 --- a/packages/nhost_storage_dart/pubspec.yaml +++ b/packages/nhost_storage_dart/pubspec.yaml @@ -2,7 +2,7 @@ name: nhost_storage_dart description: Nhost Dart Storage Service SDK version: 2.2.0 homepage: https://nhost.io -repository: https://github.com/nhost/nhost-dart/tree/main/packages/nhost_sdk +repository: https://github.com/nhost/nhost-dart/tree/main/packages/nhost_storage_dart issue_tracker: https://github.com/nhost/nhost-dart/issues environment: From f3d762e36a7e5c04dba119d177214fe1aa320738 Mon Sep 17 00:00:00 2001 From: ildysilva Date: Sat, 6 Jun 2026 22:33:07 +0100 Subject: [PATCH 5/8] chore: improve package descriptions --- packages/nhost_auth_dart/pubspec.yaml | 2 +- packages/nhost_dart/pubspec.yaml | 2 +- packages/nhost_flutter_auth/pubspec.yaml | 4 ++-- packages/nhost_flutter_graphql/pubspec.yaml | 4 ++-- packages/nhost_functions_dart/pubspec.yaml | 2 +- packages/nhost_gql_links/pubspec.yaml | 2 +- packages/nhost_graphql_adapter/pubspec.yaml | 2 +- packages/nhost_sdk/pubspec.yaml | 2 +- packages/nhost_storage_dart/pubspec.yaml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/nhost_auth_dart/pubspec.yaml b/packages/nhost_auth_dart/pubspec.yaml index b2b8f80e..53982d2f 100644 --- a/packages/nhost_auth_dart/pubspec.yaml +++ b/packages/nhost_auth_dart/pubspec.yaml @@ -1,5 +1,5 @@ name: nhost_auth_dart -description: Nhost Dart Auth Service SDK +description: Dart client for Nhost Auth with email/password, passwordless, MFA, anonymous sign-in, and session management. version: 2.6.1 homepage: https://nhost.io repository: https://github.com/nhost/nhost-dart/tree/main/packages/nhost_auth_dart diff --git a/packages/nhost_dart/pubspec.yaml b/packages/nhost_dart/pubspec.yaml index c687733d..eb062fb6 100644 --- a/packages/nhost_dart/pubspec.yaml +++ b/packages/nhost_dart/pubspec.yaml @@ -1,5 +1,5 @@ name: nhost_dart -description: Nhost Dart SDK +description: Dart client for Nhost Auth, Storage, Functions, and GraphQL helpers for apps using Nhost. version: 2.2.0 homepage: https://nhost.io repository: https://github.com/nhost/nhost-dart/tree/main/packages/nhost_dart diff --git a/packages/nhost_flutter_auth/pubspec.yaml b/packages/nhost_flutter_auth/pubspec.yaml index 0dc3d282..42924402 100644 --- a/packages/nhost_flutter_auth/pubspec.yaml +++ b/packages/nhost_flutter_auth/pubspec.yaml @@ -1,7 +1,7 @@ name: nhost_flutter_auth description: > - Provides Nhost authentication state to your Flutter app, making it easy to set - up protected resources, and react to sign ins and sign outs. + Flutter widgets and listenables for exposing Nhost authentication state in + your app. version: 4.2.1 homepage: https://nhost.io repository: https://github.com/nhost/nhost-dart/tree/main/packages/nhost_flutter_auth diff --git a/packages/nhost_flutter_graphql/pubspec.yaml b/packages/nhost_flutter_graphql/pubspec.yaml index 419c1c8d..a8a2f96d 100644 --- a/packages/nhost_flutter_graphql/pubspec.yaml +++ b/packages/nhost_flutter_graphql/pubspec.yaml @@ -1,7 +1,7 @@ name: nhost_flutter_graphql description: > - Provides GraphQL clients to your Flutter application, automatically configured - to work with Nhost + Flutter GraphQL provider for authenticated Nhost queries and subscriptions + with graphql_flutter. version: 3.1.2 homepage: https://nhost.io repository: https://github.com/nhost/nhost-dart/tree/main/packages/nhost_flutter_graphql diff --git a/packages/nhost_functions_dart/pubspec.yaml b/packages/nhost_functions_dart/pubspec.yaml index db9c9e07..176c62c5 100644 --- a/packages/nhost_functions_dart/pubspec.yaml +++ b/packages/nhost_functions_dart/pubspec.yaml @@ -1,5 +1,5 @@ name: nhost_functions_dart -description: Nhost Dart Functions Service SDK +description: Dart client for calling Nhost serverless functions with authenticated HTTP requests. version: 2.0.10 homepage: https://nhost.io repository: https://github.com/nhost/nhost-dart/tree/main/packages/nhost_functions_dart diff --git a/packages/nhost_gql_links/pubspec.yaml b/packages/nhost_gql_links/pubspec.yaml index cae3a500..e1333fc0 100644 --- a/packages/nhost_gql_links/pubspec.yaml +++ b/packages/nhost_gql_links/pubspec.yaml @@ -1,6 +1,6 @@ name: nhost_gql_links version: 4.0.11 -description: Constructs GraphQL links for use with graphql and ferry packages +description: Low-level GraphQL links for Nhost authentication over HTTP and WebSocket transports. homepage: https://nhost.io repository: https://github.com/nhost/nhost-dart/tree/main/packages/nhost_gql_links issue_tracker: https://github.com/nhost/nhost-dart/issues diff --git a/packages/nhost_graphql_adapter/pubspec.yaml b/packages/nhost_graphql_adapter/pubspec.yaml index 0146295f..dd7ed062 100644 --- a/packages/nhost_graphql_adapter/pubspec.yaml +++ b/packages/nhost_graphql_adapter/pubspec.yaml @@ -1,6 +1,6 @@ name: nhost_graphql_adapter version: 4.0.10 -description: Easily connect to your Nhost.io GraphQL backend using the graphql package. +description: GraphQL client helpers for connecting package:graphql clients to an authenticated Nhost backend. homepage: https://nhost.io repository: https://github.com/nhost/nhost-dart/tree/main/packages/nhost_graphql_adapter issue_tracker: https://github.com/nhost/nhost-dart/issues diff --git a/packages/nhost_sdk/pubspec.yaml b/packages/nhost_sdk/pubspec.yaml index 3e2665bf..6c2898fe 100644 --- a/packages/nhost_sdk/pubspec.yaml +++ b/packages/nhost_sdk/pubspec.yaml @@ -1,5 +1,5 @@ name: nhost_sdk -description: Nhost authentication and file storage/retrieval APIs for the Dart language. +description: Shared core types and low-level HTTP APIs used by the Nhost Dart and Flutter packages. version: 5.8.0 homepage: https://nhost.io repository: https://github.com/nhost/nhost-dart/tree/main/packages/nhost_sdk diff --git a/packages/nhost_storage_dart/pubspec.yaml b/packages/nhost_storage_dart/pubspec.yaml index 9fc283ce..a51610e8 100644 --- a/packages/nhost_storage_dart/pubspec.yaml +++ b/packages/nhost_storage_dart/pubspec.yaml @@ -1,5 +1,5 @@ name: nhost_storage_dart -description: Nhost Dart Storage Service SDK +description: Dart client for Nhost Storage with file upload, download, deletion, presigned URLs, and image transforms. version: 2.2.0 homepage: https://nhost.io repository: https://github.com/nhost/nhost-dart/tree/main/packages/nhost_storage_dart From 37583f81637bd7c5430e7985a3c1677c3f7d4ea6 Mon Sep 17 00:00:00 2001 From: ildysilva Date: Sat, 6 Jun 2026 22:36:48 +0100 Subject: [PATCH 6/8] docs: align readme dependency versions --- packages/nhost_auth_dart/README.md | 2 +- packages/nhost_flutter_auth/README.md | 2 +- packages/nhost_flutter_graphql/README.md | 2 +- packages/nhost_functions_dart/README.md | 2 +- packages/nhost_gql_links/README.md | 2 +- packages/nhost_graphql_adapter/README.md | 2 +- packages/nhost_storage_dart/README.md | 4 ++-- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/nhost_auth_dart/README.md b/packages/nhost_auth_dart/README.md index 8e1441aa..91618dbe 100644 --- a/packages/nhost_auth_dart/README.md +++ b/packages/nhost_auth_dart/README.md @@ -44,5 +44,5 @@ void main() async { ```yaml dependencies: - nhost_auth_dart: ^2.0.0 + nhost_auth_dart: ^2.6.1 ``` diff --git a/packages/nhost_flutter_auth/README.md b/packages/nhost_flutter_auth/README.md index 956603ba..65a7299f 100644 --- a/packages/nhost_flutter_auth/README.md +++ b/packages/nhost_flutter_auth/README.md @@ -13,7 +13,7 @@ outs. ```yaml dependencies: - nhost_flutter_auth: ^3.0.0 + nhost_flutter_auth: ^4.2.1 ``` ## 🔥 More Dart & Flutter packages from Nhost diff --git a/packages/nhost_flutter_graphql/README.md b/packages/nhost_flutter_graphql/README.md index 7dd52dc6..573acdf8 100644 --- a/packages/nhost_flutter_graphql/README.md +++ b/packages/nhost_flutter_graphql/README.md @@ -12,7 +12,7 @@ to work with [Nhost](https://nhost.io). ```yaml dependencies: - nhost_flutter_graphql: ^3.0.0 + nhost_flutter_graphql: ^3.1.2 ``` ## 🔥 More Dart & Flutter packages from Nhost diff --git a/packages/nhost_functions_dart/README.md b/packages/nhost_functions_dart/README.md index 0def7731..67342264 100644 --- a/packages/nhost_functions_dart/README.md +++ b/packages/nhost_functions_dart/README.md @@ -29,5 +29,5 @@ void main() async { ```yaml dependencies: - nhost_functions_dart: ^1.0.0 + nhost_functions_dart: ^2.0.10 ``` diff --git a/packages/nhost_gql_links/README.md b/packages/nhost_gql_links/README.md index ea7af443..04b3630b 100644 --- a/packages/nhost_gql_links/README.md +++ b/packages/nhost_gql_links/README.md @@ -14,7 +14,7 @@ and generally isn't meant for use directly. ```yaml dependencies: - nhost_gql_links: ^3.0.0 + nhost_gql_links: ^4.0.11 ``` ## 🔥 More Dart & Flutter packages from Nhost diff --git a/packages/nhost_graphql_adapter/README.md b/packages/nhost_graphql_adapter/README.md index 01c579e1..a96a6c60 100644 --- a/packages/nhost_graphql_adapter/README.md +++ b/packages/nhost_graphql_adapter/README.md @@ -17,7 +17,7 @@ for widgets that work great with ```yaml dependencies: - nhost_graphql_adapter: ^3.0.0 + nhost_graphql_adapter: ^4.0.10 ``` ## 🔥 More Dart & Flutter packages from Nhost diff --git a/packages/nhost_storage_dart/README.md b/packages/nhost_storage_dart/README.md index edf889f8..05321cae 100644 --- a/packages/nhost_storage_dart/README.md +++ b/packages/nhost_storage_dart/README.md @@ -58,6 +58,6 @@ void main() async { ```yaml dependencies: - nhost_auth_dart: ^2.0.0 - nhost_storage_dart: ^2.0.0 + nhost_auth_dart: ^2.6.1 + nhost_storage_dart: ^2.2.0 ``` From bf39230007960c06ea02845b16eaf4cb8f254a80 Mon Sep 17 00:00:00 2001 From: ildysilva Date: Sun, 7 Jun 2026 02:12:49 +0100 Subject: [PATCH 7/8] feat: add nhost_flutter package with full auth DX and complete auth API coverage --- .gitignore | 4 + .vscode/launch.json | 8 + .../nhost_auth_dart/lib/src/auth_client.dart | 152 ++++ packages/nhost_dart/lib/nhost_dart.dart | 7 +- packages/nhost_flutter/CHANGELOG.md | 8 + packages/nhost_flutter/analysis_options.yaml | 1 + packages/nhost_flutter/example/.gitignore | 45 + packages/nhost_flutter/example/README.md | 3 + .../example/analysis_options.yaml | 1 + packages/nhost_flutter/example/ios/.gitignore | 34 + .../ios/Flutter/AppFrameworkInfo.plist | 24 + .../example/ios/Flutter/Debug.xcconfig | 2 + .../example/ios/Flutter/Release.xcconfig | 2 + packages/nhost_flutter/example/ios/Podfile | 43 + .../ios/Runner.xcodeproj/project.pbxproj | 732 +++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 101 +++ .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../example/ios/Runner/AppDelegate.swift | 16 + .../AppIcon.appiconset/Contents.json | 122 +++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 295 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 450 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 282 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 462 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 704 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 586 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 1674 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 762 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 1226 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 1418 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 + .../ios/Runner/Base.lproj/Main.storyboard | 26 + .../example/ios/Runner/Info.plist | 70 ++ .../ios/Runner/Runner-Bridging-Header.h | 1 + .../example/ios/Runner/SceneDelegate.swift | 6 + .../example/ios/RunnerTests/RunnerTests.swift | 12 + .../nhost_flutter/example/lib/config.dart | 4 + packages/nhost_flutter/example/lib/main.dart | 766 ++++++++++++++++++ packages/nhost_flutter/example/pubspec.yaml | 21 + .../example/pubspec_overrides.yaml | 22 + packages/nhost_flutter/lib/nhost_flutter.dart | 13 + .../lib/src/auth_client_extensions.dart | 55 ++ .../nhost_flutter/lib/src/auth_state.dart | 19 + packages/nhost_flutter/lib/src/nhost.dart | 109 +++ .../lib/src/secure_auth_store.dart | 39 + .../lib/src/widgets/nhost_auth_gate.dart | 45 + .../src/widgets/nhost_auth_state_builder.dart | 40 + .../lib/src/widgets/nhost_signed_in.dart | 22 + .../lib/src/widgets/nhost_signed_out.dart | 22 + .../lib/src/widgets/nhost_user_builder.dart | 26 + packages/nhost_flutter/pubspec.yaml | 28 + packages/nhost_flutter/pubspec_overrides.yaml | 20 + .../test/auth_client_extensions_test.dart | 72 ++ .../nhost_flutter/test/auth_state_test.dart | 58 ++ .../test/helpers/fake_auth_client.dart | 243 ++++++ .../test/nhost_singleton_test.dart | 76 ++ .../test/secure_auth_store_test.dart | 17 + .../test/widgets/nhost_auth_gate_test.dart | 86 ++ .../nhost_auth_state_builder_test.dart | 73 ++ .../widgets/nhost_signed_in_out_test.dart | 68 ++ .../test/widgets/nhost_user_builder_test.dart | 64 ++ .../lib/src/base/hasura_auth_client.dart | 60 ++ 76 files changed, 3601 insertions(+), 1 deletion(-) create mode 100644 packages/nhost_flutter/CHANGELOG.md create mode 100644 packages/nhost_flutter/analysis_options.yaml create mode 100644 packages/nhost_flutter/example/.gitignore create mode 100644 packages/nhost_flutter/example/README.md create mode 100644 packages/nhost_flutter/example/analysis_options.yaml create mode 100644 packages/nhost_flutter/example/ios/.gitignore create mode 100644 packages/nhost_flutter/example/ios/Flutter/AppFrameworkInfo.plist create mode 100644 packages/nhost_flutter/example/ios/Flutter/Debug.xcconfig create mode 100644 packages/nhost_flutter/example/ios/Flutter/Release.xcconfig create mode 100644 packages/nhost_flutter/example/ios/Podfile create mode 100644 packages/nhost_flutter/example/ios/Runner.xcodeproj/project.pbxproj create mode 100644 packages/nhost_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 packages/nhost_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/nhost_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 packages/nhost_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 packages/nhost_flutter/example/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 packages/nhost_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/nhost_flutter/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 packages/nhost_flutter/example/ios/Runner/AppDelegate.swift create mode 100644 packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 packages/nhost_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 packages/nhost_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 packages/nhost_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 packages/nhost_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 packages/nhost_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 packages/nhost_flutter/example/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 packages/nhost_flutter/example/ios/Runner/Base.lproj/Main.storyboard create mode 100644 packages/nhost_flutter/example/ios/Runner/Info.plist create mode 100644 packages/nhost_flutter/example/ios/Runner/Runner-Bridging-Header.h create mode 100644 packages/nhost_flutter/example/ios/Runner/SceneDelegate.swift create mode 100644 packages/nhost_flutter/example/ios/RunnerTests/RunnerTests.swift create mode 100644 packages/nhost_flutter/example/lib/config.dart create mode 100644 packages/nhost_flutter/example/lib/main.dart create mode 100644 packages/nhost_flutter/example/pubspec.yaml create mode 100644 packages/nhost_flutter/example/pubspec_overrides.yaml create mode 100644 packages/nhost_flutter/lib/nhost_flutter.dart create mode 100644 packages/nhost_flutter/lib/src/auth_client_extensions.dart create mode 100644 packages/nhost_flutter/lib/src/auth_state.dart create mode 100644 packages/nhost_flutter/lib/src/nhost.dart create mode 100644 packages/nhost_flutter/lib/src/secure_auth_store.dart create mode 100644 packages/nhost_flutter/lib/src/widgets/nhost_auth_gate.dart create mode 100644 packages/nhost_flutter/lib/src/widgets/nhost_auth_state_builder.dart create mode 100644 packages/nhost_flutter/lib/src/widgets/nhost_signed_in.dart create mode 100644 packages/nhost_flutter/lib/src/widgets/nhost_signed_out.dart create mode 100644 packages/nhost_flutter/lib/src/widgets/nhost_user_builder.dart create mode 100644 packages/nhost_flutter/pubspec.yaml create mode 100644 packages/nhost_flutter/pubspec_overrides.yaml create mode 100644 packages/nhost_flutter/test/auth_client_extensions_test.dart create mode 100644 packages/nhost_flutter/test/auth_state_test.dart create mode 100644 packages/nhost_flutter/test/helpers/fake_auth_client.dart create mode 100644 packages/nhost_flutter/test/nhost_singleton_test.dart create mode 100644 packages/nhost_flutter/test/secure_auth_store_test.dart create mode 100644 packages/nhost_flutter/test/widgets/nhost_auth_gate_test.dart create mode 100644 packages/nhost_flutter/test/widgets/nhost_auth_state_builder_test.dart create mode 100644 packages/nhost_flutter/test/widgets/nhost_signed_in_out_test.dart create mode 100644 packages/nhost_flutter/test/widgets/nhost_user_builder_test.dart diff --git a/.gitignore b/.gitignore index c356c480..8b346603 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,7 @@ packages/**/example/android/app/src/main/kotlin/io/flutter/plugins/GeneratedPlug .env.test.local .env.production.local .melos_tool + +# AI +docs/superpowers/ + diff --git a/.vscode/launch.json b/.vscode/launch.json index f075dbf0..1ab75806 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,14 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "name": "nhost_flutter", + "cwd": "packages/nhost_flutter/example", + "program": "lib/main.dart", + "request": "launch", + "flutterMode": "debug", + "type": "dart" + }, { "name": "nhost_flutter_auth", "cwd": "packages/nhost_flutter_auth/example", diff --git a/packages/nhost_auth_dart/lib/src/auth_client.dart b/packages/nhost_auth_dart/lib/src/auth_client.dart index 392c494e..047dffba 100644 --- a/packages/nhost_auth_dart/lib/src/auth_client.dart +++ b/packages/nhost_auth_dart/lib/src/auth_client.dart @@ -805,6 +805,152 @@ class NhostAuthClient implements HasuraAuthClient { //#endregion + //#region PAT, fetchUser, verifyToken + + /// Signs in using a Personal Access Token (PAT). + @override + Future signInWithPat(String pat) async { + log.finer('Attempting sign in (PAT)'); + AuthResponse? res; + try { + res = await _apiClient.post( + '/signin/pat', + jsonBody: {'personalAccessToken': pat}, + responseDeserializer: AuthResponse.fromJson, + ); + } catch (e, st) { + log.finer('Sign in (PAT) failed', e, st); + await clearSession(); + rethrow; + } + log.finer('Sign in (PAT) successful'); + await setSession(res!.session!); + return res; + } + + /// Fetches the current user's profile from the server. + @override + Future fetchUser() async { + log.finer('Fetching current user'); + final user = await _apiClient.get( + '/user', + responseDeserializer: User.fromJson, + headers: _session.authenticationHeaders, + ); + _currentUser = user; + return user; + } + + /// Verifies whether [accessToken] is still valid on the server. + @override + Future verifyToken(String accessToken) async { + log.finer('Verifying token'); + try { + await _apiClient.post( + '/token/verify', + headers: {'Authorization': 'Bearer $accessToken'}, + ); + return true; + } on ApiException { + return false; + } + } + + //#endregion + + //#region WebAuthn — platform-dependent stubs + + @override + Future> signInWithWebAuthn() async { + return _apiClient.post( + '/signin/webauthn', + responseDeserializer: (json) => Map.from(json as Map), + ); + } + + @override + Future verifyWebAuthnSignIn( + Map assertionResponse, + ) async { + log.finer('Attempting WebAuthn sign-in verification'); + final res = await _apiClient.post( + '/signin/webauthn/verify', + jsonBody: assertionResponse, + responseDeserializer: AuthResponse.fromJson, + ); + await setSession(res.session!); + return res; + } + + @override + Future> signUpWithWebAuthn({String? email}) async { + return _apiClient.post( + '/signup/webauthn', + jsonBody: {if (email != null) 'email': email}, + responseDeserializer: (json) => Map.from(json as Map), + ); + } + + @override + Future verifyWebAuthnSignUp( + Map attestationResponse, + ) async { + log.finer('Attempting WebAuthn sign-up verification'); + final res = await _apiClient.post( + '/signup/webauthn/verify', + jsonBody: attestationResponse, + responseDeserializer: AuthResponse.fromJson, + ); + if (res.session != null) await setSession(res.session!); + return res; + } + + @override + Future> addWebAuthnCredential() async { + return _apiClient.post( + '/user/webauthn/add', + headers: _session.authenticationHeaders, + responseDeserializer: (json) => Map.from(json as Map), + ); + } + + @override + Future verifyAddWebAuthnCredential( + Map attestationResponse, + ) async { + await _apiClient.post( + '/user/webauthn/verify', + jsonBody: attestationResponse, + headers: _session.authenticationHeaders, + ); + } + + @override + Future> elevateWithWebAuthn() async { + return _apiClient.post( + '/elevate/webauthn', + headers: _session.authenticationHeaders, + responseDeserializer: (json) => Map.from(json as Map), + ); + } + + @override + Future verifyWebAuthnElevation( + Map assertionResponse, + ) async { + log.finer('Attempting WebAuthn elevation verification'); + final res = await _apiClient.post( + '/elevate/webauthn/verify', + jsonBody: assertionResponse, + headers: _session.authenticationHeaders, + responseDeserializer: AuthResponse.fromJson, + ); + if (res.session != null) await setSession(res.session!); + return res; + } + + //#endregion + //#region Token and session Handling Future _refreshSession([String? initRefreshToken]) async { @@ -857,6 +1003,12 @@ class NhostAuthClient implements HasuraAuthClient { if (e is ApiException && e.statusCode == unauthorizedStatus) { log.finest('Unauthorized refresh token. Forcing signout.'); await signOut(); + } else { + // Non-401 failure (e.g. network error, 5xx): clearSession is not called, + // so we must manually clear _loading to prevent authenticationState from + // remaining stuck at inProgress forever (issue #180). + _loading = false; + _onAuthStateChanged(authenticationState); } log.severe('Exception during token refresh', e, st); diff --git a/packages/nhost_dart/lib/nhost_dart.dart b/packages/nhost_dart/lib/nhost_dart.dart index f5859555..0e750f5d 100644 --- a/packages/nhost_dart/lib/nhost_dart.dart +++ b/packages/nhost_dart/lib/nhost_dart.dart @@ -12,13 +12,18 @@ export 'package:nhost_sdk/nhost_sdk.dart' show ApiException, Session, + User, + AuthResponse, + MultiFactorAuthResponse, createNhostServiceEndpoint, ServiceUrls, Subdomain, AuthenticationState, AuthStateChangedCallback, UnsubscribeDelegate, - AuthStore; + AuthStore, + DeanonymizeOptions, + DeanonymizeSignInMethod; export 'package:nhost_storage_dart/nhost_storage_dart.dart' show NhostStorageClient, ImageCornerRadius, ImageTransform; export 'package:nhost_auth_dart/nhost_auth_dart.dart' show NhostAuthClient; diff --git a/packages/nhost_flutter/CHANGELOG.md b/packages/nhost_flutter/CHANGELOG.md new file mode 100644 index 00000000..cda7e885 --- /dev/null +++ b/packages/nhost_flutter/CHANGELOG.md @@ -0,0 +1,8 @@ +## 1.0.0 + +- Initial release. +- `Nhost.initialize()` / `Nhost.instance` / `Nhost.local()` singleton API. +- `SecureAuthStore` backed by `flutter_secure_storage` (default). +- Sealed `AuthState` hierarchy: `AuthStateLoading`, `AuthStateSignedIn`, `AuthStateSignedOut`. +- `authStateChanges` stream and `authStateListenable` on `NhostAuthClient`. +- Auth widgets: `NhostAuthGate`, `NhostAuthStateBuilder`, `NhostSignedIn`, `NhostSignedOut`, `NhostUserBuilder`. diff --git a/packages/nhost_flutter/analysis_options.yaml b/packages/nhost_flutter/analysis_options.yaml new file mode 100644 index 00000000..572dd239 --- /dev/null +++ b/packages/nhost_flutter/analysis_options.yaml @@ -0,0 +1 @@ +include: package:lints/recommended.yaml diff --git a/packages/nhost_flutter/example/.gitignore b/packages/nhost_flutter/example/.gitignore new file mode 100644 index 00000000..3820a95c --- /dev/null +++ b/packages/nhost_flutter/example/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/packages/nhost_flutter/example/README.md b/packages/nhost_flutter/example/README.md new file mode 100644 index 00000000..237f8536 --- /dev/null +++ b/packages/nhost_flutter/example/README.md @@ -0,0 +1,3 @@ +# nhost_flutter_example + +A new Flutter project. diff --git a/packages/nhost_flutter/example/analysis_options.yaml b/packages/nhost_flutter/example/analysis_options.yaml new file mode 100644 index 00000000..f9b30346 --- /dev/null +++ b/packages/nhost_flutter/example/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flutter_lints/flutter.yaml diff --git a/packages/nhost_flutter/example/ios/.gitignore b/packages/nhost_flutter/example/ios/.gitignore new file mode 100644 index 00000000..7a7f9873 --- /dev/null +++ b/packages/nhost_flutter/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/packages/nhost_flutter/example/ios/Flutter/AppFrameworkInfo.plist b/packages/nhost_flutter/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..391a902b --- /dev/null +++ b/packages/nhost_flutter/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + + diff --git a/packages/nhost_flutter/example/ios/Flutter/Debug.xcconfig b/packages/nhost_flutter/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..ec97fc6f --- /dev/null +++ b/packages/nhost_flutter/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/nhost_flutter/example/ios/Flutter/Release.xcconfig b/packages/nhost_flutter/example/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..c4855bfe --- /dev/null +++ b/packages/nhost_flutter/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/nhost_flutter/example/ios/Podfile b/packages/nhost_flutter/example/ios/Podfile new file mode 100644 index 00000000..620e46eb --- /dev/null +++ b/packages/nhost_flutter/example/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/packages/nhost_flutter/example/ios/Runner.xcodeproj/project.pbxproj b/packages/nhost_flutter/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..93fba73a --- /dev/null +++ b/packages/nhost_flutter/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,732 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + CFCA3411CCEFD031824BF762 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD338F30B45336939DE98143 /* Pods_Runner.framework */; }; + E07329A372348F3233D12B91 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12454FDB7B0662B1D4241238 /* Pods_RunnerTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 12454FDB7B0662B1D4241238 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 1D95F44F251FCE215897879D /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 4CC4AF7CFED5E26CFEB0D2B2 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 5F80C02967755E7DB2A7A06E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 8E7E5B137788D5306651503E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B0412D4587F4B2ABBBD773EA /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + CD338F30B45336939DE98143 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FD6D7F31F881DE326CB185AF /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 46A6AD4501C44E70B70EE62E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E07329A372348F3233D12B91 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CFCA3411CCEFD031824BF762 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + 97C234F8D6B6D23BD91ECA5B /* Pods */, + EE675CBBA5E6CCBFF40BD63C /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + 97C234F8D6B6D23BD91ECA5B /* Pods */ = { + isa = PBXGroup; + children = ( + 8E7E5B137788D5306651503E /* Pods-Runner.debug.xcconfig */, + 4CC4AF7CFED5E26CFEB0D2B2 /* Pods-Runner.release.xcconfig */, + 5F80C02967755E7DB2A7A06E /* Pods-Runner.profile.xcconfig */, + 1D95F44F251FCE215897879D /* Pods-RunnerTests.debug.xcconfig */, + B0412D4587F4B2ABBBD773EA /* Pods-RunnerTests.release.xcconfig */, + FD6D7F31F881DE326CB185AF /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + EE675CBBA5E6CCBFF40BD63C /* Frameworks */ = { + isa = PBXGroup; + children = ( + CD338F30B45336939DE98143 /* Pods_Runner.framework */, + 12454FDB7B0662B1D4241238 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + EF13CA40C14EF1B85B041795 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 46A6AD4501C44E70B70EE62E /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + C80DBCC7863375EE011B8BAE /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + A8742DAF69B4CD9128879215 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + A8742DAF69B4CD9128879215 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + C80DBCC7863375EE011B8BAE /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + EF13CA40C14EF1B85B041795 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.nhostFlutterExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1D95F44F251FCE215897879D /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.nhostFlutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B0412D4587F4B2ABBBD773EA /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.nhostFlutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FD6D7F31F881DE326CB185AF /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.nhostFlutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.nhostFlutterExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.nhostFlutterExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/nhost_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/nhost_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/packages/nhost_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/nhost_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/nhost_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/nhost_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/nhost_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/nhost_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/packages/nhost_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/nhost_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/nhost_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..e3773d42 --- /dev/null +++ b/packages/nhost_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/nhost_flutter/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/nhost_flutter/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/packages/nhost_flutter/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/nhost_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/nhost_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/nhost_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/nhost_flutter/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/nhost_flutter/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/packages/nhost_flutter/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/nhost_flutter/example/ios/Runner/AppDelegate.swift b/packages/nhost_flutter/example/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..c30b367e --- /dev/null +++ b/packages/nhost_flutter/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,16 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } +} diff --git a/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d36b1fab --- /dev/null +++ b/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..797d452e458972bab9d994556c8305db4c827017 GIT binary patch literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed2d933e1120817fe9182483a228007b18ab6ae GIT binary patch literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 literal 0 HcmV?d00001 diff --git a/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd7b0099ca80c806f8fe495613e8d6c69460d76 GIT binary patch literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX literal 0 HcmV?d00001 diff --git a/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe730945a01f64a61e2235dbe3f45b08f7729182 GIT binary patch literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..502f463a9bc882b461c96aadf492d1729e49e725 GIT binary patch literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec303439225b78712f49115768196d8d76f6790 GIT binary patch literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fea27c705180eb716271f41b582e76dcbd90 GIT binary patch literal 1674 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 literal 0 HcmV?d00001 diff --git a/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8 GIT binary patch literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H literal 0 HcmV?d00001 diff --git a/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 00000000..0bedcf2f --- /dev/null +++ b/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..89c2725b --- /dev/null +++ b/packages/nhost_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/nhost_flutter/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/nhost_flutter/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/packages/nhost_flutter/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/nhost_flutter/example/ios/Runner/Base.lproj/Main.storyboard b/packages/nhost_flutter/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/packages/nhost_flutter/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/nhost_flutter/example/ios/Runner/Info.plist b/packages/nhost_flutter/example/ios/Runner/Info.plist new file mode 100644 index 00000000..b603bde0 --- /dev/null +++ b/packages/nhost_flutter/example/ios/Runner/Info.plist @@ -0,0 +1,70 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Nhost Flutter Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + nhost_flutter_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/packages/nhost_flutter/example/ios/Runner/Runner-Bridging-Header.h b/packages/nhost_flutter/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..308a2a56 --- /dev/null +++ b/packages/nhost_flutter/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/nhost_flutter/example/ios/Runner/SceneDelegate.swift b/packages/nhost_flutter/example/ios/Runner/SceneDelegate.swift new file mode 100644 index 00000000..b9ce8ea2 --- /dev/null +++ b/packages/nhost_flutter/example/ios/Runner/SceneDelegate.swift @@ -0,0 +1,6 @@ +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} diff --git a/packages/nhost_flutter/example/ios/RunnerTests/RunnerTests.swift b/packages/nhost_flutter/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..86a7c3b1 --- /dev/null +++ b/packages/nhost_flutter/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/nhost_flutter/example/lib/config.dart b/packages/nhost_flutter/example/lib/config.dart new file mode 100644 index 00000000..46c18957 --- /dev/null +++ b/packages/nhost_flutter/example/lib/config.dart @@ -0,0 +1,4 @@ +// Replace with your Nhost project subdomain and region. +// For local development use Nhost.local() instead of Nhost.initialize(). +const subdomain = 'xnnteixlkkyixpmpmwml'; +const region = 'eu-central-1'; diff --git a/packages/nhost_flutter/example/lib/main.dart b/packages/nhost_flutter/example/lib/main.dart new file mode 100644 index 00000000..2b86fcd1 --- /dev/null +++ b/packages/nhost_flutter/example/lib/main.dart @@ -0,0 +1,766 @@ +// nhost_flutter example app. +// Demonstrates every P1 Flutter Experience API: +// Nhost.initialize(), NhostAuthGate, NhostAuthStateBuilder, +// NhostSignedIn/Out, NhostUserBuilder, authStateChanges, authStateListenable +// sign-in, register, anonymous sign-in, forgot password, change password +import 'package:flutter/material.dart'; +import 'package:nhost_flutter/nhost_flutter.dart'; + +import 'config.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + await Nhost.initialize( + subdomain: Subdomain(subdomain: subdomain, region: region), + // SecureAuthStore is used by default — tokens are persisted automatically. + // For local development, swap the line above with: + // await Nhost.local(); + ); + + runApp(const NhostExampleApp()); +} + +class NhostExampleApp extends StatelessWidget { + const NhostExampleApp({super.key}); + + @override + Widget build(BuildContext context) { + return NhostAuthProvider( + auth: Nhost.instance.auth, + child: MaterialApp( + title: 'nhost_flutter example', + home: NhostAuthGate( + loading: (_) => const _SplashScreen(), + signedOut: (_) => const _SignInScreen(), + signedIn: (_, user, __) => _HomeScreen(user: user), + ), + ), + ); + } +} + +// --------------------------------------------------------------------------- +// Splash +// --------------------------------------------------------------------------- + +class _SplashScreen extends StatelessWidget { + const _SplashScreen(); + + @override + Widget build(BuildContext context) { + return const Scaffold( + body: Center(child: CircularProgressIndicator()), + ); + } +} + +// --------------------------------------------------------------------------- +// Sign-in +// --------------------------------------------------------------------------- + +class _SignInScreen extends StatefulWidget { + const _SignInScreen(); + + @override + State<_SignInScreen> createState() => _SignInScreenState(); +} + +class _SignInScreenState extends State<_SignInScreen> { + final _email = TextEditingController(text: 'user-1@nhost.io'); + final _password = TextEditingController(text: 'password-1'); + bool _loading = false; + + Future _signIn() async { + setState(() => _loading = true); + try { + await Nhost.instance.auth.signInEmailPassword( + email: _email.text, + password: _password.text, + ); + } on ApiException catch (e) { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Sign in failed: ${e.responseBody}')), + ); + } finally { + if (mounted) setState(() => _loading = false); + } + } + + Future _signInAnonymously() async { + setState(() => _loading = true); + try { + await Nhost.instance.auth.signInAnonymous(null, null, null); + } on ApiException catch (e) { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Anonymous sign in failed: ${e.responseBody}')), + ); + } finally { + if (mounted) setState(() => _loading = false); + } + } + + void _goToRegister() => Navigator.of(context).push( + MaterialPageRoute(builder: (_) => const _RegisterScreen()), + ); + + void _goToForgotPassword() => Navigator.of(context).push( + MaterialPageRoute(builder: (_) => const _ForgotPasswordScreen()), + ); + + @override + void dispose() { + _email.dispose(); + _password.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Sign in')), + body: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextField( + controller: _email, + decoration: const InputDecoration( + labelText: 'Email', + border: OutlineInputBorder(), + ), + keyboardType: TextInputType.emailAddress, + ), + const SizedBox(height: 12), + TextField( + controller: _password, + decoration: const InputDecoration( + labelText: 'Password', + border: OutlineInputBorder(), + ), + obscureText: true, + ), + Align( + alignment: Alignment.centerRight, + child: TextButton( + onPressed: _goToForgotPassword, + child: const Text('Forgot password?'), + ), + ), + const SizedBox(height: 8), + SizedBox( + width: double.infinity, + child: FilledButton( + onPressed: _loading ? null : _signIn, + child: _loading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Text('Sign in'), + ), + ), + const SizedBox(height: 8), + SizedBox( + width: double.infinity, + child: OutlinedButton.icon( + icon: const Icon(Icons.person_outline), + label: const Text('Continue as guest'), + onPressed: _loading ? null : _signInAnonymously, + ), + ), + const SizedBox(height: 12), + TextButton( + onPressed: _goToRegister, + child: const Text("Don't have an account? Register"), + ), + ], + ), + ), + ); + } +} + +// --------------------------------------------------------------------------- +// Register +// --------------------------------------------------------------------------- + +class _RegisterScreen extends StatefulWidget { + const _RegisterScreen(); + + @override + State<_RegisterScreen> createState() => _RegisterScreenState(); +} + +class _RegisterScreenState extends State<_RegisterScreen> { + final _displayName = TextEditingController(); + final _email = TextEditingController(); + final _password = TextEditingController(); + bool _loading = false; + + Future _register() async { + setState(() => _loading = true); + try { + await Nhost.instance.auth.signUp( + email: _email.text, + password: _password.text, + displayName: _displayName.text.isEmpty ? null : _displayName.text, + ); + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Account created! Check your email to verify.'), + ), + ); + Navigator.of(context).pop(); + } on ApiException catch (e) { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Registration failed: ${e.responseBody}')), + ); + } finally { + if (mounted) setState(() => _loading = false); + } + } + + @override + void dispose() { + _displayName.dispose(); + _email.dispose(); + _password.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Create account')), + body: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextField( + controller: _displayName, + decoration: const InputDecoration( + labelText: 'Display name (optional)', + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 12), + TextField( + controller: _email, + decoration: const InputDecoration( + labelText: 'Email', + border: OutlineInputBorder(), + ), + keyboardType: TextInputType.emailAddress, + ), + const SizedBox(height: 12), + TextField( + controller: _password, + decoration: const InputDecoration( + labelText: 'Password', + border: OutlineInputBorder(), + ), + obscureText: true, + ), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: FilledButton( + onPressed: _loading ? null : _register, + child: _loading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Text('Create account'), + ), + ), + const SizedBox(height: 12), + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Already have an account? Sign in'), + ), + ], + ), + ), + ); + } +} + +// --------------------------------------------------------------------------- +// Forgot password — sends reset email +// --------------------------------------------------------------------------- + +class _ForgotPasswordScreen extends StatefulWidget { + const _ForgotPasswordScreen(); + + @override + State<_ForgotPasswordScreen> createState() => _ForgotPasswordScreenState(); +} + +class _ForgotPasswordScreenState extends State<_ForgotPasswordScreen> { + final _email = TextEditingController(); + bool _loading = false; + bool _sent = false; + + Future _send() async { + setState(() => _loading = true); + try { + await Nhost.instance.auth.resetPassword(email: _email.text); + if (!mounted) return; + setState(() => _sent = true); + } on ApiException catch (e) { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Failed: ${e.responseBody}')), + ); + } finally { + if (mounted) setState(() => _loading = false); + } + } + + @override + void dispose() { + _email.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Forgot password')), + body: Padding( + padding: const EdgeInsets.all(24), + child: _sent + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.mark_email_read_outlined, + size: 64, color: Colors.green), + const SizedBox(height: 16), + Text( + 'Password reset email sent to ${_email.text}.\nCheck your inbox.', + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Back to sign in'), + ), + ], + ) + : Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + "Enter your email and we'll send you a link to reset your password.", + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + TextField( + controller: _email, + decoration: const InputDecoration( + labelText: 'Email', + border: OutlineInputBorder(), + ), + keyboardType: TextInputType.emailAddress, + ), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: FilledButton( + onPressed: _loading ? null : _send, + child: _loading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Text('Send reset email'), + ), + ), + ], + ), + ), + ); + } +} + +// --------------------------------------------------------------------------- +// Home — demonstrates all P1 widgets +// --------------------------------------------------------------------------- + +class _HomeScreen extends StatelessWidget { + const _HomeScreen({required this.user}); + final User user; + + @override + Widget build(BuildContext context) { + final isAnonymous = user.isAnonymous; + + return Scaffold( + appBar: AppBar( + title: const Text('nhost_flutter demo'), + actions: [ + if (isAnonymous) + TextButton.icon( + icon: const Icon(Icons.upgrade), + label: const Text('Upgrade'), + onPressed: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => const _ConvertAnonymousScreen(), + ), + ), + ), + IconButton( + icon: const Icon(Icons.lock_outline), + tooltip: 'Change password', + onPressed: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => const _ChangePasswordScreen(), + ), + ), + ), + IconButton( + icon: const Icon(Icons.logout), + tooltip: 'Sign out', + onPressed: () => Nhost.instance.auth.signOut(), + ), + ], + ), + body: ListView( + padding: const EdgeInsets.all(16), + children: [ + if (isAnonymous) + _InfoBanner( + message: + 'You are signed in as a guest. Upgrade your account to save your data.', + action: TextButton( + onPressed: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => const _ConvertAnonymousScreen(), + ), + ), + child: const Text('Upgrade now'), + ), + ), + _Section( + title: 'NhostUserBuilder', + child: NhostUserBuilder( + builder: (_, u) => Text( + 'Hello, ${u.displayName.isNotEmpty ? u.displayName : (u.email ?? 'guest')}!', + style: Theme.of(context).textTheme.titleMedium, + ), + ), + ), + _Section( + title: 'NhostSignedIn / NhostSignedOut', + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + NhostSignedIn( + child: Chip( + avatar: const Icon(Icons.check_circle, color: Colors.green), + label: const Text('Signed in'), + ), + ), + NhostSignedOut( + child: Chip( + avatar: const Icon(Icons.cancel, color: Colors.red), + label: const Text('Signed out'), + ), + ), + ], + ), + ), + _Section( + title: 'NhostAuthStateBuilder (sealed switch)', + child: NhostAuthStateBuilder( + builder: (_, state) => switch (state) { + AuthStateLoading() => const Text('Loading…'), + AuthStateSignedOut() => const Text('Not signed in'), + AuthStateSignedIn(:final user) => + Text('Signed in as ${user.email ?? 'guest'}'), + }, + ), + ), + _Section( + title: 'authStateChanges stream', + child: StreamBuilder( + stream: Nhost.instance.auth.authStateChanges, + builder: (_, snap) { + final state = snap.data; + return Text(switch (state) { + null => 'Waiting for first event…', + AuthStateLoading() => 'Loading…', + AuthStateSignedOut() => 'Stream: signed out', + AuthStateSignedIn(:final user) => + 'Stream: signed in as ${user.email ?? 'guest'}', + }); + }, + ), + ), + _Section( + title: 'authStateListenable', + child: ValueListenableBuilder( + valueListenable: Nhost.instance.auth.authStateListenable, + builder: (_, state, __) => Text(switch (state) { + AuthStateLoading() => 'Listenable: loading…', + AuthStateSignedOut() => 'Listenable: signed out', + AuthStateSignedIn(:final user) => + 'Listenable: signed in as ${user.email ?? 'guest'}', + }), + ), + ), + ], + ), + ); + } +} + +// --------------------------------------------------------------------------- +// Convert anonymous account → full account +// --------------------------------------------------------------------------- + +class _ConvertAnonymousScreen extends StatefulWidget { + const _ConvertAnonymousScreen(); + + @override + State<_ConvertAnonymousScreen> createState() => + _ConvertAnonymousScreenState(); +} + +class _ConvertAnonymousScreenState extends State<_ConvertAnonymousScreen> { + final _email = TextEditingController(); + final _password = TextEditingController(); + bool _loading = false; + + Future _convert() async { + setState(() => _loading = true); + try { + await Nhost.instance.auth.deanonymizeUser( + DeanonymizeOptions( + signInMethod: DeanonymizeSignInMethod.emailPassword, + email: _email.text, + password: _password.text, + ), + ); + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Account upgraded! Check your email.')), + ); + Navigator.of(context).pop(); + } on ApiException catch (e) { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Upgrade failed: ${e.responseBody}')), + ); + } finally { + if (mounted) setState(() => _loading = false); + } + } + + @override + void dispose() { + _email.dispose(); + _password.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Upgrade account')), + body: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Convert your guest account to a permanent account.', + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + TextField( + controller: _email, + decoration: const InputDecoration( + labelText: 'Email', + border: OutlineInputBorder(), + ), + keyboardType: TextInputType.emailAddress, + ), + const SizedBox(height: 12), + TextField( + controller: _password, + decoration: const InputDecoration( + labelText: 'Password', + border: OutlineInputBorder(), + ), + obscureText: true, + ), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: FilledButton( + onPressed: _loading ? null : _convert, + child: _loading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Text('Upgrade account'), + ), + ), + ], + ), + ), + ); + } +} + +// --------------------------------------------------------------------------- +// Change password (for logged-in users) +// --------------------------------------------------------------------------- + +class _ChangePasswordScreen extends StatefulWidget { + const _ChangePasswordScreen(); + + @override + State<_ChangePasswordScreen> createState() => _ChangePasswordScreenState(); +} + +class _ChangePasswordScreenState extends State<_ChangePasswordScreen> { + final _newPassword = TextEditingController(); + bool _loading = false; + + Future _changePassword() async { + setState(() => _loading = true); + try { + await Nhost.instance.auth.changePassword( + newPassword: _newPassword.text, + ); + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Password changed successfully.')), + ); + Navigator.of(context).pop(); + } on ApiException catch (e) { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Failed: ${e.responseBody}')), + ); + } finally { + if (mounted) setState(() => _loading = false); + } + } + + @override + void dispose() { + _newPassword.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Change password')), + body: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextField( + controller: _newPassword, + decoration: const InputDecoration( + labelText: 'New password', + border: OutlineInputBorder(), + ), + obscureText: true, + ), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: FilledButton( + onPressed: _loading ? null : _changePassword, + child: _loading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Text('Change password'), + ), + ), + ], + ), + ), + ); + } +} + +// --------------------------------------------------------------------------- +// Shared widgets +// --------------------------------------------------------------------------- + +class _InfoBanner extends StatelessWidget { + const _InfoBanner({required this.message, required this.action}); + final String message; + final Widget action; + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.secondaryContainer, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + const Icon(Icons.info_outline), + const SizedBox(width: 8), + Expanded(child: Text(message)), + action, + ], + ), + ); + } +} + +class _Section extends StatelessWidget { + const _Section({required this.title, required this.child}); + final String title; + final Widget child; + + @override + Widget build(BuildContext context) { + return Card( + margin: const EdgeInsets.only(bottom: 12), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, + style: Theme.of(context) + .textTheme + .labelSmall + ?.copyWith(color: Colors.grey)), + const SizedBox(height: 8), + child, + ], + ), + ), + ); + } +} diff --git a/packages/nhost_flutter/example/pubspec.yaml b/packages/nhost_flutter/example/pubspec.yaml new file mode 100644 index 00000000..5056f35a --- /dev/null +++ b/packages/nhost_flutter/example/pubspec.yaml @@ -0,0 +1,21 @@ +name: nhost_flutter_example +description: Example app demonstrating nhost_flutter P1 Flutter Experience APIs. +publish_to: none +version: 1.0.0 + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" + + +dependencies: + flutter: + sdk: flutter + nhost_flutter: + path: ../ + cupertino_icons: + +dev_dependencies: + flutter_test: + sdk: flutter + lints: ^5.1.1 diff --git a/packages/nhost_flutter/example/pubspec_overrides.yaml b/packages/nhost_flutter/example/pubspec_overrides.yaml new file mode 100644 index 00000000..6576a886 --- /dev/null +++ b/packages/nhost_flutter/example/pubspec_overrides.yaml @@ -0,0 +1,22 @@ +# melos_managed_dependency_overrides: nhost_auth_dart,nhost_dart,nhost_flutter,nhost_flutter_auth,nhost_flutter_graphql,nhost_functions_dart,nhost_gql_links,nhost_graphql_adapter,nhost_sdk,nhost_storage_dart +dependency_overrides: + nhost_auth_dart: + path: ../../nhost_auth_dart + nhost_dart: + path: ../../nhost_dart + nhost_flutter: + path: .. + nhost_flutter_auth: + path: ../../nhost_flutter_auth + nhost_flutter_graphql: + path: ../../nhost_flutter_graphql + nhost_functions_dart: + path: ../../nhost_functions_dart + nhost_gql_links: + path: ../../nhost_gql_links + nhost_graphql_adapter: + path: ../../nhost_graphql_adapter + nhost_sdk: + path: ../../nhost_sdk + nhost_storage_dart: + path: ../../nhost_storage_dart diff --git a/packages/nhost_flutter/lib/nhost_flutter.dart b/packages/nhost_flutter/lib/nhost_flutter.dart new file mode 100644 index 00000000..9df092f6 --- /dev/null +++ b/packages/nhost_flutter/lib/nhost_flutter.dart @@ -0,0 +1,13 @@ +export 'package:nhost_dart/nhost_dart.dart'; +export 'package:nhost_flutter_auth/nhost_flutter_auth.dart'; +export 'package:nhost_flutter_graphql/nhost_flutter_graphql.dart'; + +export 'src/auth_state.dart'; +export 'src/secure_auth_store.dart'; +export 'src/auth_client_extensions.dart'; +export 'src/nhost.dart'; +export 'src/widgets/nhost_auth_gate.dart'; +export 'src/widgets/nhost_auth_state_builder.dart'; +export 'src/widgets/nhost_signed_in.dart'; +export 'src/widgets/nhost_signed_out.dart'; +export 'src/widgets/nhost_user_builder.dart'; diff --git a/packages/nhost_flutter/lib/src/auth_client_extensions.dart b/packages/nhost_flutter/lib/src/auth_client_extensions.dart new file mode 100644 index 00000000..4ae83628 --- /dev/null +++ b/packages/nhost_flutter/lib/src/auth_client_extensions.dart @@ -0,0 +1,55 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:nhost_dart/nhost_dart.dart'; + +import 'auth_state.dart'; + +extension NhostAuthClientFlutterX on NhostAuthClient { + static final _state = Expando<_AuthFlutterState>(); + + _AuthFlutterState get _flutterState => + _state[this] ??= _AuthFlutterState(this); + + /// A broadcast [Stream] that emits an [AuthState] on every authentication + /// state change. + Stream get authStateChanges => _flutterState.stream; + + /// A [ValueListenable] that always holds the current [AuthState]. + ValueListenable get authStateListenable => _flutterState.notifier; +} + +class _AuthFlutterState { + _AuthFlutterState(NhostAuthClient auth) : _auth = auth { + _notifier = ValueNotifier(_mapCurrentState()); + _controller = StreamController.broadcast(); + _unsubscribe = auth.addAuthStateChangedCallback((_) { + final mapped = _mapCurrentState(); + _notifier.value = mapped; + _controller.add(mapped); + }); + } + + final NhostAuthClient _auth; + late final StreamController _controller; + late final ValueNotifier _notifier; + late final UnsubscribeDelegate _unsubscribe; + + Stream get stream => _controller.stream; + ValueListenable get notifier => _notifier; + + AuthState _mapCurrentState() => switch (_auth.authenticationState) { + AuthenticationState.inProgress => const AuthStateLoading(), + AuthenticationState.signedOut => const AuthStateSignedOut(), + AuthenticationState.signedIn => AuthStateSignedIn( + user: _auth.currentUser!, + session: _auth.userSession.session!, + ), + }; + + void dispose() { + _unsubscribe(); + _controller.close(); + _notifier.dispose(); + } +} diff --git a/packages/nhost_flutter/lib/src/auth_state.dart b/packages/nhost_flutter/lib/src/auth_state.dart new file mode 100644 index 00000000..ba7db49d --- /dev/null +++ b/packages/nhost_flutter/lib/src/auth_state.dart @@ -0,0 +1,19 @@ +import 'package:nhost_sdk/nhost_sdk.dart'; + +sealed class AuthState { + const AuthState(); +} + +final class AuthStateLoading extends AuthState { + const AuthStateLoading(); +} + +final class AuthStateSignedOut extends AuthState { + const AuthStateSignedOut(); +} + +final class AuthStateSignedIn extends AuthState { + const AuthStateSignedIn({required this.user, required this.session}); + final User user; + final Session session; +} diff --git a/packages/nhost_flutter/lib/src/nhost.dart b/packages/nhost_flutter/lib/src/nhost.dart new file mode 100644 index 00000000..d7f387b2 --- /dev/null +++ b/packages/nhost_flutter/lib/src/nhost.dart @@ -0,0 +1,109 @@ +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart' as http; +import 'package:nhost_dart/nhost_dart.dart'; + +import 'secure_auth_store.dart'; + +/// Entry point for the Nhost Flutter SDK. +/// +/// Call [initialize] once in `main()` before [runApp], then access the client +/// anywhere via [instance]. +/// +/// ```dart +/// void main() async { +/// WidgetsFlutterBinding.ensureInitialized(); +/// await Nhost.initialize( +/// subdomain: Subdomain(subdomain: 'xxx', region: 'eu-central-1'), +/// ); +/// runApp(const MyApp()); +/// } +/// ``` +class Nhost { + Nhost._(); + + static NhostClient? _instance; + + /// Initializes the Nhost SDK and returns the configured [NhostClient]. + /// + /// Exactly one of [subdomain] or [serviceUrls] must be provided. + /// + /// Tokens are persisted via [SecureAuthStore] by default. Pass [authStore] + /// to override. Pass `restoreSession: false` to skip the automatic restore. + /// + /// Throws [StateError] if called more than once without [reset]. + static Future initialize({ + Subdomain? subdomain, + ServiceUrls? serviceUrls, + AuthStore? authStore, + Duration? tokenRefreshInterval, + http.Client? httpClientOverride, + bool restoreSession = true, + }) async { + if (_instance != null) { + throw StateError( + 'Nhost is already initialized. Call Nhost.reset() first.', + ); + } + + _instance = NhostClient( + subdomain: subdomain, + serviceUrls: serviceUrls, + authStore: authStore ?? const SecureAuthStore(), + tokenRefreshInterval: tokenRefreshInterval, + httpClientOverride: httpClientOverride, + ); + + if (restoreSession) { + await _instance!.auth + .signInWithStoredCredentials() + .catchError((_) => AuthResponse(session: null)); + } + + return _instance!; + } + + /// Convenience initializer for local Nhost development. + /// + /// Defaults to `http://localhost:1337` — the standard `nhost dev` port. + /// On Android emulators pass `authUrl: 'http://10.0.2.2:1337/v1/auth'` etc. + static Future local({ + String authUrl = 'http://localhost:1337/v1/auth', + String storageUrl = 'http://localhost:1337/v1/storage', + String functionsUrl = 'http://localhost:1337/v1/functions', + String graphqlUrl = 'http://localhost:1337/v1/graphql', + AuthStore? authStore, + http.Client? httpClientOverride, + bool restoreSession = true, + }) => + initialize( + serviceUrls: ServiceUrls( + authUrl: authUrl, + storageUrl: storageUrl, + functionsUrl: functionsUrl, + graphqlUrl: graphqlUrl, + ), + authStore: authStore, + httpClientOverride: httpClientOverride, + restoreSession: restoreSession, + ); + + /// The initialized [NhostClient]. + /// + /// Throws [StateError] if accessed before [initialize] is called. + static NhostClient get instance { + final inst = _instance; + if (inst == null) { + throw StateError( + 'Nhost has not been initialized. Call Nhost.initialize() first.', + ); + } + return inst; + } + + /// Clears the singleton. Intended for use in tests only. + @visibleForTesting + static void reset() { + _instance?.close(); + _instance = null; + } +} diff --git a/packages/nhost_flutter/lib/src/secure_auth_store.dart b/packages/nhost_flutter/lib/src/secure_auth_store.dart new file mode 100644 index 00000000..b9cd235d --- /dev/null +++ b/packages/nhost_flutter/lib/src/secure_auth_store.dart @@ -0,0 +1,39 @@ +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:nhost_sdk/nhost_sdk.dart'; + +/// An [AuthStore] backed by `flutter_secure_storage`. +/// +/// Used by default in [Nhost.initialize]. Swap it out by passing a custom +/// [AuthStore] to the `authStore` parameter. +/// +/// Secure defaults: +/// - Android: `encryptedSharedPreferences: true` +/// - iOS: `KeychainAccessibility.first_unlock` +class SecureAuthStore implements AuthStore { + const SecureAuthStore({ + this.androidOptions = const AndroidOptions( + encryptedSharedPreferences: true, + ), + this.iOSOptions = const IOSOptions( + accessibility: KeychainAccessibility.first_unlock, + ), + }); + + final AndroidOptions androidOptions; + final IOSOptions iOSOptions; + + FlutterSecureStorage get _storage => FlutterSecureStorage( + aOptions: androidOptions, + iOptions: iOSOptions, + ); + + @override + Future getString(String key) => _storage.read(key: key); + + @override + Future setString(String key, String value) => + _storage.write(key: key, value: value); + + @override + Future removeItem(String key) => _storage.delete(key: key); +} diff --git a/packages/nhost_flutter/lib/src/widgets/nhost_auth_gate.dart b/packages/nhost_flutter/lib/src/widgets/nhost_auth_gate.dart new file mode 100644 index 00000000..88850a09 --- /dev/null +++ b/packages/nhost_flutter/lib/src/widgets/nhost_auth_gate.dart @@ -0,0 +1,45 @@ +import 'package:flutter/widgets.dart'; +import 'package:nhost_flutter_auth/nhost_flutter_auth.dart'; + +/// Routes to different widgets based on the current authentication state. +/// +/// Must be a descendant of [NhostAuthProvider]. +/// +/// ```dart +/// NhostAuthGate( +/// loading: (context) => const SplashScreen(), +/// signedOut: (context) => const LoginScreen(), +/// signedIn: (context, user, session) => HomeScreen(user: user), +/// ) +/// ``` +class NhostAuthGate extends StatelessWidget { + const NhostAuthGate({ + super.key, + required this.signedOut, + required this.signedIn, + this.loading, + }); + + /// Builder shown while authentication state is being determined. + /// Defaults to [SizedBox.shrink]. + final WidgetBuilder? loading; + + /// Builder shown when no user is authenticated. + final WidgetBuilder signedOut; + + /// Builder shown when a user is authenticated. + final Widget Function(BuildContext context, User user, Session session) + signedIn; + + @override + Widget build(BuildContext context) { + final auth = NhostAuthProvider.of(context)!; + return switch (auth.authenticationState) { + AuthenticationState.inProgress => + loading?.call(context) ?? const SizedBox.shrink(), + AuthenticationState.signedOut => signedOut(context), + AuthenticationState.signedIn => + signedIn(context, auth.currentUser!, auth.userSession.session!), + }; + } +} diff --git a/packages/nhost_flutter/lib/src/widgets/nhost_auth_state_builder.dart b/packages/nhost_flutter/lib/src/widgets/nhost_auth_state_builder.dart new file mode 100644 index 00000000..8e819e64 --- /dev/null +++ b/packages/nhost_flutter/lib/src/widgets/nhost_auth_state_builder.dart @@ -0,0 +1,40 @@ +import 'package:flutter/widgets.dart'; +import 'package:nhost_flutter_auth/nhost_flutter_auth.dart'; + +import '../auth_state.dart'; + +/// Builds a widget tree based on the current [AuthState]. +/// +/// The [builder] receives a sealed [AuthState] — use an exhaustive `switch` +/// with no `default` case: +/// +/// ```dart +/// NhostAuthStateBuilder( +/// builder: (context, state) => switch (state) { +/// AuthStateLoading() => const SplashScreen(), +/// AuthStateSignedOut() => const LoginScreen(), +/// AuthStateSignedIn(:final user) => HomeScreen(user: user), +/// }, +/// ) +/// ``` +/// +/// Must be a descendant of [NhostAuthProvider]. +class NhostAuthStateBuilder extends StatelessWidget { + const NhostAuthStateBuilder({super.key, required this.builder}); + + final Widget Function(BuildContext context, AuthState state) builder; + + @override + Widget build(BuildContext context) { + final auth = NhostAuthProvider.of(context)!; + final state = switch (auth.authenticationState) { + AuthenticationState.inProgress => const AuthStateLoading(), + AuthenticationState.signedOut => const AuthStateSignedOut(), + AuthenticationState.signedIn => AuthStateSignedIn( + user: auth.currentUser!, + session: auth.userSession.session!, + ), + }; + return builder(context, state); + } +} diff --git a/packages/nhost_flutter/lib/src/widgets/nhost_signed_in.dart b/packages/nhost_flutter/lib/src/widgets/nhost_signed_in.dart new file mode 100644 index 00000000..5f9e2432 --- /dev/null +++ b/packages/nhost_flutter/lib/src/widgets/nhost_signed_in.dart @@ -0,0 +1,22 @@ +import 'package:flutter/widgets.dart'; +import 'package:nhost_flutter_auth/nhost_flutter_auth.dart'; + +/// Shows [child] only when a user is authenticated. Renders [orElse] +/// (default: [SizedBox.shrink]) when signed out or loading. +/// +/// Must be a descendant of [NhostAuthProvider]. +class NhostSignedIn extends StatelessWidget { + const NhostSignedIn({super.key, required this.child, this.orElse}); + + final Widget child; + final Widget? orElse; + + @override + Widget build(BuildContext context) { + final auth = NhostAuthProvider.of(context)!; + if (auth.authenticationState == AuthenticationState.signedIn) { + return child; + } + return orElse ?? const SizedBox.shrink(); + } +} diff --git a/packages/nhost_flutter/lib/src/widgets/nhost_signed_out.dart b/packages/nhost_flutter/lib/src/widgets/nhost_signed_out.dart new file mode 100644 index 00000000..2a46394f --- /dev/null +++ b/packages/nhost_flutter/lib/src/widgets/nhost_signed_out.dart @@ -0,0 +1,22 @@ +import 'package:flutter/widgets.dart'; +import 'package:nhost_flutter_auth/nhost_flutter_auth.dart'; + +/// Shows [child] only when no user is authenticated. Renders [orElse] +/// (default: [SizedBox.shrink]) when signed in or loading. +/// +/// Must be a descendant of [NhostAuthProvider]. +class NhostSignedOut extends StatelessWidget { + const NhostSignedOut({super.key, required this.child, this.orElse}); + + final Widget child; + final Widget? orElse; + + @override + Widget build(BuildContext context) { + final auth = NhostAuthProvider.of(context)!; + if (auth.authenticationState == AuthenticationState.signedOut) { + return child; + } + return orElse ?? const SizedBox.shrink(); + } +} diff --git a/packages/nhost_flutter/lib/src/widgets/nhost_user_builder.dart b/packages/nhost_flutter/lib/src/widgets/nhost_user_builder.dart new file mode 100644 index 00000000..31ff8c12 --- /dev/null +++ b/packages/nhost_flutter/lib/src/widgets/nhost_user_builder.dart @@ -0,0 +1,26 @@ +import 'package:flutter/widgets.dart'; +import 'package:nhost_flutter_auth/nhost_flutter_auth.dart'; + +/// Builds a widget using the currently authenticated [User]. +/// +/// Renders [orElse] (default: [SizedBox.shrink]) when the user is not signed +/// in or authentication is still loading. +/// +/// Must be a descendant of [NhostAuthProvider]. +class NhostUserBuilder extends StatelessWidget { + const NhostUserBuilder({super.key, required this.builder, this.orElse}); + + final Widget Function(BuildContext context, User user) builder; + final WidgetBuilder? orElse; + + @override + Widget build(BuildContext context) { + final auth = NhostAuthProvider.of(context)!; + final user = auth.currentUser; + if (auth.authenticationState == AuthenticationState.signedIn && + user != null) { + return builder(context, user); + } + return orElse?.call(context) ?? const SizedBox.shrink(); + } +} diff --git a/packages/nhost_flutter/pubspec.yaml b/packages/nhost_flutter/pubspec.yaml new file mode 100644 index 00000000..6f0dce54 --- /dev/null +++ b/packages/nhost_flutter/pubspec.yaml @@ -0,0 +1,28 @@ +name: nhost_flutter +description: > + First-class Flutter package for Nhost. Includes auth, storage, GraphQL, + secure persistence by default, and modern sealed auth state APIs. +version: 1.0.0 +homepage: https://nhost.io +repository: https://github.com/nhost/nhost-dart/tree/main/packages/nhost_flutter +issue_tracker: https://github.com/nhost/nhost-dart/issues + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" + +dependencies: + flutter: + sdk: flutter + nhost_dart: ^2.2.0 + nhost_flutter_auth: ^4.2.1 + nhost_flutter_graphql: ^3.1.2 + nhost_sdk: ^5.8.0 + flutter_secure_storage: ^9.0.0 + http: ^1.1.0 + +dev_dependencies: + flutter_test: + sdk: flutter + lints: ^5.1.1 + mockito: ^5.4.5 diff --git a/packages/nhost_flutter/pubspec_overrides.yaml b/packages/nhost_flutter/pubspec_overrides.yaml new file mode 100644 index 00000000..8412bb6d --- /dev/null +++ b/packages/nhost_flutter/pubspec_overrides.yaml @@ -0,0 +1,20 @@ +# melos_managed_dependency_overrides: nhost_auth_dart,nhost_dart,nhost_flutter_auth,nhost_flutter_graphql,nhost_functions_dart,nhost_gql_links,nhost_graphql_adapter,nhost_sdk,nhost_storage_dart +dependency_overrides: + nhost_auth_dart: + path: ../nhost_auth_dart + nhost_dart: + path: ../nhost_dart + nhost_flutter_auth: + path: ../nhost_flutter_auth + nhost_flutter_graphql: + path: ../nhost_flutter_graphql + nhost_functions_dart: + path: ../nhost_functions_dart + nhost_gql_links: + path: ../nhost_gql_links + nhost_graphql_adapter: + path: ../nhost_graphql_adapter + nhost_sdk: + path: ../nhost_sdk + nhost_storage_dart: + path: ../nhost_storage_dart diff --git a/packages/nhost_flutter/test/auth_client_extensions_test.dart b/packages/nhost_flutter/test/auth_client_extensions_test.dart new file mode 100644 index 00000000..fe62ccea --- /dev/null +++ b/packages/nhost_flutter/test/auth_client_extensions_test.dart @@ -0,0 +1,72 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:nhost_flutter/nhost_flutter.dart'; + +import 'helpers/fake_auth_client.dart'; + +void main() { + group('NhostAuthClientFlutterX', () { + late FakeAuthClient auth; + + setUp(() { + auth = FakeAuthClient(AuthenticationState.signedOut); + }); + + group('authStateChanges stream', () { + test('emits AuthStateSignedIn when client fires signedIn callback', + () async { + final states = []; + final sub = auth.authStateChanges.listen(states.add); + addTearDown(sub.cancel); + + auth.simulateSignIn(makeUser(), makeSession()); + + await Future.microtask(() {}); + expect(states, [isA()]); + expect((states.first as AuthStateSignedIn).user.email, + 'test@example.com'); + }); + + test('emits AuthStateSignedOut when client fires signedOut callback', + () async { + auth = FakeAuthClient(AuthenticationState.signedIn) + ..simulateSignIn(makeUser(), makeSession()); + + final states = []; + final sub = auth.authStateChanges.listen(states.add); + addTearDown(sub.cancel); + + auth.simulateSignOut(); + + await Future.microtask(() {}); + expect(states, [isA()]); + }); + + test('is a broadcast stream — multiple listeners allowed', () async { + final sub1 = auth.authStateChanges.listen((_) {}); + final sub2 = auth.authStateChanges.listen((_) {}); + addTearDown(sub1.cancel); + addTearDown(sub2.cancel); + expect(() => auth.authStateChanges, returnsNormally); + }); + }); + + group('authStateListenable', () { + test('initial value reflects signedOut state', () { + expect(auth.authStateListenable.value, isA()); + }); + + test('notifies listeners on state change', () { + final notifier = + auth.authStateListenable as ValueNotifier; + var notified = false; + notifier.addListener(() => notified = true); + + auth.simulateSignIn(makeUser(), makeSession()); + + expect(notified, isTrue); + expect(notifier.value, isA()); + }); + }); + }); +} diff --git a/packages/nhost_flutter/test/auth_state_test.dart b/packages/nhost_flutter/test/auth_state_test.dart new file mode 100644 index 00000000..cb7cca78 --- /dev/null +++ b/packages/nhost_flutter/test/auth_state_test.dart @@ -0,0 +1,58 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:nhost_flutter/nhost_flutter.dart'; +import 'package:nhost_sdk/nhost_sdk.dart'; + +void main() { + group('AuthState sealed classes', () { + test('AuthStateLoading is an AuthState', () { + const AuthState state = AuthStateLoading(); + expect(state, isA()); + }); + + test('AuthStateSignedOut is an AuthState', () { + const AuthState state = AuthStateSignedOut(); + expect(state, isA()); + }); + + test('AuthStateSignedIn carries user and session', () { + final user = makeTestUser(); + final session = makeTestSession(); + final AuthState state = AuthStateSignedIn(user: user, session: session); + + expect(state, isA()); + expect((state as AuthStateSignedIn).user.email, 'ada@example.com'); + expect(state.session.accessToken, 'tok'); + }); + + test('exhaustive switch compiles without default', () { + const AuthState state = AuthStateLoading(); + final label = switch (state) { + AuthStateLoading() => 'loading', + AuthStateSignedOut() => 'out', + AuthStateSignedIn() => 'in', + }; + expect(label, 'loading'); + }); + }); +} + +User makeTestUser() => User( + id: 'u1', + displayName: 'Ada', + locale: 'en', + createdAt: DateTime.utc(2024), + isAnonymous: false, + defaultRole: 'user', + roles: const ['user'], + emailVerified: true, + phoneNumber: '', + phoneNumberVerified: false, + email: 'ada@example.com', + ); + +Session makeTestSession() => Session( + accessToken: 'tok', + accessTokenExpiresIn: const Duration(seconds: 900), + refreshToken: 'ref', + user: makeTestUser(), + ); diff --git a/packages/nhost_flutter/test/helpers/fake_auth_client.dart b/packages/nhost_flutter/test/helpers/fake_auth_client.dart new file mode 100644 index 00000000..eb853673 --- /dev/null +++ b/packages/nhost_flutter/test/helpers/fake_auth_client.dart @@ -0,0 +1,243 @@ +import 'package:nhost_dart/nhost_dart.dart'; +import 'package:nhost_sdk/nhost_sdk.dart'; + +/// Minimal fake that drives auth state changes without a real server. +class FakeAuthClient implements NhostAuthClient { + FakeAuthClient(this._state); + + AuthenticationState _state; + User? _user; + // Cached UserSession — built once in simulateSignIn to avoid repeated + // JwtDecoder.decode calls that would fail on every userSession getter access. + UserSession _cachedSession = UserSession(); + + final List _listeners = []; + + void simulateSignIn(User user, Session session) { + _user = user; + _cachedSession = UserSession()..session = session; + _state = AuthenticationState.signedIn; + for (final l in List.of(_listeners)) { + l(AuthenticationState.signedIn); + } + } + + void simulateSignOut() { + _user = null; + _cachedSession = UserSession(); + _state = AuthenticationState.signedOut; + for (final l in List.of(_listeners)) { + l(AuthenticationState.signedOut); + } + } + + @override + AuthenticationState get authenticationState => _state; + + @override + User? get currentUser => _user; + + @override + UserSession get userSession => _cachedSession; + + @override + UnsubscribeDelegate addAuthStateChangedCallback( + AuthStateChangedCallback callback) { + _listeners.add(callback); + return () => _listeners.remove(callback); + } + + // --- unused stubs --- + @override + String? get accessToken => null; + @override + String? getClaim(String c) => null; + @override + void close() {} + @override + UnsubscribeDelegate addTokenChangedCallback(TokenChangedCallback cb) => + () {}; + @override + UnsubscribeDelegate addSessionRefreshFailedCallback( + SessionRefreshFailedCallback cb) => + () {}; + @override + Future signUp( + {required String email, + required String password, + String? locale, + String? defaultRole, + Map? metadata, + List? roles, + String? displayName, + String? redirectTo, + String? turnstileResponse}) => + throw UnimplementedError(); + @override + Future signInEmailPassword( + {required String email, required String password}) => + throw UnimplementedError(); + @override + Future signInIdToken( + {required String provider, + required String idToken, + String? nonce, + String? locale, + String? defaultRole, + Map? metadata, + List? roles, + String? displayName, + String? redirectTo}) => + throw UnimplementedError(); + @override + Future linkIdToken( + {required String provider, + required String idToken, + String? nonce}) => + throw UnimplementedError(); + @override + Future signInWithEmailPasswordless( + {required String email, + String? locale, + String? defaultRole, + Map? metadata, + List? roles, + String? displayName, + String? redirectTo}) => + throw UnimplementedError(); + @override + Future signInAnonymous(String? displayName, String? locale, + Map? metadata) => + throw UnimplementedError(); + @override + Future deanonymizeUser(DeanonymizeOptions options) => + throw UnimplementedError(); + @override + Future signInWithSmsPasswordless( + {required String phoneNumber, + String? locale, + String? defaultRole, + Map? metadata, + List? roles, + String? displayName, + String? redirectTo}) => + throw UnimplementedError(); + @override + Future completeSmsPasswordlessSignIn( + String phoneNumber, String otp) => + throw UnimplementedError(); + @override + Future signInEmailOTP( + {required String email, + String? locale, + String? defaultRole, + Map? metadata, + List? roles, + String? displayName, + String? redirectTo}) => + throw UnimplementedError(); + @override + Future verifyEmailOTP( + {required String email, required String otp}) => + throw UnimplementedError(); + @override + Future signInWithStoredCredentials() => + throw UnimplementedError(); + @override + Future signInWithRefreshToken(String refreshToken) => + throw UnimplementedError(); + @override + Future signOut({bool all = false}) => + throw UnimplementedError(); + @override + Future sendVerificationEmail( + {required String email, String? redirectTo}) => + throw UnimplementedError(); + @override + Future changeEmail(String newEmail) => throw UnimplementedError(); + @override + Future changePassword( + {required String newPassword, String? ticket}) => + throw UnimplementedError(); + @override + Future resetPassword({required String email, String? redirectTo}) => + throw UnimplementedError(); + @override + Future generateMfa() => throw UnimplementedError(); + @override + Future enableMfa(String totp) => throw UnimplementedError(); + @override + Future disableMfa(String code) => throw UnimplementedError(); + @override + Future completeMfaSignIn( + {required String otp, required String ticket}) => + throw UnimplementedError(); + @override + Future completeOAuthProviderSignIn(Uri redirectUrl) => + throw UnimplementedError(); + @override + Future signInWithPat(String pat) => throw UnimplementedError(); + @override + Future fetchUser() => throw UnimplementedError(); + @override + Future verifyToken(String accessToken) => throw UnimplementedError(); + @override + Future> signInWithWebAuthn() => + throw UnimplementedError(); + @override + Future verifyWebAuthnSignIn( + Map assertionResponse) => + throw UnimplementedError(); + @override + Future> signUpWithWebAuthn({String? email}) => + throw UnimplementedError(); + @override + Future verifyWebAuthnSignUp( + Map attestationResponse) => + throw UnimplementedError(); + @override + Future> addWebAuthnCredential() => + throw UnimplementedError(); + @override + Future verifyAddWebAuthnCredential( + Map attestationResponse) => + throw UnimplementedError(); + @override + Future> elevateWithWebAuthn() => + throw UnimplementedError(); + @override + Future verifyWebAuthnElevation( + Map assertionResponse) => + throw UnimplementedError(); + @override + Future setSession(Session session) => throw UnimplementedError(); + @override + Future clearSession() => throw UnimplementedError(); +} + +User makeUser({String email = 'test@example.com'}) => User( + id: 'u1', + displayName: 'Test', + locale: 'en', + createdAt: DateTime.utc(2024), + isAnonymous: false, + defaultRole: 'user', + roles: const ['user'], + emailVerified: true, + phoneNumber: '', + phoneNumberVerified: false, + email: email, + ); + +// A valid JWT is required because UserSession.session= calls JwtDecoder.decode. +const _validJwt = + 'eyJhbGciOiJIUzI1NiJ9' + '.eyJodHRwczovL2hhc3VyYS5pby9qd3QvY2xhaW1zIjp7IngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsidXNlciJdLCJ4LWhhc3VyYS1kZWZhdWx0LXJvbGUiOiJ1c2VyIiwieC1oYXN1cmEtdXNlci1pZCI6InUxIn0sInN1YiI6InUxIiwiaWF0IjoxNjAwMDAwMDAwLCJleHAiOjk5OTk5OTk5OTl9' + '.signature'; + +Session makeSession() => Session( + accessToken: _validJwt, + accessTokenExpiresIn: const Duration(seconds: 900), + refreshToken: 'ref', + user: makeUser(), + ); diff --git a/packages/nhost_flutter/test/nhost_singleton_test.dart b/packages/nhost_flutter/test/nhost_singleton_test.dart new file mode 100644 index 00000000..a9222e8e --- /dev/null +++ b/packages/nhost_flutter/test/nhost_singleton_test.dart @@ -0,0 +1,76 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:nhost_flutter/nhost_flutter.dart'; + +/// Minimal in-memory [AuthStore] for tests — avoids a flutter_secure_storage +/// platform channel during unit tests. +class _MemoryAuthStore implements AuthStore { + final _store = {}; + @override + Future getString(String key) async => _store[key]; + @override + Future setString(String key, String value) async => _store[key] = value; + @override + Future removeItem(String key) async => _store.remove(key); +} + +void main() { + tearDown(() => Nhost.reset()); + + group('Nhost singleton', () { + test('initialize returns an NhostClient', () async { + final client = await Nhost.initialize( + subdomain: Subdomain(subdomain: 'test', region: 'us-east-1'), + authStore: _MemoryAuthStore(), + restoreSession: false, + ); + expect(client, isA()); + }); + + test('instance returns the same client after initialize', () async { + final client = await Nhost.initialize( + subdomain: Subdomain(subdomain: 'test', region: 'us-east-1'), + authStore: _MemoryAuthStore(), + restoreSession: false, + ); + expect(Nhost.instance, same(client)); + }); + + test('calling initialize twice throws StateError', () async { + await Nhost.initialize( + subdomain: Subdomain(subdomain: 'test', region: 'us-east-1'), + authStore: _MemoryAuthStore(), + restoreSession: false, + ); + expect( + () => Nhost.initialize( + subdomain: Subdomain(subdomain: 'test', region: 'us-east-1'), + authStore: _MemoryAuthStore(), + restoreSession: false, + ), + throwsA(isA()), + ); + }); + + test('accessing instance before initialize throws StateError', () { + expect(() => Nhost.instance, throwsA(isA())); + }); + + test('reset clears the singleton', () async { + await Nhost.initialize( + subdomain: Subdomain(subdomain: 'test', region: 'us-east-1'), + authStore: _MemoryAuthStore(), + restoreSession: false, + ); + Nhost.reset(); + expect(() => Nhost.instance, throwsA(isA())); + }); + + test('local() uses localhost service URLs', () async { + final client = await Nhost.local( + authStore: _MemoryAuthStore(), + restoreSession: false, + ); + expect(client.serviceUrls?.authUrl, contains('localhost:1337')); + }); + }); +} diff --git a/packages/nhost_flutter/test/secure_auth_store_test.dart b/packages/nhost_flutter/test/secure_auth_store_test.dart new file mode 100644 index 00000000..4c401728 --- /dev/null +++ b/packages/nhost_flutter/test/secure_auth_store_test.dart @@ -0,0 +1,17 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:nhost_flutter/nhost_flutter.dart'; + +void main() { + group('SecureAuthStore', () { + test('implements AuthStore', () { + const AuthStore store = SecureAuthStore(); + expect(store, isA()); + }); + + test('default instance is const-constructible', () { + const a = SecureAuthStore(); + const b = SecureAuthStore(); + expect(identical(a, b), isTrue); + }); + }); +} diff --git a/packages/nhost_flutter/test/widgets/nhost_auth_gate_test.dart b/packages/nhost_flutter/test/widgets/nhost_auth_gate_test.dart new file mode 100644 index 00000000..a7b60bd4 --- /dev/null +++ b/packages/nhost_flutter/test/widgets/nhost_auth_gate_test.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:nhost_flutter/nhost_flutter.dart'; + +import '../helpers/fake_auth_client.dart'; + +Widget _wrap(FakeAuthClient auth, Widget child) => MaterialApp( + home: NhostAuthProvider(auth: auth, child: child), + ); + +void main() { + group('NhostAuthGate', () { + testWidgets('shows signedOut widget when signed out', (tester) async { + final auth = FakeAuthClient(AuthenticationState.signedOut); + await tester.pumpWidget(_wrap( + auth, + NhostAuthGate( + signedOut: (_) => const Text('login'), + signedIn: (_, __, ___) => const Text('home'), + ), + )); + expect(find.text('login'), findsOneWidget); + expect(find.text('home'), findsNothing); + }); + + testWidgets('shows signedIn widget with user when signed in', + (tester) async { + final auth = FakeAuthClient(AuthenticationState.signedIn) + ..simulateSignIn(makeUser(), makeSession()); + await tester.pumpWidget(_wrap( + auth, + NhostAuthGate( + signedOut: (_) => const Text('login'), + signedIn: (_, user, __) => Text('home:${user.email}'), + ), + )); + expect(find.text('home:test@example.com'), findsOneWidget); + }); + + testWidgets('shows SizedBox.shrink when loading and no loading builder', + (tester) async { + final auth = FakeAuthClient(AuthenticationState.inProgress); + await tester.pumpWidget(_wrap( + auth, + NhostAuthGate( + signedOut: (_) => const Text('login'), + signedIn: (_, __, ___) => const Text('home'), + ), + )); + expect(find.text('login'), findsNothing); + expect(find.text('home'), findsNothing); + }); + + testWidgets('shows loading widget when provided and inProgress', + (tester) async { + final auth = FakeAuthClient(AuthenticationState.inProgress); + await tester.pumpWidget(_wrap( + auth, + NhostAuthGate( + loading: (_) => const Text('loading'), + signedOut: (_) => const Text('login'), + signedIn: (_, __, ___) => const Text('home'), + ), + )); + expect(find.text('loading'), findsOneWidget); + }); + + testWidgets('switches widget when auth state changes', (tester) async { + final auth = FakeAuthClient(AuthenticationState.signedOut); + await tester.pumpWidget(_wrap( + auth, + NhostAuthGate( + signedOut: (_) => const Text('login'), + signedIn: (_, __, ___) => const Text('home'), + ), + )); + expect(find.text('login'), findsOneWidget); + + auth.simulateSignIn(makeUser(), makeSession()); + await tester.pump(); + + expect(find.text('home'), findsOneWidget); + expect(find.text('login'), findsNothing); + }); + }); +} diff --git a/packages/nhost_flutter/test/widgets/nhost_auth_state_builder_test.dart b/packages/nhost_flutter/test/widgets/nhost_auth_state_builder_test.dart new file mode 100644 index 00000000..80e74669 --- /dev/null +++ b/packages/nhost_flutter/test/widgets/nhost_auth_state_builder_test.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:nhost_flutter/nhost_flutter.dart'; + +import '../helpers/fake_auth_client.dart'; + +Widget _wrap(FakeAuthClient auth, Widget child) => + MaterialApp(home: NhostAuthProvider(auth: auth, child: child)); + +void main() { + group('NhostAuthStateBuilder', () { + testWidgets('passes AuthStateSignedOut when signed out', (tester) async { + final auth = FakeAuthClient(AuthenticationState.signedOut); + AuthState? received; + await tester.pumpWidget(_wrap( + auth, + NhostAuthStateBuilder(builder: (_, state) { + received = state; + return const SizedBox(); + }), + )); + expect(received, isA()); + }); + + testWidgets('passes AuthStateLoading when inProgress', (tester) async { + final auth = FakeAuthClient(AuthenticationState.inProgress); + AuthState? received; + await tester.pumpWidget(_wrap( + auth, + NhostAuthStateBuilder(builder: (_, state) { + received = state; + return const SizedBox(); + }), + )); + expect(received, isA()); + }); + + testWidgets('passes AuthStateSignedIn with user when signed in', + (tester) async { + final auth = FakeAuthClient(AuthenticationState.signedIn) + ..simulateSignIn(makeUser(email: 'x@y.com'), makeSession()); + AuthState? received; + await tester.pumpWidget(_wrap( + auth, + NhostAuthStateBuilder(builder: (_, state) { + received = state; + return const SizedBox(); + }), + )); + expect(received, isA()); + expect((received as AuthStateSignedIn).user.email, 'x@y.com'); + }); + + testWidgets('rebuilds when auth state changes', (tester) async { + final auth = FakeAuthClient(AuthenticationState.signedOut); + final states = []; + await tester.pumpWidget(_wrap( + auth, + NhostAuthStateBuilder(builder: (_, state) { + states.add(state); + return const SizedBox(); + }), + )); + + auth.simulateSignIn(makeUser(), makeSession()); + await tester.pump(); + + expect(states.length, 2); + expect(states[0], isA()); + expect(states[1], isA()); + }); + }); +} diff --git a/packages/nhost_flutter/test/widgets/nhost_signed_in_out_test.dart b/packages/nhost_flutter/test/widgets/nhost_signed_in_out_test.dart new file mode 100644 index 00000000..e895e188 --- /dev/null +++ b/packages/nhost_flutter/test/widgets/nhost_signed_in_out_test.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:nhost_flutter/nhost_flutter.dart'; + +import '../helpers/fake_auth_client.dart'; + +Widget _wrap(FakeAuthClient auth, Widget child) => + MaterialApp(home: NhostAuthProvider(auth: auth, child: child)); + +void main() { + group('NhostSignedIn', () { + testWidgets('shows child when signed in', (tester) async { + final auth = FakeAuthClient(AuthenticationState.signedIn) + ..simulateSignIn(makeUser(), makeSession()); + await tester.pumpWidget( + _wrap(auth, const NhostSignedIn(child: Text('admin')))); + expect(find.text('admin'), findsOneWidget); + }); + + testWidgets('hides child when signed out', (tester) async { + final auth = FakeAuthClient(AuthenticationState.signedOut); + await tester.pumpWidget( + _wrap(auth, const NhostSignedIn(child: Text('admin')))); + expect(find.text('admin'), findsNothing); + }); + + testWidgets('shows orElse when signed out and orElse provided', + (tester) async { + final auth = FakeAuthClient(AuthenticationState.signedOut); + await tester.pumpWidget(_wrap( + auth, + const NhostSignedIn(child: Text('admin'), orElse: Text('guest')), + )); + expect(find.text('guest'), findsOneWidget); + expect(find.text('admin'), findsNothing); + }); + }); + + group('NhostSignedOut', () { + testWidgets('shows child when signed out', (tester) async { + final auth = FakeAuthClient(AuthenticationState.signedOut); + await tester.pumpWidget( + _wrap(auth, const NhostSignedOut(child: Text('login')))); + expect(find.text('login'), findsOneWidget); + }); + + testWidgets('hides child when signed in', (tester) async { + final auth = FakeAuthClient(AuthenticationState.signedIn) + ..simulateSignIn(makeUser(), makeSession()); + await tester.pumpWidget( + _wrap(auth, const NhostSignedOut(child: Text('login')))); + expect(find.text('login'), findsNothing); + }); + + testWidgets('shows orElse when signed in and orElse provided', + (tester) async { + final auth = FakeAuthClient(AuthenticationState.signedIn) + ..simulateSignIn(makeUser(), makeSession()); + await tester.pumpWidget(_wrap( + auth, + const NhostSignedOut( + child: Text('login'), orElse: Text('dashboard')), + )); + expect(find.text('dashboard'), findsOneWidget); + expect(find.text('login'), findsNothing); + }); + }); +} diff --git a/packages/nhost_flutter/test/widgets/nhost_user_builder_test.dart b/packages/nhost_flutter/test/widgets/nhost_user_builder_test.dart new file mode 100644 index 00000000..9d97ff7f --- /dev/null +++ b/packages/nhost_flutter/test/widgets/nhost_user_builder_test.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:nhost_flutter/nhost_flutter.dart'; + +import '../helpers/fake_auth_client.dart'; + +Widget _wrap(FakeAuthClient auth, Widget child) => + MaterialApp(home: NhostAuthProvider(auth: auth, child: child)); + +void main() { + group('NhostUserBuilder', () { + testWidgets('calls builder with current user when signed in', + (tester) async { + final auth = FakeAuthClient(AuthenticationState.signedIn) + ..simulateSignIn(makeUser(email: 'ada@example.com'), makeSession()); + await tester.pumpWidget(_wrap( + auth, + NhostUserBuilder( + builder: (_, user) => Text('hi ${user.email}'), + ), + )); + expect(find.text('hi ada@example.com'), findsOneWidget); + }); + + testWidgets('renders SizedBox.shrink when signed out and no orElse', + (tester) async { + final auth = FakeAuthClient(AuthenticationState.signedOut); + await tester.pumpWidget(_wrap( + auth, + NhostUserBuilder(builder: (_, user) => Text('hi ${user.email}')), + )); + expect(find.byType(Text), findsNothing); + }); + + testWidgets('renders orElse when signed out', (tester) async { + final auth = FakeAuthClient(AuthenticationState.signedOut); + await tester.pumpWidget(_wrap( + auth, + NhostUserBuilder( + builder: (_, user) => Text('hi ${user.email}'), + orElse: (_) => const Text('please sign in'), + ), + )); + expect(find.text('please sign in'), findsOneWidget); + }); + + testWidgets('rebuilds when user signs in', (tester) async { + final auth = FakeAuthClient(AuthenticationState.signedOut); + await tester.pumpWidget(_wrap( + auth, + NhostUserBuilder( + builder: (_, user) => Text('hi ${user.email}'), + orElse: (_) => const Text('signed out'), + ), + )); + expect(find.text('signed out'), findsOneWidget); + + auth.simulateSignIn(makeUser(email: 'b@c.com'), makeSession()); + await tester.pump(); + + expect(find.text('hi b@c.com'), findsOneWidget); + }); + }); +} diff --git a/packages/nhost_sdk/lib/src/base/hasura_auth_client.dart b/packages/nhost_sdk/lib/src/base/hasura_auth_client.dart index c1faaa31..2f4d2cce 100644 --- a/packages/nhost_sdk/lib/src/base/hasura_auth_client.dart +++ b/packages/nhost_sdk/lib/src/base/hasura_auth_client.dart @@ -128,6 +128,66 @@ abstract class HasuraAuthClient { Future completeOAuthProviderSignIn(Uri redirectUrl); + /// Signs in using a Personal Access Token (PAT). + /// + /// PATs are long-lived tokens suited for CI/CD, scripts, and server-to-server + /// calls. Create a PAT in the Nhost dashboard → Account settings → PATs. + Future signInWithPat(String pat); + + /// Fetches the current user's profile directly from the server. + /// + /// Useful for forcing a user-data refresh without a full token cycle. + Future fetchUser(); + + /// Verifies whether [accessToken] is still valid on the server. + /// + /// Returns `true` if the token is valid, `false` otherwise. + Future verifyToken(String accessToken); + + // --------------------------------------------------------------------------- + // WebAuthn — platform-dependent + // The methods below require a FIDO2/WebAuthn platform integration. + // On Flutter, pair with a native plugin (e.g. flutter_web_auth_2). + // Call the server endpoints, but the challenge creation/assertion must be + // handled by the platform layer before calling these. + // --------------------------------------------------------------------------- + + /// Initiates WebAuthn sign-in. Returns the server challenge that the + /// platform layer must sign before calling [verifyWebAuthnSignIn]. + Future> signInWithWebAuthn(); + + /// Completes WebAuthn sign-in with the [assertionResponse] produced by the + /// platform authenticator. + Future verifyWebAuthnSignIn( + Map assertionResponse, + ); + + /// Initiates WebAuthn sign-up. Returns the server challenge that the + /// platform layer must use to create a new credential. + Future> signUpWithWebAuthn({String? email}); + + /// Completes WebAuthn sign-up with the [attestationResponse] produced by + /// the platform authenticator. + Future verifyWebAuthnSignUp( + Map attestationResponse, + ); + + /// Initiates adding a new WebAuthn credential to an existing account. + Future> addWebAuthnCredential(); + + /// Completes adding a WebAuthn credential with the [attestationResponse]. + Future verifyAddWebAuthnCredential( + Map attestationResponse, + ); + + /// Initiates a session-elevation flow via WebAuthn. + Future> elevateWithWebAuthn(); + + /// Completes session elevation with the [assertionResponse]. + Future verifyWebAuthnElevation( + Map assertionResponse, + ); + @visibleForTesting Future setSession(Session session); From 6784faebdbc6158bd53767f883914d708b4cb129 Mon Sep 17 00:00:00 2001 From: Ildeberto Vasconcelos Date: Sun, 7 Jun 2026 03:23:27 +0100 Subject: [PATCH 8/8] Update Nhost subdomain in config.dart --- packages/nhost_flutter/example/lib/config.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nhost_flutter/example/lib/config.dart b/packages/nhost_flutter/example/lib/config.dart index 46c18957..c41eecfb 100644 --- a/packages/nhost_flutter/example/lib/config.dart +++ b/packages/nhost_flutter/example/lib/config.dart @@ -1,4 +1,4 @@ // Replace with your Nhost project subdomain and region. // For local development use Nhost.local() instead of Nhost.initialize(). -const subdomain = 'xnnteixlkkyixpmpmwml'; +const subdomain = 'xnnmloqkoasdlwm'; const region = 'eu-central-1';