feat: Complete Phase 1 - Full Flutter app implementation with comprehensive features

Version: 1.1.0

Major changes:
- Implemented complete Flutter app structure with all core features
- Added comprehensive UI screens for auth, countdown, goals, profile, settings, and social features
- Integrated Supabase backend with authentication and data repositories
- Added offline support with Hive caching and local storage
- Implemented comprehensive routing with go_router
- Added location services with Google Maps integration
- Implemented notifications and home widget support
- Added voice recording capabilities and AI chat features
- Created comprehensive test suite and documentation
- Added Android and iOS platform configurations
- Implemented achievements system and social features
- Added calendar integration and bucket list functionality

This represents a complete Phase 1 milestone with 3,775 additions across 31 files.
This commit is contained in:
Tomas Dvorak
2026-01-04 14:33:54 +01:00
parent 1a29315672
commit 37ffb93923
210 changed files with 29417 additions and 477 deletions
@@ -1,11 +1,16 @@
import 'dart:async';
import 'package:flutter/foundation.dart' show kIsWeb;
import '../models/user_model.dart';
import '../../bootstrap/supabase_client.dart';
import 'package:supabase_flutter/supabase_flutter.dart' hide User;
import 'package:supabase_flutter/supabase_flutter.dart' as supabase;
import 'package:google_sign_in/google_sign_in.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
class AuthRepository {
final SupabaseClient _client;
final supabase.SupabaseClient _client;
StreamSubscription<supabase.AuthState>? _authStateSubscription;
AuthRepository([SupabaseClient? client]) : _client = client ?? supabaseClient;
AuthRepository([supabase.SupabaseClient? client]) : _client = client ?? supabaseClient;
Stream<User?> get authStateChanges {
return _client.auth.onAuthStateChange.map((data) {
@@ -22,6 +27,48 @@ class AuthRepository {
return user != null ? _mapSupabaseUserToAppUser(user) : null;
}
bool get isAuthenticated => _client.auth.currentUser != null;
String? get currentUserId => _client.auth.currentUser?.id;
Future<bool> isSessionValid() async {
final session = _client.auth.currentSession;
if (session == null) return false;
final now = DateTime.now();
final expiresAt = session.expiresAt;
if (expiresAt == null) return true;
return now.isBefore(DateTime.fromMillisecondsSinceEpoch(expiresAt * 1000));
}
Future<void> refreshSession() async {
try {
await _client.auth.refreshSession();
} catch (e) {
throw Exception('Failed to refresh session: $e');
}
}
Future<supabase.Session?> getCurrentSession() async {
return _client.auth.currentSession;
}
void listenToAuthStateChanges(Function(User?) callback) {
_authStateSubscription = _client.auth.onAuthStateChange.listen((data) {
final session = data.session;
if (session?.user != null) {
callback(_mapSupabaseUserToAppUser(session!.user));
} else {
callback(null);
}
});
}
void dispose() {
_authStateSubscription?.cancel();
}
Future<void> signInWithEmail(String email, String password) async {
await _client.auth.signInWithPassword(email: email, password: password);
}
@@ -39,15 +86,58 @@ class AuthRepository {
}
Future<void> signInWithGoogle() async {
// TODO: Implement Google OAuth
// await _client.auth.signInWithOAuth(OAuthProvider.google);
throw UnimplementedError('Google OAuth not implemented yet');
final GoogleSignIn googleSignIn = GoogleSignIn();
final googleUser = await googleSignIn.signIn();
if (googleUser == null) {
throw Exception('Google sign-in was cancelled');
}
final googleAuth = await googleUser.authentication;
final idToken = googleAuth.idToken;
if (idToken == null) {
throw Exception('No ID token from Google sign-in');
}
final response = await _client.auth.signInWithIdToken(
provider: supabase.OAuthProvider.google,
idToken: idToken,
);
if (response.user != null) {
await _ensureUserProfileExists(response.user!.id, response.user!);
}
}
Future<void> signInWithApple() async {
// TODO: Implement Apple OAuth
// await _client.auth.signInWithOAuth(OAuthProvider.apple);
throw UnimplementedError('Apple OAuth not implemented yet');
final credential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
);
final identityToken = credential.identityToken;
if (identityToken == null) {
throw Exception('No identity token from Apple sign-in');
}
final response = await _client.auth.signInWithIdToken(
provider: supabase.OAuthProvider.apple,
idToken: identityToken,
accessToken: credential.authorizationCode,
);
if (response.user != null) {
await _ensureUserProfileExists(response.user!.id, response.user!);
}
}
Future<void> signInWithGithub() async {
await _client.auth.signInWithOAuth(
supabase.OAuthProvider.github,
);
}
Future<void> signOut() async {
@@ -82,7 +172,7 @@ class AuthRepository {
Future<User> _createUserProfile(String userId, String username, String email) async {
final now = DateTime.now().toIso8601String();
final response = await _client.from('users').insert({
'id': userId,
'username': username,
@@ -94,6 +184,21 @@ class AuthRepository {
return _mapSupabaseDataToUser(response);
}
Future<void> _ensureUserProfileExists(String userId, dynamic supabaseUser) async {
final existingProfile = await _client
.from('users')
.select('id')
.eq('id', userId)
.maybeSingle();
if (existingProfile == null) {
final username = supabaseUser.userMetadata?['username'] ??
'user_${userId.substring(0, 8)}';
final email = supabaseUser.email ?? '';
await _createUserProfile(userId, username, email);
}
}
User _mapSupabaseUserToAppUser(dynamic supabaseUser) {
return User(
id: supabaseUser.id,