Skip to content

feature/supa_avatar #136

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,80 @@ SupaSocialsAuth(
),
```

## SupaAvatar

A plug-and-play widget to show and edit a Supabase user's profile image.
Supports both readonly and editable modes with full customization options.


<img src="screenshots/avatar.gif" alt="avatar-screenshot">


### Usage

```dart
import 'package:supabase_auth_ui/supabase_auth_ui.dart';

class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key});

@override
Widget build(BuildContext context) {
return Center(
child: SupaAvatar(
radius: 50,
isEditable: true,
),
);
}
}
```
When you tap on the avatar & `isEditable: true`, the avatar editor is opened:

| dialog | modal |
| -------- | ------- |
| <img src="screenshots/dialog-editor.png" alt="dialog-editor" width="300"/> | <img src="screenshots/modal-editor.png" alt="modal-editor" width="300"/> |
| `editorType: SupaAvatarEditorType.dialog,` | `editorType: SupaAvatarEditorType.modal,` |

### Customization

```dart
SupaAvatar(
radius: 60,
isEditable: true,
supabaseStorageBucket: 'avatars',
supabaseStoragePath: 'profile_image', // stored as userId/profile_image
supabaseUserAttributeImageUrlKey: 'avatar_url',

fallbackIcon: Icon(Icons.person_2_rounded, size: 32),
cacheBuster: DateTime.now().millisecondsSinceEpoch.toString(),

// editor customization
editorType: SupaAvatarEditorType.dialog, // defaults to SupaAvatarEditorType.modal
editorShape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
editorBackgroundColor: Colors.grey[900],

// Snackbar colors
snackBarBackgroundColor: Colors.green[600],
snackBarTextColor: Colors.white,
snackBarErrorBackgroundColor: Colors.red[600],
snackBarErrorTextColor: Colors.white,
snackBarDuration: Duration(seconds: 2),
)
```


### Notes

- The image is stored in `avatars/{userId}/{supabaseStoragePath}`.
- The metadata field (`avatar_url` by default) is updated on upload/remove.
- Uses `cacheBuster` to bypass CDN cache after upload.
- Tapping on avatar opens a bottom sheet or dialog for edit options when `isEditable` is true.

---

## Theming

This library uses bare Flutter components so that you can control the appearance of the components using your own theme.
Expand Down
2 changes: 2 additions & 0 deletions example/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/

# IntelliJ related
Expand Down
Binary file added example/assets/supabase-logo-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion example/ios/Flutter/AppFrameworkInfo.plist
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>11.0</string>
<string>12.0</string>
</dict>
</plist>
2 changes: 1 addition & 1 deletion example/ios/Podfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '11.0'
# platform :ios, '12.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
Expand Down
67 changes: 59 additions & 8 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,42 +1,93 @@
PODS:
- app_links (0.0.1):
- app_links (0.0.2):
- Flutter
- AppAuth (1.7.6):
- AppAuth/Core (= 1.7.6)
- AppAuth/ExternalUserAgent (= 1.7.6)
- AppAuth/Core (1.7.6)
- AppAuth/ExternalUserAgent (1.7.6):
- AppAuth/Core
- Flutter (1.0.0)
- google_sign_in_ios (0.0.1):
- AppAuth (>= 1.7.4)
- Flutter
- FlutterMacOS
- GoogleSignIn (~> 7.1)
- GTMSessionFetcher (>= 3.4.0)
- GoogleSignIn (7.1.0):
- AppAuth (< 2.0, >= 1.7.3)
- GTMAppAuth (< 5.0, >= 4.1.1)
- GTMSessionFetcher/Core (~> 3.3)
- GTMAppAuth (4.1.1):
- AppAuth/Core (~> 1.7)
- GTMSessionFetcher/Core (< 4.0, >= 3.3)
- GTMSessionFetcher (3.5.0):
- GTMSessionFetcher/Full (= 3.5.0)
- GTMSessionFetcher/Core (3.5.0)
- GTMSessionFetcher/Full (3.5.0):
- GTMSessionFetcher/Core
- image_picker_ios (0.0.1):
- Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sign_in_with_apple (0.0.1):
- Flutter
- url_launcher_ios (0.0.1):
- Flutter

DEPENDENCIES:
- app_links (from `.symlinks/plugins/app_links/ios`)
- Flutter (from `Flutter`)
- google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/darwin`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)

