import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:relationship_saver/core/config/app_theme.dart'; import 'package:relationship_saver/features/shared/frosted_card.dart'; import 'package:relationship_saver/integrations/backend/backend_gateway_provider.dart'; import 'package:relationship_saver/integrations/backend/models/backend_models.dart'; import 'package:uuid/uuid.dart'; class SyncView extends ConsumerStatefulWidget { const SyncView({super.key}); @override ConsumerState createState() => _SyncViewState(); } class _SyncViewState extends ConsumerState { static const Uuid _uuid = Uuid(); bool _busy = false; String _status = 'Ready. Local-first mode keeps app usable without sync.'; String? _cursor; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.fromLTRB(28, 24, 28, 28), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Sync', style: Theme.of(context).textTheme.headlineMedium), const SizedBox(height: 6), Text( 'Push local changes and pull remote updates when connectivity is available.', style: Theme.of( context, ).textTheme.bodyLarge?.copyWith(color: AppTheme.textSecondary), ), const SizedBox(height: 16), FrostedCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Wrap( spacing: 10, runSpacing: 10, children: [ FilledButton.icon( onPressed: _busy ? null : _pushSample, icon: const Icon(Icons.upload_rounded), label: const Text('Push Sample Changes'), ), OutlinedButton.icon( onPressed: _busy ? null : _pullChanges, icon: const Icon(Icons.download_rounded), label: const Text('Pull Changes'), ), ], ), const SizedBox(height: 16), Text( 'Cursor: ${_cursor ?? 'not set'}', style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: 8), Text( _status, style: Theme.of(context).textTheme.bodyLarge?.copyWith( color: AppTheme.textSecondary, ), ), ], ), ), const SizedBox(height: 16), FrostedCard( child: Text( 'Sync policy\n• Core usage never blocks on backend\n• Retries only for transient failures\n• Non-idempotent requests require idempotency key', style: Theme.of(context).textTheme.bodyLarge, ), ), ], ), ); } Future _pullChanges() async { setState(() { _busy = true; _status = 'Pulling changes...'; }); try { final SyncPullResult result = await ref .read(backendGatewayProvider) .pullChanges(cursor: _cursor); setState(() { _cursor = result.cursor; _status = 'Pulled ${result.changes.length} changes successfully.'; }); } catch (error) { setState(() { _status = 'Pull failed. Staying offline-safe. ($error)'; }); } finally { setState(() { _busy = false; }); } } Future _pushSample() async { setState(() { _busy = true; _status = 'Pushing local sample changes...'; }); final List changes = [ ChangeEnvelope( schemaVersion: 1, entityType: 'capture', entityId: 'capture-${_uuid.v4()}', op: ChangeOperation.upsert, modifiedAt: DateTime.now().toUtc(), clientMutationId: _uuid.v4(), payload: const { 'summary': 'Shared a thoughtful check-in after work.', 'sentiment': 'positive', }, ), ]; try { final SyncPushResult result = await ref .read(backendGatewayProvider) .pushChanges(changes: changes, cursor: _cursor); setState(() { _cursor = result.cursor; _status = 'Push complete. accepted=${result.accepted.length}, rejected=${result.rejected.length}'; }); } catch (error) { setState(() { _status = 'Push failed. Local data remains safe. ($error)'; }); } finally { setState(() { _busy = false; }); } } }