[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.

como sacar o pino do coquilho do guindaste zulaine 75

+365 -14
+3
devtools_options.yaml
··· 1 + description: This file stores settings for Dart & Flutter DevTools. 2 + documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states 3 + extensions:
+4 -12
lib/screens/auth_prompt_screen.dart
··· 1 1 import 'package:flutter/cupertino.dart'; 2 2 import 'package:ionicons/ionicons.dart'; 3 3 import 'login_screen.dart'; 4 + import 'register_screen.dart'; 4 5 5 6 class AuthPromptScreen extends StatelessWidget { 6 7 final VoidCallback? onClose; ··· 74 75 child: CupertinoButton( 75 76 color: CupertinoColors.systemGrey6, 76 77 onPressed: () { 77 - // Register functionality will be implemented later 78 - showCupertinoDialog( 79 - context: context, 80 - builder: (context) => CupertinoAlertDialog( 81 - title: const Text('Coming Soon'), 82 - content: const Text('Registration will be available in a future update.'), 83 - actions: [ 84 - CupertinoDialogAction( 85 - child: const Text('OK'), 86 - onPressed: () => Navigator.of(context).pop(), 87 - ), 88 - ], 78 + Navigator.of(context).push( 79 + CupertinoPageRoute( 80 + builder: (context) => const RegisterScreen(), 89 81 ), 90 82 ); 91 83 },
+297
lib/screens/register_screen.dart
··· 1 + import 'package:flutter/cupertino.dart'; 2 + import 'package:flutter/material.dart'; 3 + import 'package:ionicons/ionicons.dart'; 4 + import 'package:provider/provider.dart'; 5 + import '../services/auth_service.dart'; 6 + 7 + class RegisterScreen extends StatefulWidget { 8 + const RegisterScreen({super.key}); 9 + 10 + @override 11 + State<RegisterScreen> createState() => _RegisterScreenState(); 12 + } 13 + 14 + class _RegisterScreenState extends State<RegisterScreen> { 15 + final TextEditingController _emailController = TextEditingController(); 16 + final TextEditingController _handleController = TextEditingController(); 17 + final TextEditingController _passwordController = TextEditingController(); 18 + final TextEditingController _inviteCodeController = TextEditingController(); 19 + 20 + bool _isPasswordVisible = false; 21 + bool _isRegistering = false; 22 + String? _errorMessage; 23 + 24 + @override 25 + void dispose() { 26 + _emailController.dispose(); 27 + _handleController.dispose(); 28 + _passwordController.dispose(); 29 + _inviteCodeController.dispose(); 30 + super.dispose(); 31 + } 32 + 33 + Future<void> _register() async { 34 + setState(() { 35 + _isRegistering = true; 36 + _errorMessage = null; 37 + }); 38 + 39 + final authService = Provider.of<AuthService>(context, listen: false); 40 + 41 + // Get the handle and add the domain suffix 42 + final String handle = "${_handleController.text}.sprk.so"; 43 + 44 + final success = await authService.register( 45 + handle, 46 + _emailController.text, 47 + _passwordController.text, 48 + _inviteCodeController.text.isEmpty ? null : _inviteCodeController.text, 49 + ); 50 + 51 + setState(() { 52 + _isRegistering = false; 53 + }); 54 + 55 + if (success) { 56 + if (mounted) { 57 + Navigator.of(context).pushReplacementNamed('/home'); 58 + } 59 + } else { 60 + setState(() { 61 + _errorMessage = authService.error; 62 + }); 63 + } 64 + } 65 + 66 + bool _isFormValid() { 67 + return _emailController.text.isNotEmpty && 68 + _handleController.text.isNotEmpty && 69 + _passwordController.text.isNotEmpty; 70 + } 71 + 72 + @override 73 + Widget build(BuildContext context) { 74 + return CupertinoPageScaffold( 75 + backgroundColor: CupertinoColors.systemBackground, 76 + navigationBar: CupertinoNavigationBar( 77 + backgroundColor: CupertinoColors.systemBackground, 78 + border: null, 79 + leading: CupertinoButton( 80 + padding: EdgeInsets.zero, 81 + onPressed: () => Navigator.of(context).pop(), 82 + child: const Icon(Ionicons.chevron_back), 83 + ), 84 + middle: const Text('Create Account'), 85 + ), 86 + child: SafeArea( 87 + child: GestureDetector( 88 + onTap: () => FocusScope.of(context).unfocus(), 89 + child: SingleChildScrollView( 90 + child: Padding( 91 + padding: const EdgeInsets.all(24.0), 92 + child: Column( 93 + crossAxisAlignment: CrossAxisAlignment.start, 94 + children: [ 95 + const SizedBox(height: 20), 96 + // Email Field 97 + _buildLabel('Email'), 98 + const SizedBox(height: 8), 99 + CupertinoTextField( 100 + controller: _emailController, 101 + placeholder: 'Your email address', 102 + keyboardType: TextInputType.emailAddress, 103 + padding: const EdgeInsets.all(16), 104 + decoration: BoxDecoration( 105 + color: CupertinoColors.systemGrey6, 106 + borderRadius: BorderRadius.circular(12), 107 + ), 108 + prefix: const Padding( 109 + padding: EdgeInsets.only(left: 16), 110 + child: Icon(Ionicons.mail_outline, color: CupertinoColors.systemGrey), 111 + ), 112 + onChanged: (_) => setState(() {}), 113 + ), 114 + 115 + const SizedBox(height: 24), 116 + 117 + // Handle Field with .sprk.so suffix 118 + _buildLabel('Username'), 119 + const SizedBox(height: 8), 120 + Container( 121 + decoration: BoxDecoration( 122 + color: CupertinoColors.systemGrey6, 123 + borderRadius: BorderRadius.circular(12), 124 + ), 125 + child: Row( 126 + children: [ 127 + const Padding( 128 + padding: EdgeInsets.only(left: 16), 129 + child: Icon(Ionicons.at_outline, color: CupertinoColors.systemGrey), 130 + ), 131 + Expanded( 132 + child: CupertinoTextField( 133 + controller: _handleController, 134 + placeholder: 'username', 135 + padding: const EdgeInsets.all(16), 136 + decoration: const BoxDecoration( 137 + color: CupertinoColors.systemGrey6, 138 + borderRadius: BorderRadius.zero, 139 + ), 140 + onChanged: (_) => setState(() {}), 141 + ), 142 + ), 143 + Container( 144 + padding: const EdgeInsets.only(right: 16), 145 + child: Text( 146 + '.sprk.so', 147 + style: TextStyle( 148 + fontSize: 16, 149 + color: CupertinoColors.systemGrey.resolveFrom(context), 150 + ), 151 + ), 152 + ), 153 + ], 154 + ), 155 + ), 156 + 157 + const SizedBox(height: 24), 158 + 159 + // Password Field 160 + _buildLabel('Password'), 161 + const SizedBox(height: 8), 162 + CupertinoTextField( 163 + controller: _passwordController, 164 + placeholder: 'Your password', 165 + obscureText: !_isPasswordVisible, 166 + padding: const EdgeInsets.all(16), 167 + decoration: BoxDecoration( 168 + color: CupertinoColors.systemGrey6, 169 + borderRadius: BorderRadius.circular(12), 170 + ), 171 + prefix: const Padding( 172 + padding: EdgeInsets.only(left: 16), 173 + child: Icon(Ionicons.lock_closed_outline, color: CupertinoColors.systemGrey), 174 + ), 175 + suffix: CupertinoButton( 176 + padding: const EdgeInsets.only(right: 8), 177 + onPressed: () { 178 + setState(() { 179 + _isPasswordVisible = !_isPasswordVisible; 180 + }); 181 + }, 182 + child: Icon( 183 + _isPasswordVisible 184 + ? Ionicons.eye_off_outline 185 + : Ionicons.eye_outline, 186 + color: CupertinoColors.systemGrey, 187 + ), 188 + ), 189 + onChanged: (_) => setState(() {}), 190 + ), 191 + 192 + const SizedBox(height: 24), 193 + 194 + // Invite Code Field (Optional) 195 + _buildLabel('Invite Code (Optional)'), 196 + const SizedBox(height: 8), 197 + CupertinoTextField( 198 + controller: _inviteCodeController, 199 + placeholder: 'Enter invite code if you have one', 200 + padding: const EdgeInsets.all(16), 201 + decoration: BoxDecoration( 202 + color: CupertinoColors.systemGrey6, 203 + borderRadius: BorderRadius.circular(12), 204 + ), 205 + prefix: const Padding( 206 + padding: EdgeInsets.only(left: 16), 207 + child: Icon(Ionicons.ticket_outline, color: CupertinoColors.systemGrey), 208 + ), 209 + ), 210 + 211 + if (_errorMessage != null) ...[ 212 + const SizedBox(height: 24), 213 + Container( 214 + padding: const EdgeInsets.all(16), 215 + decoration: BoxDecoration( 216 + color: CupertinoColors.systemRed.withOpacity(0.1), 217 + borderRadius: BorderRadius.circular(12), 218 + ), 219 + child: Row( 220 + children: [ 221 + const Icon( 222 + Ionicons.alert_circle_outline, 223 + color: CupertinoColors.systemRed, 224 + ), 225 + const SizedBox(width: 8), 226 + Expanded( 227 + child: Text( 228 + _errorMessage!, 229 + style: const TextStyle( 230 + color: CupertinoColors.systemRed, 231 + ), 232 + ), 233 + ), 234 + ], 235 + ), 236 + ), 237 + ], 238 + 239 + const SizedBox(height: 40), 240 + 241 + // Register Button 242 + SizedBox( 243 + width: double.infinity, 244 + child: CupertinoButton( 245 + padding: const EdgeInsets.symmetric(vertical: 16), 246 + color: CupertinoColors.systemPink, 247 + borderRadius: BorderRadius.circular(12), 248 + onPressed: _isFormValid() && !_isRegistering 249 + ? _register 250 + : null, 251 + child: _isRegistering 252 + ? const CupertinoActivityIndicator(color: CupertinoColors.white) 253 + : const Text( 254 + 'Create Account', 255 + style: TextStyle( 256 + fontSize: 16, 257 + fontWeight: FontWeight.bold, 258 + ), 259 + ), 260 + ), 261 + ), 262 + 263 + const SizedBox(height: 24), 264 + 265 + Row( 266 + mainAxisAlignment: MainAxisAlignment.center, 267 + children: [ 268 + const Text( 269 + 'Already have an account?', 270 + style: TextStyle(color: CupertinoColors.systemGrey), 271 + ), 272 + CupertinoButton( 273 + padding: const EdgeInsets.only(left: 8), 274 + onPressed: () => Navigator.of(context).pop(), 275 + child: const Text('Sign in'), 276 + ), 277 + ], 278 + ), 279 + ], 280 + ), 281 + ), 282 + ), 283 + ), 284 + ), 285 + ); 286 + } 287 + 288 + Widget _buildLabel(String text) { 289 + return Text( 290 + text, 291 + style: const TextStyle( 292 + fontSize: 16, 293 + fontWeight: FontWeight.w500, 294 + ), 295 + ); 296 + } 297 + }
+61 -2
lib/services/auth_service.dart
··· 104 104 throw Exception('PDS endpoint not found in DID document'); 105 105 } 106 106 107 - String pdsDomain = pdsUrl.replaceFirst('http://', '').replaceFirst('https://', '').replaceFirst('/', ''); 107 + String pdsDomain = pdsUrl 108 + .replaceFirst('http://', '') 109 + .replaceFirst('https://', '') 110 + .replaceFirst('/', ''); 108 111 final session = await createSession( 109 112 identifier: handle, 110 113 password: password, ··· 132 135 } 133 136 } 134 137 138 + Future<bool> register( 139 + String handle, 140 + String email, 141 + String password, 142 + String? inviteCode, 143 + ) async { 144 + _isLoading = true; 145 + _error = null; 146 + notifyListeners(); 147 + 148 + try { 149 + ATProto at = ATProto.anonymous(service: 'pds.sprk.so'); 150 + final createResponse = await at.server.createAccount( 151 + handle: handle, 152 + email: email, 153 + password: password, 154 + inviteCode: inviteCode, 155 + ); 156 + if (createResponse.status != HttpStatus.ok) { 157 + throw Exception('Failed to create account: ${createResponse.status}'); 158 + } 159 + 160 + Session session = Session.fromJson({ 161 + 'did': createResponse.data.did, 162 + 'handle': handle, 163 + 'email': email, 164 + 'emailConfirmed': false, 165 + 'accessJwt': createResponse.data.accessJwt, 166 + 'refreshJwt': createResponse.data.refreshJwt, 167 + 'didDoc': createResponse.data.didDoc, 168 + 'active': true, 169 + }); 170 + 171 + _session = session; 172 + if (_session == null) { 173 + throw Exception('Failed to create session'); 174 + } 175 + 176 + _atProto = ATProto.fromSession(_session!); 177 + 178 + // Save session to persistent storage 179 + await _saveSession(_session!); 180 + 181 + _isLoading = false; 182 + notifyListeners(); 183 + return true; 184 + } catch (e) { 185 + _error = e.toString(); 186 + _isLoading = false; 187 + notifyListeners(); 188 + return false; 189 + } 190 + } 191 + 135 192 // Logout 136 193 Future<void> logout() async { 137 194 _isLoading = true; ··· 171 228 if (_atProto == null) return null; 172 229 173 230 try { 174 - final response = await _atProto!.repo.getRecord(uri: AtUri.parse('at://${_session!.did}/app.bsky.actor.profile/self')); 231 + final response = await _atProto!.repo.getRecord( 232 + uri: AtUri.parse('at://${_session!.did}/app.bsky.actor.profile/self'), 233 + ); 175 234 return response.data.toJson(); 176 235 } catch (e) { 177 236 _error = e.toString();