[READ ONLY MIRROR] Open Source TikTok alternative built on AT Protocol github.com/sprksocial/client
flutter atproto video dart
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

persist session on shared preferences

+114 -12
+32 -6
lib/screens/splash_screen.dart
··· 1 1 import 'dart:async'; 2 2 import 'package:flutter/cupertino.dart'; 3 + import 'package:provider/provider.dart'; 4 + import '../services/auth_service.dart'; 3 5 import '../utils/app_colors.dart'; 4 6 5 7 class SplashScreen extends StatefulWidget { ··· 30 32 31 33 _animationController.forward(); 32 34 33 - // Simply go to the main screen after a delay 34 - Timer(const Duration(seconds: 3), () { 35 - if (mounted) { 36 - Navigator.of(context).pushReplacementNamed('/home'); 37 - } 38 - }); 35 + // Check if user is already authenticated 36 + _checkAuthentication(); 37 + } 38 + 39 + Future<void> _checkAuthentication() async { 40 + // Wait for animations to complete 41 + await Future.delayed(const Duration(seconds: 2)); 42 + 43 + if (!mounted) return; 44 + 45 + final authService = Provider.of<AuthService>(context, listen: false); 46 + 47 + // Wait for auth service to finish loading saved session 48 + while (authService.isLoading) { 49 + await Future.delayed(const Duration(milliseconds: 100)); 50 + if (!mounted) return; 51 + } 52 + 53 + // Check if session is valid 54 + final bool isSessionValid = await authService.validateSession(); 55 + 56 + if (!mounted) return; 57 + 58 + if (isSessionValid) { 59 + // User is authenticated, go to home 60 + Navigator.of(context).pushReplacementNamed('/home'); 61 + } else { 62 + // User is not authenticated, go to login 63 + Navigator.of(context).pushReplacementNamed('/auth'); 64 + } 39 65 } 40 66 41 67 @override
+82 -6
lib/services/auth_service.dart
··· 1 1 import 'package:atproto/atproto.dart'; 2 + import 'package:atproto/core.dart'; 2 3 import 'package:flutter/foundation.dart'; 3 4 import 'dart:convert'; 4 5 import 'package:http/http.dart' as http; 6 + import 'package:shared_preferences/shared_preferences.dart'; 5 7 6 8 class AuthService extends ChangeNotifier { 7 - dynamic _session; 9 + Session? _session; 8 10 bool _isLoading = false; 9 11 String? _error; 10 12 ATProto? _atProto; 13 + static const String _sessionKey = 'user_session'; 11 14 12 15 // Getters 13 16 bool get isAuthenticated => _session != null; 14 17 bool get isLoading => _isLoading; 15 18 String? get error => _error; 16 - dynamic get session => _session; 19 + Session? get session => _session; 17 20 ATProto? get atproto => _atProto; 18 21 22 + // Constructor to initialize and check for saved session 23 + AuthService() { 24 + _loadSavedSession(); 25 + } 26 + 27 + // Load saved session from SharedPreferences 28 + Future<void> _loadSavedSession() async { 29 + _isLoading = true; 30 + notifyListeners(); 31 + 32 + try { 33 + final prefs = await SharedPreferences.getInstance(); 34 + final savedSessionJson = prefs.getString(_sessionKey); 35 + 36 + if (savedSessionJson != null) { 37 + Map<String, dynamic> savedSession = json.decode(savedSessionJson); 38 + _session = Session.fromJson(savedSession); 39 + if (_session != null) { 40 + _atProto = ATProto.fromSession(_session!); 41 + } 42 + } 43 + } catch (e) { 44 + _error = 'Failed to load saved session: ${e.toString()}'; 45 + } finally { 46 + _isLoading = false; 47 + notifyListeners(); 48 + } 49 + } 50 + 51 + // Save session to SharedPreferences 52 + Future<void> _saveSession(Session sessionData) async { 53 + try { 54 + final prefs = await SharedPreferences.getInstance(); 55 + final sessionJson = sessionData.toJson(); 56 + await prefs.setString(_sessionKey, json.encode(sessionJson)); 57 + } catch (e) { 58 + _error = 'Failed to save session: ${e.toString()}'; 59 + notifyListeners(); 60 + } 61 + } 62 + 63 + // Clear saved session from SharedPreferences 64 + Future<void> _clearSavedSession() async { 65 + try { 66 + final prefs = await SharedPreferences.getInstance(); 67 + await prefs.remove(_sessionKey); 68 + } catch (e) { 69 + _error = 'Failed to clear session: ${e.toString()}'; 70 + notifyListeners(); 71 + } 72 + } 73 + 19 74 // Login with handle and password 20 75 Future<bool> login(String handle, String password) async { 21 76 _isLoading = true; ··· 35 90 ); 36 91 37 92 if (didDocResponse.statusCode != 200) { 38 - print(didDocResponse); 39 93 throw Exception( 40 94 'Failed to fetch DID document: ${didDocResponse.statusCode}', 41 95 ); ··· 58 112 ); 59 113 60 114 _session = session.data; 61 - _atProto = ATProto.fromSession(_session); 115 + if (_session == null) { 116 + throw Exception('Failed to create session'); 117 + } 118 + 119 + _atProto = ATProto.fromSession(_session!); 120 + 121 + // Save session to persistent storage 122 + await _saveSession(_session!); 123 + 62 124 _isLoading = false; 63 125 notifyListeners(); 64 126 return true; ··· 77 139 78 140 try { 79 141 if (_atProto != null) { 80 - // Create a new session with the ATProto client 81 - // final atproto = ATProto.fromSession(_session); 142 + await _clearSavedSession(); 82 143 _session = null; 83 144 _atProto = null; 84 145 } ··· 87 148 } finally { 88 149 _isLoading = false; 89 150 notifyListeners(); 151 + } 152 + } 153 + 154 + // Check if session is valid 155 + Future<bool> validateSession() async { 156 + if (_atProto == null || _session == null) return false; 157 + 158 + try { 159 + // Perform a lightweight API call to check session validity 160 + await _atProto!.identity.resolveHandle(handle: _session!.handle); 161 + return true; 162 + } catch (e) { 163 + // Session is invalid, clear it 164 + await logout(); 165 + return false; 90 166 } 91 167 } 92 168