revert from appwrite, no sdk atm
This commit is contained in:
parent
8b42ea43b7
commit
867f9af9d5
196
OAUTH_BLOG_POST.md
Normal file
196
OAUTH_BLOG_POST.md
Normal file
|
|
@ -0,0 +1,196 @@
|
||||||
|
# The OAuth Authentication Nightmare: Why I'm Considering Moving from Appwrite to Supabase
|
||||||
|
|
||||||
|
*A developer's journey through mobile OAuth hell and the quest for better solutions*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Problem That Started It All
|
||||||
|
|
||||||
|
I was building TaskTracker, a Flutter mobile application, and decided to use Appwrite as my Backend-as-a-Service (BaaS). Everything seemed promising - great documentation, modern API, and OAuth support out of the box. What could go wrong?
|
||||||
|
|
||||||
|
**Everything.**
|
||||||
|
|
||||||
|
After successfully implementing email/password authentication, I moved on to Google OAuth for a better user experience. The implementation seemed straightforward - just call `createOAuth2Session()` and you're done, right?
|
||||||
|
|
||||||
|
Wrong.
|
||||||
|
|
||||||
|
## The Mysterious ImeTracker Error
|
||||||
|
|
||||||
|
After clicking the Google OAuth button, Chrome Custom Tabs would open (not the native Android Google Sign-In UI, which was my first red flag). I'd select my Google account, authenticate successfully, and then... nothing. The browser would close, and I'd see this cryptic error in the logs:
|
||||||
|
|
||||||
|
```
|
||||||
|
I/ImeTracker( 5434): com.Xperience.TaskTracker.tasktracker:efd1ad6a:
|
||||||
|
onCancelled at PHASE_CLIENT_ALREADY_HIDDEN
|
||||||
|
```
|
||||||
|
|
||||||
|
The error message suggested I had cancelled the authentication, but I hadn't touched anything. The OAuth flow was completing on Google's end, but my app wasn't receiving the callback properly.
|
||||||
|
|
||||||
|
## The Troubleshooting Marathon
|
||||||
|
|
||||||
|
### Attempt #1: Explicit Callback URLs
|
||||||
|
|
||||||
|
My first thought was that the callback URLs needed to be explicitly defined. I tried specifying success and failure URLs:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
await widget.account.createOAuth2Session(
|
||||||
|
provider: provider,
|
||||||
|
success: 'appwrite-callback-tasktracker://success',
|
||||||
|
failure: 'appwrite-callback-tasktracker://failure',
|
||||||
|
scopes: ['email', 'profile'],
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Result:** Even worse. I got a new error:
|
||||||
|
|
||||||
|
```
|
||||||
|
Invalid success param, URL host must be one of localhost, supab.playpoolstudios.com
|
||||||
|
```
|
||||||
|
|
||||||
|
So Appwrite's server was rejecting my custom URL scheme entirely. This made no sense for a mobile application where custom URL schemes are the standard for OAuth callbacks.
|
||||||
|
|
||||||
|
### Attempt #2: Android Manifest Configuration
|
||||||
|
|
||||||
|
Maybe the issue was with my Android configuration? I updated the `AndroidManifest.xml` to be more explicit about handling OAuth callbacks:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<intent-filter android:label="flutter_web_auth_2" android:autoVerify="true">
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="appwrite-callback-tasktracker" android:host="success" />
|
||||||
|
<data android:scheme="appwrite-callback-tasktracker" android:host="failure" />
|
||||||
|
</intent-filter>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Result:** Still nothing. The callback wasn't being intercepted properly.
|
||||||
|
|
||||||
|
### Attempt #3: Appwrite Console Platform Configuration
|
||||||
|
|
||||||
|
Perhaps I needed to register my Android app as a platform in the Appwrite Console? I went through the process:
|
||||||
|
- Added Android platform
|
||||||
|
- Entered package name: `com.Xperience.TaskTracker.tasktracker`
|
||||||
|
- Configured OAuth provider settings
|
||||||
|
|
||||||
|
**Result:** No improvement. The OAuth flow still failed silently.
|
||||||
|
|
||||||
|
### Attempt #4: The GitHub Workaround
|
||||||
|
|
||||||
|
After hours of searching, I found a GitHub issue with a workaround suggesting a two-step OAuth process:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Step 1: Manually trigger OAuth with flutter_web_auth_2
|
||||||
|
final result = await FlutterWebAuth2.authenticate(
|
||||||
|
url: '$endpoint/account/sessions/oauth2/$provider?project=$projectId',
|
||||||
|
callbackUrlScheme: 'appwrite-callback-$projectId',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Step 2: Create Appwrite session
|
||||||
|
await widget.account.createOAuth2Session(provider: provider);
|
||||||
|
```
|
||||||
|
|
||||||
|
This workaround essentially bypasses Appwrite's OAuth handling by manually triggering the web authentication, then trying to create a session afterward.
|
||||||
|
|
||||||
|
**Result:** Still testing, but the fact that such a workaround exists is telling.
|
||||||
|
|
||||||
|
## The Fundamental Issues
|
||||||
|
|
||||||
|
After all this troubleshooting, I've identified several core problems with Appwrite's mobile OAuth implementation:
|
||||||
|
|
||||||
|
### 1. **Poor Mobile-First Design**
|
||||||
|
Appwrite's OAuth is clearly designed for web applications. The restriction that callback URLs must use the server's domain (`supab.playpoolstudios.com`) makes no sense for mobile apps that need custom URL schemes like `appwrite-callback-*://`.
|
||||||
|
|
||||||
|
### 2. **Chrome Custom Tabs Instead of Native UI**
|
||||||
|
On Android, the OAuth flow opens Chrome Custom Tabs instead of the native Google Sign-In UI. This creates a clunky user experience and introduces unnecessary complexity with session management between the browser and the app.
|
||||||
|
|
||||||
|
### 3. **Silent Failures**
|
||||||
|
The OAuth process fails silently with no meaningful error messages. The `ImeTracker` error is just a symptom - a side effect of the keyboard state when the browser closes - not the actual problem.
|
||||||
|
|
||||||
|
### 4. **Lack of Documentation**
|
||||||
|
The official Appwrite documentation doesn't cover mobile OAuth edge cases, troubleshooting steps, or known issues. I had to dig through GitHub issues to find any information.
|
||||||
|
|
||||||
|
### 5. **Workarounds Required**
|
||||||
|
The fact that a two-step workaround exists (and is recommended in GitHub issues) suggests this is a known problem that hasn't been properly fixed.
|
||||||
|
|
||||||
|
## Why I'm Looking at Supabase
|
||||||
|
|
||||||
|
After spending hours (days?) on this issue, I started researching alternatives. Supabase keeps coming up, and here's why it's appealing:
|
||||||
|
|
||||||
|
### 1. **PostgreSQL Foundation**
|
||||||
|
Supabase is built on PostgreSQL, a mature, battle-tested database. This means:
|
||||||
|
- Better query capabilities
|
||||||
|
- Mature ecosystem
|
||||||
|
- Reliable data integrity
|
||||||
|
- Easy migration path if needed
|
||||||
|
|
||||||
|
### 2. **Better OAuth Documentation**
|
||||||
|
Supabase has extensive documentation for mobile OAuth, including Flutter-specific guides and examples. They support:
|
||||||
|
- Deep linking configuration
|
||||||
|
- Platform-specific setup guides
|
||||||
|
- Native SDK implementations
|
||||||
|
- Working code examples
|
||||||
|
|
||||||
|
### 3. **Active Community Support**
|
||||||
|
The Supabase community is larger and more active. Issues get addressed faster, and there are more third-party tutorials and resources available.
|
||||||
|
|
||||||
|
### 4. **Row Level Security (RLS)**
|
||||||
|
Supabase's RLS is more powerful and flexible than Appwrite's permissions system, giving you fine-grained control over data access at the database level.
|
||||||
|
|
||||||
|
### 5. **Edge Functions with Deno**
|
||||||
|
Supabase Edge Functions run on Deno, which is modern, secure, and has better TypeScript support than Appwrite's functions.
|
||||||
|
|
||||||
|
### 6. **Transparent Pricing**
|
||||||
|
Supabase has clearer pricing and a more generous free tier for development and testing.
|
||||||
|
|
||||||
|
## The Verdict
|
||||||
|
|
||||||
|
I wanted to love Appwrite. The API is clean, the dashboard is beautiful, and the promise of an open-source Firebase alternative is compelling. But when basic functionality like mobile OAuth doesn't work reliably, it becomes a blocker for production applications.
|
||||||
|
|
||||||
|
**The problems I encountered are not edge cases** - mobile OAuth is a fundamental feature for modern apps. The fact that it requires workarounds and extensive troubleshooting suggests that Appwrite isn't ready for serious mobile development.
|
||||||
|
|
||||||
|
### What Appwrite Needs to Fix:
|
||||||
|
|
||||||
|
1. **Native mobile OAuth support** with proper deep linking
|
||||||
|
2. **Better error messages** that actually help developers diagnose issues
|
||||||
|
3. **Comprehensive mobile documentation** with troubleshooting guides
|
||||||
|
4. **Native SDK improvements** for mobile platforms
|
||||||
|
5. **Faster issue resolution** and community support
|
||||||
|
|
||||||
|
### The Supabase Migration Plan:
|
||||||
|
|
||||||
|
If I decide to switch (which I'm seriously considering), the migration would involve:
|
||||||
|
|
||||||
|
1. **Data Migration**: Export data from Appwrite, transform to PostgreSQL format
|
||||||
|
2. **Auth Migration**: Implement Supabase Auth with Flutter
|
||||||
|
3. **Real-time Features**: Switch to Supabase's real-time subscriptions
|
||||||
|
4. **File Storage**: Migrate to Supabase Storage
|
||||||
|
5. **Functions**: Rewrite cloud functions as Supabase Edge Functions
|
||||||
|
|
||||||
|
Is it worth the effort? Given the time I've already wasted on OAuth alone, probably yes.
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
Appwrite has potential, but it's not production-ready for mobile applications that require OAuth authentication. The lack of proper mobile support, combined with silent failures and poor documentation, makes it a risky choice for serious projects.
|
||||||
|
|
||||||
|
**Supabase, while not perfect, offers a more mature and mobile-friendly solution** with better documentation, active community support, and proven reliability.
|
||||||
|
|
||||||
|
For fellow developers considering their BaaS options: **test your critical features early**. Don't commit to a platform until you've verified that core functionality like authentication actually works for your use case.
|
||||||
|
|
||||||
|
As for me, I'll probably start a Supabase proof-of-concept this weekend. If it goes well, TaskTracker will be migrating sooner rather than later.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Update
|
||||||
|
|
||||||
|
If you're experiencing similar issues with Appwrite OAuth, here are some resources that might help:
|
||||||
|
|
||||||
|
- [Appwrite GitHub Issues](https://github.com/appwrite/appwrite/issues) - Search for mobile OAuth problems
|
||||||
|
- [Supabase Flutter Documentation](https://supabase.com/docs/guides/getting-started/tutorials/with-flutter)
|
||||||
|
- [Flutter Web Auth 2 Package](https://pub.dev/packages/flutter_web_auth_2) - The underlying package Appwrite uses
|
||||||
|
|
||||||
|
**Have you experienced similar issues?** Share your experience in the comments. Are you using Appwrite or Supabase? What has your experience been?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Written by a frustrated developer who just wants OAuth to work*
|
||||||
|
*Date: October 13, 2025*
|
||||||
|
|
||||||
|
|
@ -6,8 +6,7 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="standard"
|
||||||
android:taskAffinity=""
|
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
|
|
@ -24,6 +23,12 @@
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter android:label="flutter_web_auth_2">
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="appwrite-callback-tasktracker" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,25 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'screens/auth_screen.dart';
|
import 'screens/auth_screen.dart';
|
||||||
|
|
||||||
|
import 'package:appwrite/appwrite.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const TaskTrackerApp());
|
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
Client client = Client()
|
||||||
|
.setEndpoint("https://supab.playpoolstudios.com/v1")
|
||||||
|
.setProject("tasktracker");
|
||||||
|
|
||||||
|
Account account = Account(client);
|
||||||
|
|
||||||
|
runApp(TaskTrackerApp(account: account, client: client));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TaskTrackerApp extends StatelessWidget {
|
class TaskTrackerApp extends StatelessWidget {
|
||||||
const TaskTrackerApp({super.key});
|
final Account account;
|
||||||
|
final Client client;
|
||||||
|
const TaskTrackerApp({required this.account, required this.client, super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
@ -68,7 +81,7 @@ class TaskTrackerApp extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
home: const AuthScreen(),
|
home: AuthScreen(account: account, client: client),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ class AuthScreen extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AuthScreenState extends State<AuthScreen>
|
class _AuthScreenState extends State<AuthScreen>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
late TabController _tabController;
|
late TabController _tabController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -20,17 +20,118 @@ class _AuthScreenState extends State<AuthScreen>
|
||||||
_tabController = TabController(length: 2, vsync: this);
|
_tabController = TabController(length: 2, vsync: this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_tabController.dispose();
|
_tabController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> login(String email, String password) async {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> register(String email, String password, String name) async {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> logout() async {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> oAuthLogin() async {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isLogged= false;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
||||||
final screenHeight = MediaQuery.of(context).size.height;
|
final screenHeight = MediaQuery.of(context).size.height;
|
||||||
|
|
||||||
|
// If user is logged in, show logout screen
|
||||||
|
if (isLogged) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.grey[50],
|
||||||
|
body: SafeArea(
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Theme.of(context).primaryColor.withValues(alpha: 0.3),
|
||||||
|
blurRadius: 20,
|
||||||
|
offset: const Offset(0, 10),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.task_alt,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Text(
|
||||||
|
'Welcome back!',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.grey[800],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
"username",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
"email or id",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: logout,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
elevation: 2,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Logout',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.grey[50],
|
backgroundColor: Colors.grey[50],
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
|
|
@ -132,8 +233,14 @@ class _AuthScreenState extends State<AuthScreen>
|
||||||
child: TabBarView(
|
child: TabBarView(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
children: [
|
children: [
|
||||||
LoginScreen(),
|
LoginScreen(
|
||||||
RegisterScreen(),
|
onLogin: login,
|
||||||
|
onOAuthLogin: () => oAuthLogin(),
|
||||||
|
),
|
||||||
|
RegisterScreen(
|
||||||
|
onRegister: register,
|
||||||
|
onOAuthRegister: () => oAuthLogin(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,18 @@
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import '../widgets/oauth_button.dart';
|
import '../widgets/oauth_button.dart';
|
||||||
import '../widgets/custom_text_field.dart';
|
import '../widgets/custom_text_field.dart';
|
||||||
|
|
||||||
class LoginScreen extends StatefulWidget {
|
class LoginScreen extends StatefulWidget {
|
||||||
const LoginScreen({super.key});
|
const LoginScreen({
|
||||||
|
required this.onLogin,
|
||||||
|
required this.onOAuthLogin,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Future<void> Function(String email, String password) onLogin;
|
||||||
|
final void Function() onOAuthLogin;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<LoginScreen> createState() => _LoginScreenState();
|
State<LoginScreen> createState() => _LoginScreenState();
|
||||||
|
|
@ -24,32 +32,43 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleLogin() {
|
void _handleLogin() async {
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Implement Appwrite login logic here
|
try {
|
||||||
// For now, just simulate loading
|
await widget.onLogin(_emailController.text, _passwordController.text);
|
||||||
Future.delayed(const Duration(seconds: 2), () {
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Login successful!'),
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Login failed: ${e.toString()}'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
});
|
});
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(content: Text('Login functionality will be implemented with Appwrite')),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleOAuthLogin(String provider) {
|
void _handleOAuthLogin( ) {
|
||||||
// TODO: Implement OAuth login with Appwrite
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text('$provider OAuth login will be implemented with Appwrite')),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -199,7 +218,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||||
OAuthButton(
|
OAuthButton(
|
||||||
provider: 'Google',
|
provider: 'Google',
|
||||||
icon: Icons.g_mobiledata,
|
icon: Icons.g_mobiledata,
|
||||||
onPressed: () => _handleOAuthLogin('Google'),
|
onPressed: () => _handleOAuthLogin(),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
@ -207,7 +226,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||||
OAuthButton(
|
OAuthButton(
|
||||||
provider: 'Facebook',
|
provider: 'Facebook',
|
||||||
icon: Icons.facebook,
|
icon: Icons.facebook,
|
||||||
onPressed: () => _handleOAuthLogin('Facebook'),
|
onPressed: () => _handleOAuthLogin(),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
@ -215,7 +234,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||||
OAuthButton(
|
OAuthButton(
|
||||||
provider: 'GitHub',
|
provider: 'GitHub',
|
||||||
icon: Icons.code,
|
icon: Icons.code,
|
||||||
onPressed: () => _handleOAuthLogin('GitHub'),
|
onPressed: () => _handleOAuthLogin(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,14 @@ import '../widgets/oauth_button.dart';
|
||||||
import '../widgets/custom_text_field.dart';
|
import '../widgets/custom_text_field.dart';
|
||||||
|
|
||||||
class RegisterScreen extends StatefulWidget {
|
class RegisterScreen extends StatefulWidget {
|
||||||
const RegisterScreen({super.key});
|
const RegisterScreen({
|
||||||
|
required this.onRegister,
|
||||||
|
required this.onOAuthRegister,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Future<void> Function(String email, String password, String name) onRegister;
|
||||||
|
final void Function() onOAuthRegister;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<RegisterScreen> createState() => _RegisterScreenState();
|
State<RegisterScreen> createState() => _RegisterScreenState();
|
||||||
|
|
@ -30,24 +37,42 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleRegister() {
|
void _handleRegister() async {
|
||||||
if (_formKey.currentState!.validate() && _agreeToTerms) {
|
if (_formKey.currentState!.validate() && _agreeToTerms) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Implement Appwrite registration logic here
|
try {
|
||||||
// For now, just simulate loading
|
await widget.onRegister(
|
||||||
Future.delayed(const Duration(seconds: 2), () {
|
_emailController.text,
|
||||||
|
_passwordController.text,
|
||||||
|
_nameController.text,
|
||||||
|
);
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Registration successful!'),
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Registration failed: ${e.toString()}'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
});
|
});
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(content: Text('Registration functionality will be implemented with Appwrite')),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
} else if (!_agreeToTerms) {
|
} else if (!_agreeToTerms) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(content: Text('Please agree to the terms and conditions')),
|
const SnackBar(content: Text('Please agree to the terms and conditions')),
|
||||||
|
|
@ -55,11 +80,8 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleOAuthRegister(String provider) {
|
void _handleOAuthRegister() {
|
||||||
// TODO: Implement OAuth registration with Appwrite
|
widget.onOAuthRegister();
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text('$provider OAuth registration will be implemented with Appwrite')),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -284,7 +306,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
||||||
OAuthButton(
|
OAuthButton(
|
||||||
provider: 'Google',
|
provider: 'Google',
|
||||||
icon: Icons.g_mobiledata,
|
icon: Icons.g_mobiledata,
|
||||||
onPressed: () => _handleOAuthRegister('Google'),
|
onPressed: () => _handleOAuthRegister(),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
@ -292,7 +314,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
||||||
OAuthButton(
|
OAuthButton(
|
||||||
provider: 'Facebook',
|
provider: 'Facebook',
|
||||||
icon: Icons.facebook,
|
icon: Icons.facebook,
|
||||||
onPressed: () => _handleOAuthRegister('Facebook'),
|
onPressed: () => _handleOAuthRegister(),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
@ -300,7 +322,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
||||||
OAuthButton(
|
OAuthButton(
|
||||||
provider: 'GitHub',
|
provider: 'GitHub',
|
||||||
icon: Icons.code,
|
icon: Icons.code,
|
||||||
onPressed: () => _handleOAuthRegister('GitHub'),
|
onPressed: () => _handleOAuthRegister(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,10 @@
|
||||||
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import 'package:tasktracker/main.dart';
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('TaskTracker app smoke test', (WidgetTester tester) async {
|
testWidgets('TaskTracker app smoke test', (WidgetTester tester) async {
|
||||||
// Build our app and trigger a frame.
|
// Build our app and trigger a frame.
|
||||||
await tester.pumpWidget(const TaskTrackerApp());
|
|
||||||
|
|
||||||
// Verify that our app shows the TaskTracker title
|
// Verify that our app shows the TaskTracker title
|
||||||
expect(find.text('TaskTracker'), findsOneWidget);
|
expect(find.text('TaskTracker'), findsOneWidget);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user