SPEC REPOS:
trunk:
- AppAuth
- GoogleSignIn
- GTMAppAuth
- GTMSessionFetcher

EXTERNAL SOURCES:
app_links:
:path: ".symlinks/plugins/app_links/ios"
Flutter:
:path: Flutter
google_sign_in_ios:
:path: ".symlinks/plugins/google_sign_in_ios/darwin"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sign_in_with_apple:
:path: ".symlinks/plugins/sign_in_with_apple/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"

SPEC CHECKSUMS:
app_links: ab4ba54d10a13d45825336bc9707b5eadee81191
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
app_links: 76b66b60cc809390ca1ad69bfd66b998d2387ac7
AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
google_sign_in_ios: 19297361f2c51d7d8ac0201b866ef1fa5d1f94a8
GoogleSignIn: d4281ab6cf21542b1cfaff85c191f230b399d2db
GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d

PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011

COCOAPODS: 1.14.3
COCOAPODS: 1.16.2
26 changes: 22 additions & 4 deletions example/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
5577070999A7873150D42AF2 /* [CP] Embed Pods Frameworks */,
7507BAEEC32E71631CAEFDBF /* [CP] Copy Pods Resources */,
);
buildRules = (
);
Expand All @@ -156,7 +157,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
Expand Down Expand Up @@ -231,6 +232,23 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
7507BAEEC32E71631CAEFDBF /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
Expand Down Expand Up @@ -343,7 +361,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
Expand Down Expand Up @@ -420,7 +438,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
Expand Down Expand Up @@ -469,7 +487,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down Expand Up @@ -48,6 +48,7 @@
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
Expand Down
2 changes: 1 addition & 1 deletion example/ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import UIKit
import Flutter

@UIApplicationMain
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
Expand Down
47 changes: 8 additions & 39 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
import 'package:example/phone_sign_up.dart';
import 'package:example/src/core/app_router.dart';
import 'package:flutter/material.dart';
import 'package:supabase_auth_ui/supabase_auth_ui.dart';

import './home.dart';
import './sign_in.dart';
import './magic_link.dart';
import './update_password.dart';
import 'phone_sign_in.dart';
import './verify_phone.dart';


void main() async {
WidgetsFlutterBinding.ensureInitialized();

/// TODO: replace with your credentials
await Supabase.initialize(
url: 'https://yoursupabaseurl.supabase.co',
anonKey: 'your_anon_key',
url: 'https://hvjjvggueouofkqafawj.supabase.co',
anonKey: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imh2amp2Z2d1ZW91b2ZrcWFmYXdqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDM5ODA2MTMsImV4cCI6MjA1OTU1NjYxM30.m0VSa8jdoNcJFmS18xMwtM0P9yblakd2QgEXkW5kAqU',
);
runApp(const MyApp());
}
Expand All @@ -26,40 +19,16 @@ class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
return MaterialApp.router(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
title: 'Supabase Auth UI Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
inputDecorationTheme: const InputDecorationTheme(
border: OutlineInputBorder(),
),
),
initialRoute: '/',
routes: {
'/': (context) => const SignUp(),
'/magic_link': (context) => const MagicLink(),
'/update_password': (context) => const UpdatePassword(),
'/phone_sign_in': (context) => const PhoneSignIn(),
'/phone_sign_up': (context) => const PhoneSignUp(),
'/verify_phone': (context) => const VerifyPhone(),
'/home': (context) => const Home(),
},
onUnknownRoute: (RouteSettings settings) {
return MaterialPageRoute<void>(
settings: settings,
builder: (BuildContext context) => const Scaffold(
body: Center(
child: Text(
'Not Found',
style: TextStyle(
fontSize: 42,
fontWeight: FontWeight.bold,
),
),
),
),
);
},
routerConfig: appRouter,
);
}
}
Loading