diff --git a/examples/flutter-user-management/README.md b/examples/flutter-user-management/README.md new file mode 100644 index 00000000000..164f52c99cc --- /dev/null +++ b/examples/flutter-user-management/README.md @@ -0,0 +1,110 @@ +# Supabase Flutter User Management + +This example will set you up for a very common situation: users can sign up with a magic link and then update their account with public profile information, including a profile image. + +## Technologies used + +- Frontend: + - [Flutter SDK](https://flutter.dev/) - Google's UI toolkit for crafting beautiful, natively compiled applications for mobile, web, and desktop from a single codebase. + - [supabase_flutter](https://pub.dev/packages/supabase_flutter) for user management and image storage uploading. +- Backend: + - [app.supabase.io](https://app.supabase.io/): hosted Postgres database with restful API for usage with supabase_flutter. + +## Build from scratch + +### 1. Create new project + +Sign up to Supabase - [https://app.supabase.io](https://app.supabase.io) and create a new project. Wait for your database to start. + +### 2. Run "User Management" Quickstart + +Once your database has started, run the "User Management Starter" quickstart. Inside of your project, enter the `SQL editor` tab and scroll down until you see `User Management Starter: Set up a Public Profiles table which you can access with your API`. + +### 3. Get the URL and Key + +Go to the Project Settings (the cog icon), open the API tab, and find your API URL and `anon` key, you'll need these in the next step. + +The `anon` key is your client-side API key. It allows "anonymous access" to your database, until the user has logged in. Once they have logged in, the keys will switch to the user's own login token. This enables row level security for your data. Read more about this [below](#postgres-row-level-security). + +![image](https://user-images.githubusercontent.com/10214025/88916245-528c2680-d298-11ea-8a71-708f93e1ce4f.png) + +**_NOTE_**: The `service_role` key has full access to your data, bypassing any security policies. These keys have to be kept secret and are meant to be used in server environments and never on a client or browser. + +### 4. Setup deeplink redirect urls + +Go to the Authentication Settings page (the user icon). Enter the flutter app redirect url below into `Additional Redirect URLs` field. Then click Save. + +> io.supabase.flutterdemo://login-callback + +![authentication settings page](https://user-images.githubusercontent.com/689843/124574731-f735c180-de74-11eb-8f50-2d34161261dd.png) + +### 5. Run the flutter application + +- Go to `lib/utils/constants.dart` file +- Update `SUPABASE_URL` and `SUPABASE_ANNON_KEY` with your URL and Key +- Run the application: `flutter run` + +## Supabase details + +### Postgres Row level security + +This project uses very high-level Authorization using Postgres' Role Level Security. +When you start a Postgres database on Supabase, we populate it with an `auth` schema, and some helper functions. +When a user logs in, they are issued a JWT with the role `authenticated` and thier UUID. +We can use these details to provide fine-grained control over what each user can and cannot do. + +This is a trimmed-down schema, with the policies: + +```sql +-- Create a table for Public Profiles +create table profiles ( + id uuid references auth.users not null, + updated_at timestamp with time zone, + username text unique, + avatar_url text, + website text, + + primary key (id), + unique(username), + constraint username_length check (char_length(username) >= 3) +); + +alter table profiles enable row level security; + +create policy "Public profiles are viewable by everyone." + on profiles for select + using ( true ); + +create policy "Users can insert their own profile." + on profiles for insert + with check ( auth.uid() = id ); + +create policy "Users can update own profile." + on profiles for update + using ( auth.uid() = id ); + +-- Set up Realtime! +begin; + drop publication if exists supabase_realtime; + create publication supabase_realtime; +commit; +alter publication supabase_realtime add table profiles; + +-- Set up Storage! +insert into storage.buckets (id, name) +values ('avatars', 'avatars'); + +create policy "Avatar images are publicly accessible." + on storage.objects for select + using ( bucket_id = 'avatars' ); + +create policy "Anyone can upload an avatar." + on storage.objects for insert + with check ( bucket_id = 'avatars' ); +``` + +## Authors + +- [Supabase](https://supabase.io) + +Supabase is open source. We'd love for you to follow along and get involved at https://github.com/supabase/supabase diff --git a/examples/flutter-user-management/analysis_options.yaml b/examples/flutter-user-management/analysis_options.yaml new file mode 100644 index 00000000000..dc21fdb52bd --- /dev/null +++ b/examples/flutter-user-management/analysis_options.yaml @@ -0,0 +1,5 @@ +include: package:lint/analysis_options.yaml + +linter: + rules: + avoid_print: false \ No newline at end of file diff --git a/examples/flutter-user-management/android/.gitignore b/examples/flutter-user-management/android/.gitignore new file mode 100644 index 00000000000..0a741cb43d6 --- /dev/null +++ b/examples/flutter-user-management/android/.gitignore @@ -0,0 +1,11 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties diff --git a/examples/flutter-user-management/android/app/build.gradle b/examples/flutter-user-management/android/app/build.gradle new file mode 100644 index 00000000000..fec8b364067 --- /dev/null +++ b/examples/flutter-user-management/android/app/build.gradle @@ -0,0 +1,66 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 29 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "io.supabase.demoapp" + minSdkVersion 20 + targetSdkVersion 29 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + manifestPlaceholders = [ + 'appAuthRedirectScheme': 'io.supabase.demoapp' + ] + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/examples/flutter-user-management/android/app/src/debug/AndroidManifest.xml b/examples/flutter-user-management/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000000..5f5a7501249 --- /dev/null +++ b/examples/flutter-user-management/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/examples/flutter-user-management/android/app/src/main/AndroidManifest.xml b/examples/flutter-user-management/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..e21232c0b30 --- /dev/null +++ b/examples/flutter-user-management/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/flutter-user-management/android/app/src/main/kotlin/io/supabase/demoapp/MainActivity.kt b/examples/flutter-user-management/android/app/src/main/kotlin/io/supabase/demoapp/MainActivity.kt new file mode 100644 index 00000000000..93dc3a7f29d --- /dev/null +++ b/examples/flutter-user-management/android/app/src/main/kotlin/io/supabase/demoapp/MainActivity.kt @@ -0,0 +1,6 @@ +package io.supabase.demoapp + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/examples/flutter-user-management/android/app/src/main/res/drawable/launch_background.xml b/examples/flutter-user-management/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000000..304732f8842 --- /dev/null +++ b/examples/flutter-user-management/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/examples/flutter-user-management/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/flutter-user-management/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000000..db77bb4b7b0 Binary files /dev/null and b/examples/flutter-user-management/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/examples/flutter-user-management/android/app/src/main/res/mipmap-hdpi/launcher_icon.png b/examples/flutter-user-management/android/app/src/main/res/mipmap-hdpi/launcher_icon.png new file mode 100644 index 00000000000..723eb74a137 Binary files /dev/null and b/examples/flutter-user-management/android/app/src/main/res/mipmap-hdpi/launcher_icon.png differ diff --git a/examples/flutter-user-management/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/examples/flutter-user-management/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000000..17987b79bb8 Binary files /dev/null and b/examples/flutter-user-management/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/examples/flutter-user-management/android/app/src/main/res/mipmap-mdpi/launcher_icon.png b/examples/flutter-user-management/android/app/src/main/res/mipmap-mdpi/launcher_icon.png new file mode 100644 index 00000000000..430bf39102a Binary files /dev/null and b/examples/flutter-user-management/android/app/src/main/res/mipmap-mdpi/launcher_icon.png differ diff --git a/examples/flutter-user-management/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/examples/flutter-user-management/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000000..09d4391482b Binary files /dev/null and b/examples/flutter-user-management/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/examples/flutter-user-management/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png b/examples/flutter-user-management/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png new file mode 100644 index 00000000000..4ebd28f0eb5 Binary files /dev/null and b/examples/flutter-user-management/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png differ diff --git a/examples/flutter-user-management/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/flutter-user-management/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000000..d5f1c8d34e7 Binary files /dev/null and b/examples/flutter-user-management/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/examples/flutter-user-management/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png b/examples/flutter-user-management/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png new file mode 100644 index 00000000000..184a0496390 Binary files /dev/null and b/examples/flutter-user-management/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png differ diff --git a/examples/flutter-user-management/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/examples/flutter-user-management/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000000..4d6372eebdb Binary files /dev/null and b/examples/flutter-user-management/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/examples/flutter-user-management/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png b/examples/flutter-user-management/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png new file mode 100644 index 00000000000..fff447baf83 Binary files /dev/null and b/examples/flutter-user-management/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png differ diff --git a/examples/flutter-user-management/android/app/src/main/res/values/styles.xml b/examples/flutter-user-management/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000000..1f83a33fd4f --- /dev/null +++ b/examples/flutter-user-management/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/examples/flutter-user-management/android/app/src/profile/AndroidManifest.xml b/examples/flutter-user-management/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000000..5f5a7501249 --- /dev/null +++ b/examples/flutter-user-management/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/examples/flutter-user-management/android/build.gradle b/examples/flutter-user-management/android/build.gradle new file mode 100644 index 00000000000..3100ad2d555 --- /dev/null +++ b/examples/flutter-user-management/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.3.50' + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/examples/flutter-user-management/android/gradle.properties b/examples/flutter-user-management/android/gradle.properties new file mode 100644 index 00000000000..a6738207fd1 --- /dev/null +++ b/examples/flutter-user-management/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true +android.enableR8=true diff --git a/examples/flutter-user-management/android/gradle/wrapper/gradle-wrapper.properties b/examples/flutter-user-management/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..296b146b731 --- /dev/null +++ b/examples/flutter-user-management/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/examples/flutter-user-management/android/settings.gradle b/examples/flutter-user-management/android/settings.gradle new file mode 100644 index 00000000000..44e62bcf06a --- /dev/null +++ b/examples/flutter-user-management/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/examples/flutter-user-management/assets/images/logo-dark.png b/examples/flutter-user-management/assets/images/logo-dark.png new file mode 100644 index 00000000000..f78a307b925 Binary files /dev/null and b/examples/flutter-user-management/assets/images/logo-dark.png differ diff --git a/examples/flutter-user-management/assets/images/logo.png b/examples/flutter-user-management/assets/images/logo.png new file mode 100644 index 00000000000..0ae50c18140 Binary files /dev/null and b/examples/flutter-user-management/assets/images/logo.png differ diff --git a/examples/flutter-user-management/assets/images/noavatar.jpeg b/examples/flutter-user-management/assets/images/noavatar.jpeg new file mode 100644 index 00000000000..d096544006d Binary files /dev/null and b/examples/flutter-user-management/assets/images/noavatar.jpeg differ diff --git a/examples/flutter-user-management/ios/.gitignore b/examples/flutter-user-management/ios/.gitignore new file mode 100644 index 00000000000..e96ef602b8d --- /dev/null +++ b/examples/flutter-user-management/ios/.gitignore @@ -0,0 +1,32 @@ +*.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/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/examples/flutter-user-management/ios/Flutter/AppFrameworkInfo.plist b/examples/flutter-user-management/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000000..6b4c0f78a78 --- /dev/null +++ b/examples/flutter-user-management/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/examples/flutter-user-management/ios/Flutter/Debug.xcconfig b/examples/flutter-user-management/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000000..ec97fc6f302 --- /dev/null +++ b/examples/flutter-user-management/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/examples/flutter-user-management/ios/Flutter/Release.xcconfig b/examples/flutter-user-management/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000000..c4855bfe200 --- /dev/null +++ b/examples/flutter-user-management/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/examples/flutter-user-management/ios/Podfile b/examples/flutter-user-management/ios/Podfile new file mode 100644 index 00000000000..1e8c3c90a55 --- /dev/null +++ b/examples/flutter-user-management/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.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! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/examples/flutter-user-management/ios/Runner.xcodeproj/project.pbxproj b/examples/flutter-user-management/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..f5a49818750 --- /dev/null +++ b/examples/flutter-user-management/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,495 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.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 */; }; +/* End PBXBuildFile 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 */ + 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 = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; 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 = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.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 = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 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 */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + 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 */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + 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 = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 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 */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 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; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + 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; + 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"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase 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; + 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; + 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 = 9.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; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.supabase.demoapp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + 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; + 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 = 9.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; + 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; + 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 = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + 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; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.supabase.demoapp; + 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; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.supabase.demoapp; + 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 */ + 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 */; +} \ No newline at end of file diff --git a/examples/flutter-user-management/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/flutter-user-management/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000000..1d526a16ed0 --- /dev/null +++ b/examples/flutter-user-management/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/flutter-user-management/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/flutter-user-management/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000000..18d981003d6 --- /dev/null +++ b/examples/flutter-user-management/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/examples/flutter-user-management/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/examples/flutter-user-management/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000000..f9b0d7c5ea1 --- /dev/null +++ b/examples/flutter-user-management/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/examples/flutter-user-management/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/examples/flutter-user-management/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000000..a28140cfdb3 --- /dev/null +++ b/examples/flutter-user-management/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/flutter-user-management/ios/Runner.xcworkspace/contents.xcworkspacedata b/examples/flutter-user-management/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000000..1d526a16ed0 --- /dev/null +++ b/examples/flutter-user-management/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/flutter-user-management/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/flutter-user-management/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000000..18d981003d6 --- /dev/null +++ b/examples/flutter-user-management/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/examples/flutter-user-management/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/examples/flutter-user-management/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000000..f9b0d7c5ea1 --- /dev/null +++ b/examples/flutter-user-management/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/examples/flutter-user-management/ios/Runner/AppDelegate.swift b/examples/flutter-user-management/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000000..70693e4a8c1 --- /dev/null +++ b/examples/flutter-user-management/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000000..d36b1fab2d9 --- /dev/null +++ b/examples/flutter-user-management/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/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 00000000000..1f362942b97 Binary files /dev/null and b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 00000000000..138f10b2dbe Binary files /dev/null and b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 00000000000..69d63cea798 Binary files /dev/null and b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 00000000000..44bc2d6f0bb Binary files /dev/null and b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 00000000000..bef965285cb Binary files /dev/null and b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 00000000000..9246fcbd058 Binary files /dev/null and b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 00000000000..cf3ad32d52e Binary files /dev/null and b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 00000000000..69d63cea798 Binary files /dev/null and b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 00000000000..e47d503fa5d Binary files /dev/null and b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 00000000000..73502aff8c9 Binary files /dev/null and b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 00000000000..73502aff8c9 Binary files /dev/null and b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 00000000000..abb4d37291f Binary files /dev/null and b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 00000000000..8d81ca60ef0 Binary files /dev/null and b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 00000000000..855e1198810 Binary files /dev/null and b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 00000000000..34989d35504 Binary files /dev/null and b/examples/flutter-user-management/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/examples/flutter-user-management/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/examples/flutter-user-management/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 00000000000..0bedcf2fd46 --- /dev/null +++ b/examples/flutter-user-management/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/examples/flutter-user-management/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/examples/flutter-user-management/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 00000000000..9da19eacad3 Binary files /dev/null and b/examples/flutter-user-management/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/examples/flutter-user-management/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/examples/flutter-user-management/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 00000000000..9da19eacad3 Binary files /dev/null and b/examples/flutter-user-management/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/examples/flutter-user-management/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/examples/flutter-user-management/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 00000000000..9da19eacad3 Binary files /dev/null and b/examples/flutter-user-management/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/examples/flutter-user-management/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/examples/flutter-user-management/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000000..89c2725b70f --- /dev/null +++ b/examples/flutter-user-management/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/examples/flutter-user-management/ios/Runner/Base.lproj/LaunchScreen.storyboard b/examples/flutter-user-management/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000000..f2e259c7c93 --- /dev/null +++ b/examples/flutter-user-management/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/flutter-user-management/ios/Runner/Base.lproj/Main.storyboard b/examples/flutter-user-management/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000000..f3c28516fb3 --- /dev/null +++ b/examples/flutter-user-management/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/flutter-user-management/ios/Runner/Info.plist b/examples/flutter-user-management/ios/Runner/Info.plist new file mode 100644 index 00000000000..fb546c6d4ba --- /dev/null +++ b/examples/flutter-user-management/ios/Runner/Info.plist @@ -0,0 +1,57 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Supabase Demo + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + + io.supabase.flutterdemo + + + + + diff --git a/examples/flutter-user-management/ios/Runner/Runner-Bridging-Header.h b/examples/flutter-user-management/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000000..308a2a560b4 --- /dev/null +++ b/examples/flutter-user-management/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/examples/flutter-user-management/lib/components/auth_required_state.dart b/examples/flutter-user-management/lib/components/auth_required_state.dart new file mode 100644 index 00000000000..5e72a96042d --- /dev/null +++ b/examples/flutter-user-management/lib/components/auth_required_state.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; + +class AuthRequiredState + extends SupabaseAuthRequiredState { + @override + void onUnauthenticated() { + Navigator.pushNamedAndRemoveUntil(context, '/signIn', (route) => false); + } +} diff --git a/examples/flutter-user-management/lib/components/auth_state.dart b/examples/flutter-user-management/lib/components/auth_state.dart new file mode 100644 index 00000000000..6ba3c67425b --- /dev/null +++ b/examples/flutter-user-management/lib/components/auth_state.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:supabase/supabase.dart' as supabase; +import 'package:supabase_flutter/supabase_flutter.dart'; + +class AuthState extends SupabaseAuthState { + @override + void onUnauthenticated() { + Navigator.pushNamedAndRemoveUntil(context, '/signIn', (route) => false); + } + + @override + void onAuthenticated(supabase.Session session) { + Navigator.pushNamedAndRemoveUntil(context, '/profile', (route) => false); + } + + @override + void onPasswordRecovery(supabase.Session session) { + Navigator.pushNamedAndRemoveUntil( + context, '/profile/changePassword', (route) => false); + } + + @override + void onErrorAuthenticating(String message) { + print('***** onErrorAuthenticating: $message'); + } +} diff --git a/examples/flutter-user-management/lib/main.dart b/examples/flutter-user-management/lib/main.dart new file mode 100644 index 00000000000..2e18a081cbb --- /dev/null +++ b/examples/flutter-user-management/lib/main.dart @@ -0,0 +1,37 @@ +import 'package:supabase_demo/screens/profile_screen.dart'; +import 'package:flutter/material.dart'; +import 'package:supabase_demo/screens/signin_screen.dart'; +import 'package:supabase_demo/screens/splash_screen.dart'; +import 'package:supabase_demo/utils/constants.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + + // init Supabase singleton + Supabase( + url: supabaseUrl, + anonKey: supabaseAnnonKey, + authCallbackUrlHostname: 'login-callback', + debug: true, + ); + + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Supabase Demo', + theme: ThemeData.dark(), + initialRoute: '/', + routes: { + '/': (_) => SplashScreen(), + '/signIn': (_) => SignInScreen(), + '/profile': (_) => ProfileScreen(), + }, + ); + } +} diff --git a/examples/flutter-user-management/lib/screens/profile_screen.dart b/examples/flutter-user-management/lib/screens/profile_screen.dart new file mode 100644 index 00000000000..6d67fab114c --- /dev/null +++ b/examples/flutter-user-management/lib/screens/profile_screen.dart @@ -0,0 +1,294 @@ +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:supabase_demo/components/auth_required_state.dart'; +import 'package:supabase_demo/utils/helpers.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:gotrue/gotrue.dart'; +import 'package:path/path.dart'; +import 'package:rounded_loading_button/rounded_loading_button.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; + +class ProfileScreen extends StatefulWidget { + @override + _ProfileScreenState createState() => _ProfileScreenState(); +} + +class _ProfileScreenState extends AuthRequiredState { + _ProfileScreenState(); + + final scaffoldKey = GlobalKey(); + + final RoundedLoadingButtonController _signOutBtnController = + RoundedLoadingButtonController(); + final RoundedLoadingButtonController _updateProfileBtnController = + RoundedLoadingButtonController(); + + final _picker = ImagePicker(); + + User? user; + bool loadingProfile = true; + String _appBarTitle = ''; + String username = ''; + String website = ''; + String avatarUrl = ''; + + @override + void initState() { + super.initState(); + + final _user = Supabase().client.auth.user(); + if (_user != null) { + setState(() { + _appBarTitle = 'Welcome ${_user.email}'; + user = _user; + }); + loadProfile(_user.id); + } + } + + Future loadProfile(String userId) async { + final response = await Supabase() + .client + .from('profiles') + .select('username, website, avatar_url') + .eq('id', userId) + .single() + .execute(); + if (response.error == null && response.data != null) { + setState(() { + username = response.data['username'] as String? ?? ''; + website = response.data['website'] as String? ?? ''; + avatarUrl = response.data['avatar_url'] as String? ?? ''; + loadingProfile = false; + }); + } else { + setState(() { + loadingProfile = false; + }); + } + return true; + } + + final picker = ImagePicker(); + + Future updateAvatar(BuildContext context) async { + final pickedFile = await _picker.getImage(source: ImageSource.camera); + if (pickedFile != null) { + final file = File(pickedFile.path); + final fileExt = extension(file.path); + final fileName = '${randomString(15)}$fileExt'; + + final response = await Supabase() + .client + .storage + .from('avatars') + .upload(fileName, file); + if (response.error == null) { + setState(() { + avatarUrl = fileName; + }); + _onUpdateProfilePress(context); + } else { + print(response.error!.message); + } + } + } + + Future _onSignOutPress(BuildContext context) async { + await Supabase().client.auth.signOut(); + Navigator.pushNamedAndRemoveUntil(context, '/signIn', (route) => false); + return true; + } + + Future _onUpdateProfilePress(BuildContext context) async { + FocusScope.of(context).unfocus(); + + final updates = { + 'id': user?.id, + 'username': username, + 'website': website, + 'avatar_url': avatarUrl, + 'updated_at': DateTime.now().toString(), + }; + + final response = + await Supabase().client.from('profiles').upsert(updates).execute(); + if (response.error != null) { + showMessage("Update profile failed: ${response.error!.message}"); + _updateProfileBtnController.reset(); + return false; + } else { + _updateProfileBtnController.reset(); + showMessage("Profile updated!"); + return true; + } + } + + void showMessage(String message) { + final snackbar = SnackBar(content: Text(message)); + ScaffoldMessenger.of(scaffoldKey.currentContext!).showSnackBar(snackbar); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + key: scaffoldKey, + resizeToAvoidBottomInset: false, + appBar: AppBar( + title: Text(_appBarTitle), + ), + body: loadingProfile + ? SizedBox( + height: MediaQuery.of(context).size.height / 1.3, + child: const Center( + child: CircularProgressIndicator(), + ), + ) + : Padding( + padding: const EdgeInsets.all(15.0), + child: Column( + children: [ + AvatarContainer( + url: avatarUrl, + onUpdatePressed: () => updateAvatar(context), + key: Key(avatarUrl), + ), + TextFormField( + onChanged: (value) => setState(() { + username = value; + }), + initialValue: username, + keyboardType: TextInputType.emailAddress, + decoration: const InputDecoration( + labelText: 'Username', + hintText: '', + ), + ), + TextFormField( + onChanged: (value) => setState(() { + website = value; + }), + initialValue: website, + keyboardType: TextInputType.emailAddress, + decoration: const InputDecoration( + labelText: 'Website', + hintText: '', + ), + ), + const SizedBox(height: 35.0), + RoundedLoadingButton( + color: Colors.green, + controller: _updateProfileBtnController, + onPressed: () { + _onUpdateProfilePress(context); + }, + child: const Text('Update profile', + style: TextStyle(fontSize: 20, color: Colors.white)), + ), + const SizedBox(height: 35.0), + RoundedLoadingButton( + color: Colors.red, + controller: _signOutBtnController, + onPressed: () { + _onSignOutPress(context); + }, + child: const Text( + 'Sign out', + style: TextStyle(fontSize: 20, color: Colors.white), + ), + ), + ], + ), + ), + ); + } +} + +class AvatarContainer extends StatefulWidget { + final String url; + final void Function() onUpdatePressed; + const AvatarContainer( + {required this.url, required this.onUpdatePressed, Key? key}) + : super(key: key); + + @override + _AvatarContainerState createState() => _AvatarContainerState(); +} + +class _AvatarContainerState extends State { + _AvatarContainerState(); + + bool loadingImage = true; + Uint8List? image; + + @override + void initState() { + super.initState(); + + if (widget.url != '') { + downloadImage(widget.url); + } + } + + Future downloadImage(String path) async { + setState(() { + loadingImage = true; + }); + + final response = + await Supabase().client.storage.from('avatars').download(path); + if (response.error == null) { + setState(() { + image = response.data; + loadingImage = false; + }); + } else { + print(response.error!.message); + setState(() { + loadingImage = false; + }); + } + return true; + } + + ImageProvider _getImage() { + if (image != null) { + return MemoryImage(image!); + } else { + return const AssetImage('assets/images/noavatar.jpeg'); + } + } + + @override + Widget build(BuildContext context) { + if (loadingImage) { + return const CircleAvatar( + radius: 65, + child: Align( + child: CircularProgressIndicator(), + ), + ); + } else { + return CircleAvatar( + radius: 65, + backgroundImage: _getImage(), + child: Stack(children: [ + Align( + alignment: Alignment.bottomRight, + child: IconButton( + icon: const CircleAvatar( + radius: 18, + backgroundColor: Colors.white70, + child: Icon(CupertinoIcons.camera), + ), + onPressed: () => widget.onUpdatePressed(), + ), + ), + ]), + ); + } + } +} diff --git a/examples/flutter-user-management/lib/screens/signin_screen.dart b/examples/flutter-user-management/lib/screens/signin_screen.dart new file mode 100644 index 00000000000..5e75d259d37 --- /dev/null +++ b/examples/flutter-user-management/lib/screens/signin_screen.dart @@ -0,0 +1,99 @@ +import 'package:supabase_demo/components/auth_state.dart'; +import 'package:supabase_demo/utils/helpers.dart'; +import 'package:flutter/material.dart'; +import 'package:supabase_demo/utils/constants.dart'; +import 'package:rounded_loading_button/rounded_loading_button.dart'; +import 'package:supabase/supabase.dart' as supabase; +import 'package:supabase_flutter/supabase_flutter.dart'; + +class SignInScreen extends StatefulWidget { + @override + _SignInState createState() => _SignInState(); +} + +class _SignInState extends AuthState { + final formKey = GlobalKey(); + final scaffoldKey = GlobalKey(); + + final RoundedLoadingButtonController _magicLinkController = + RoundedLoadingButtonController(); + + String _email = ''; + + @override + void onErrorAuthenticating(String message) { + showMessage(message); + } + + Future _onMagicLinkPress(BuildContext context) async { + final form = formKey.currentState; + + if (form != null && form.validate()) { + form.save(); + FocusScope.of(context).unfocus(); + + final response = await Supabase().client.auth.signIn( + email: _email, + options: supabase.AuthOptions( + redirectTo: authRedirectUri, + ), + ); + if (response.error != null) { + showMessage(response.error!.message); + _magicLinkController.reset(); + } else { + showMessage('Check your email for the login link!'); + } + } else { + _magicLinkController.reset(); + } + return true; + } + + void showMessage(String message) { + final snackbar = SnackBar(content: Text(message)); + ScaffoldMessenger.of(scaffoldKey.currentContext!).showSnackBar(snackbar); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + key: scaffoldKey, + resizeToAvoidBottomInset: false, + appBar: AppBar( + title: const Text('Sign in'), + ), + body: Padding( + padding: const EdgeInsets.all(15.0), + child: Form( + key: formKey, + child: Column( + children: [ + const SizedBox(height: 25.0), + TextFormField( + onSaved: (value) => _email = value ?? '', + validator: (val) => validateEmail(val), + keyboardType: TextInputType.emailAddress, + decoration: const InputDecoration( + hintText: 'Enter your email address', + ), + ), + const SizedBox(height: 50.0), + RoundedLoadingButton( + color: Colors.green, + controller: _magicLinkController, + onPressed: () { + _onMagicLinkPress(context); + }, + child: const Text( + 'Send magic link', + style: TextStyle(fontSize: 20, color: Colors.white), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/examples/flutter-user-management/lib/screens/splash_screen.dart b/examples/flutter-user-management/lib/screens/splash_screen.dart new file mode 100644 index 00000000000..048ee41f799 --- /dev/null +++ b/examples/flutter-user-management/lib/screens/splash_screen.dart @@ -0,0 +1,50 @@ +import 'dart:async'; + +import 'package:supabase_demo/components/auth_state.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class SplashScreen extends StatefulWidget { + @override + SplashScreenState createState() => SplashScreenState(); +} + +class SplashScreenState extends AuthState + with SingleTickerProviderStateMixin { + Timer? recoverSessionTimer; + + @override + void initState() { + super.initState(); + + /// a timer to slow down session restore + /// If not user can't really see the splash screen + const _duration = Duration(seconds: 1); + recoverSessionTimer = Timer(_duration, () { + recoverSupabaseSession(); + }); + } + + /// on received auth deeplink, we should cancel recoverSessionTimer + /// and wait for auth deep link handling result + @override + void onReceivedAuthDeeplink(Uri uri) { + if (recoverSessionTimer != null) { + recoverSessionTimer!.cancel(); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: SizedBox( + height: 50.0, + child: Image.asset( + "assets/images/logo-dark.png", + ), + ), + ), + ); + } +} diff --git a/examples/flutter-user-management/lib/utils/constants.dart b/examples/flutter-user-management/lib/utils/constants.dart new file mode 100644 index 00000000000..bb9b08117af --- /dev/null +++ b/examples/flutter-user-management/lib/utils/constants.dart @@ -0,0 +1,6 @@ +/// TODO: update with your custom SCHEME and HOSTNAME +const authRedirectUri = 'io.supabase.flutterdemo://login-callback'; + +/// TODO: Add your SUPABASE_URL / SUPABASE_KEY here +const supabaseUrl = 'SUPABASE_URL'; +const supabaseAnnonKey = 'SUPABASE_KEY'; diff --git a/examples/flutter-user-management/lib/utils/helpers.dart b/examples/flutter-user-management/lib/utils/helpers.dart new file mode 100644 index 00000000000..27f325746ca --- /dev/null +++ b/examples/flutter-user-management/lib/utils/helpers.dart @@ -0,0 +1,21 @@ +import 'dart:math'; + +String? validateEmail(String? value) { + const String pattern = + r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]" + r"{0,253}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]" + r"{0,253}[a-zA-Z0-9])?)*$"; + final RegExp regex = RegExp(pattern); + if (value == null || !regex.hasMatch(value)) { + return 'Not a valid email.'; + } else { + return null; + } +} + +String randomString(int length) { + const ch = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'; + final Random r = Random(); + return String.fromCharCodes( + Iterable.generate(length, (_) => ch.codeUnitAt(r.nextInt(ch.length)))); +} diff --git a/examples/flutter-user-management/pubspec.lock b/examples/flutter-user-management/pubspec.lock new file mode 100644 index 00000000000..31bb178686a --- /dev/null +++ b/examples/flutter-user-management/pubspec.lock @@ -0,0 +1,537 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.2" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.6.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.15.0" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + gotrue: + dependency: transitive + description: + name: gotrue + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.6" + http: + dependency: transitive + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.3" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + image: + dependency: transitive + description: + name: image + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.0+3" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.3" + jwt_decode: + dependency: transitive + description: + name: jwt_decode + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.1" + lint: + dependency: "direct dev" + description: + name: lint + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.3" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.10" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + path: + dependency: "direct main" + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.11.0" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.0" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + postgrest: + dependency: transitive + description: + name: postgrest + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.1" + realtime_client: + dependency: transitive + description: + name: realtime_client + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" + rounded_loading_button: + dependency: "direct main" + description: + name: rounded_loading_button + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + rxdart: + dependency: transitive + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.26.0" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + shared_preferences_macos: + dependency: transitive + description: + name: shared_preferences_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + storage_client: + dependency: transitive + description: + name: storage_client + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1-dev.3" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + supabase: + dependency: "direct main" + description: + name: supabase + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1-dev.27" + supabase_flutter: + dependency: "direct main" + description: + name: supabase_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1-dev.2" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + uni_links: + dependency: transitive + description: + name: uni_links + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.1" + uni_links_platform_interface: + dependency: transitive + description: + name: uni_links_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + uni_links_web: + dependency: transitive + description: + name: uni_links_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0" + universal_io: + dependency: transitive + description: + name: universal_io + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + url_launcher: + dependency: transitive + description: + name: url_launcher + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.6" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.5" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.2" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" +sdks: + dart: ">=2.13.0 <3.0.0" + flutter: ">=2.0.0" diff --git a/examples/flutter-user-management/pubspec.yaml b/examples/flutter-user-management/pubspec.yaml new file mode 100644 index 00000000000..e4cf6abc872 --- /dev/null +++ b/examples/flutter-user-management/pubspec.yaml @@ -0,0 +1,58 @@ +name: supabase_demo +description: Supabase flutter user management. users can sign up with a magic link and then update their account with public profile information, including a profile image. +homepage: "https://supabase.io" +repository: "https://github.com/supabase/supabase" + +# The following line prevents the package from being accidentally published to +# pub.dev using `pub publish`. This is preferred for private packages. +publish_to: "none" # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: ">=2.12.0 <3.0.0" + +dependencies: + cupertino_icons: ^1.0.3 + flutter: + sdk: flutter + image_picker: ^0.8.0+3 + path: ^1.8.0 + rounded_loading_button: ^2.0.5 + supabase: ^0.0.1-dev.27 + supabase_flutter: ^0.0.1-dev.2 + +dev_dependencies: + flutter_launcher_icons: ^0.9.0 + flutter_test: + sdk: flutter + lint: ^1.5.3 + +flutter_icons: + android: "launcher_icon" + ios: true + image_path: "assets/images/logo.png" + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + assets: + - assets/images/ diff --git a/examples/flutter-user-management/test/widget_test.dart b/examples/flutter-user-management/test/widget_test.dart new file mode 100644 index 00000000000..6693b82df58 --- /dev/null +++ b/examples/flutter-user-management/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:supabase_demo/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +}