From 558272c9a2451c781033d0ba62a08b52191eb860 Mon Sep 17 00:00:00 2001 From: Patrick Britton Date: Sun, 1 Feb 2026 13:08:08 -0600 Subject: [PATCH] Fix reaction UI updates - prioritize local state over post model Change _reactionCountsFor and _myReactionsFor to prefer local state for immediate UI updates after toggle reactions, falling back to post model data when no local state exists. --- sojorn_app/lib/routes/app_routes.dart | 6 +- .../post/threaded_conversation_screen.dart | 77 ++++++++++++------- .../profile/viewable_profile_screen.dart | 14 ++-- sojorn_app/lib/services/api_service.dart | 3 +- .../lib/services/image_upload_service.dart | 6 +- sojorn_app/pubspec.yaml | 2 +- sojorn_app/run_chrome.bat | 2 - sojorn_app/run_chrome.ps1 | 17 +--- sojorn_app/run_dev.bat | 2 - sojorn_app/run_dev.ps1 | 17 +--- 10 files changed, 65 insertions(+), 81 deletions(-) diff --git a/sojorn_app/lib/routes/app_routes.dart b/sojorn_app/lib/routes/app_routes.dart index 6f20c56..23d7340 100644 --- a/sojorn_app/lib/routes/app_routes.dart +++ b/sojorn_app/lib/routes/app_routes.dart @@ -168,7 +168,7 @@ class AppRoutes { /// Navigate to a user profile by username static void navigateToProfile(BuildContext context, String username) { - context.go('/u/$username'); + context.push('/u/$username'); } /// Get shareable URL for a user profile @@ -203,12 +203,12 @@ class AppRoutes { static void navigateToBeacon(BuildContext context, LatLng location) { final url = '/beacon?lat=${location.latitude.toStringAsFixed(6)}&long=${location.longitude.toStringAsFixed(6)}'; - context.go(url); + context.push(url); } /// Navigate to secure chat static void navigateToSecureChat(BuildContext context) { - context.go(secureChat); + context.push(secureChat); } } diff --git a/sojorn_app/lib/screens/post/threaded_conversation_screen.dart b/sojorn_app/lib/screens/post/threaded_conversation_screen.dart index 45fcb83..2ba77d1 100644 --- a/sojorn_app/lib/screens/post/threaded_conversation_screen.dart +++ b/sojorn_app/lib/screens/post/threaded_conversation_screen.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import '../../routes/app_routes.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -106,6 +108,11 @@ class _ThreadedConversationScreenState extends ConsumerState _navigateToPost(String postId) async { if (_isTransitioning || _focusContext?.targetPost.id == postId) return; - setState(() { - _isTransitioning = true; - _error = null; - }); - - try { - final api = ref.read(apiServiceProvider); - final focusContext = await api.getPostFocusContext(postId); - if (!mounted) return; - setState(() { - _focusContext = focusContext; - }); - _seedReactionState(focusContext); - _slideController.forward(from: 0); - _fadeController.forward(from: 0); - } catch (e) { - if (!mounted) return; - setState(() { - _error = e.toString(); - }); - } finally { - if (mounted) { - setState(() => _isTransitioning = false); - } - } + + // Instead of just flipping state, we push a new route to maintain history + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => ThreadedConversationScreen(rootPostId: postId), + ), + ); } @override @@ -163,7 +152,14 @@ class _ThreadedConversationScreenState extends ConsumerState Navigator.of(context, rootNavigator: true).maybePop(), + onPressed: () { + if (Navigator.of(context).canPop()) { + Navigator.of(context).pop(); + } else { + // Fallback for direct links + context.go(AppRoutes.homeAlias); + } + }, icon: Icon(Icons.arrow_back, color: AppTheme.navyBlue), ), title: AnimatedSwitcher( @@ -809,14 +805,20 @@ class _ThreadedConversationScreenState extends ConsumerState Map.from(post.reactions!), ); + print('DEBUG: Seeded reaction counts: ${_reactionCountsByPost[post.id]}'); } if (post.myReactions != null) { _myReactionsByPost.putIfAbsent(post.id, () => post.myReactions!.toSet()); + print('DEBUG: Seeded my reactions: ${_myReactionsByPost[post.id]}'); } if (post.reactionUsers != null) { _reactionUsersByPost.putIfAbsent( @@ -827,11 +829,30 @@ class _ThreadedConversationScreenState extends ConsumerState _reactionCountsFor(Post post) { - return _reactionCountsByPost[post.id] ?? post.reactions ?? {}; + // Debug: Check what we're getting from the post model + print('DEBUG: _reactionCountsFor for post ${post.id}'); + print('DEBUG: post.reactions = ${post.reactions}'); + print('DEBUG: _reactionCountsByPost[${post.id}] = ${_reactionCountsByPost[post.id]}'); + + // Prefer local state for immediate updates after toggle reactions + final localState = _reactionCountsByPost[post.id]; + if (localState != null) { + print('DEBUG: Using local state: ${localState}'); + return localState; + } + // Fall back to post model if no local state + print('DEBUG: Using post.reactions: ${post.reactions}'); + return post.reactions ?? {}; } Set _myReactionsFor(Post post) { - return _myReactionsByPost[post.id] ?? post.myReactions?.toSet() ?? {}; + // Prefer local state for immediate updates after toggle reactions + final localState = _myReactionsByPost[post.id]; + if (localState != null) { + return localState; + } + // Fall back to post model if no local state + return post.myReactions?.toSet() ?? {}; } Map>? _reactionUsersFor(Post post) { diff --git a/sojorn_app/lib/screens/profile/viewable_profile_screen.dart b/sojorn_app/lib/screens/profile/viewable_profile_screen.dart index 5782b54..63a1853 100644 --- a/sojorn_app/lib/screens/profile/viewable_profile_screen.dart +++ b/sojorn_app/lib/screens/profile/viewable_profile_screen.dart @@ -1,6 +1,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import '../../routes/app_routes.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../models/post.dart'; import '../../models/profile.dart'; @@ -527,15 +529,11 @@ class _ViewableProfileScreenState extends ConsumerState leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () { - final navigator = Navigator.of(context); - if (navigator.canPop()) { - navigator.pop(); + if (Navigator.of(context).canPop()) { + Navigator.of(context).pop(); } else { - Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (_) => const ProfileScreen(), - ), - ); + // Safely return to home/feed instead of pushing a redundant ProfileScreen + context.go(AppRoutes.homeAlias); } }, ), diff --git a/sojorn_app/lib/services/api_service.dart b/sojorn_app/lib/services/api_service.dart index b00d10f..9ab0d27 100644 --- a/sojorn_app/lib/services/api_service.dart +++ b/sojorn_app/lib/services/api_service.dart @@ -16,7 +16,6 @@ import '../models/tone_analysis.dart'; import 'package:http/http.dart' as http; /// ApiService - Single source of truth for all backend communication. -/// Migration: Supabase direct reads are being replaced byGo API calls. class ApiService { final AuthService _authService; final http.Client _httpClient = http.Client(); @@ -28,7 +27,7 @@ class ApiService { static ApiService get instance => _instance ??= ApiService(AuthService.instance); - /// Generic function caller for Edge Functions. Handles response parsing + /// Generic caller for specialized edge endpoints. Handles response parsing /// and normalization across different response formats. Future> _callFunction( String functionName, { diff --git a/sojorn_app/lib/services/image_upload_service.dart b/sojorn_app/lib/services/image_upload_service.dart index 05662bc..acafad5 100644 --- a/sojorn_app/lib/services/image_upload_service.dart +++ b/sojorn_app/lib/services/image_upload_service.dart @@ -57,12 +57,12 @@ class UploadResult { /// Progress callback for upload operations typedef UploadProgressCallback = void Function(double progress); -/// Service for uploading images AND videos to Cloudflare R2 via Supabase Edge Functions +/// Service for uploading images AND videos to Cloudflare R2 via Go Backend class ImageUploadService { final AuthService _auth = AuthService.instance; final _storage = const FlutterSecureStorage(); - /// Get the current authentication token (Go backend or Supabase fallback) + /// Get the current authentication token Future _getAuthToken() async { return _auth.accessToken; } @@ -469,7 +469,7 @@ class ImageUploadService { onProgress?.call(0.3); - print('Uploading image via edge function...'); + print('Uploading image via R2 bridge...'); final streamedResponse = await request.send(); final response = await http.Response.fromStream(streamedResponse); diff --git a/sojorn_app/pubspec.yaml b/sojorn_app/pubspec.yaml index e33ef1c..100392d 100644 --- a/sojorn_app/pubspec.yaml +++ b/sojorn_app/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter_localizations: sdk: flutter - # Supabase (removed - migrated to Go backend) + # Backend Services (Fully migrated to Go API) firebase_core: ^3.4.0 firebase_messaging: ^15.1.0 diff --git a/sojorn_app/run_chrome.bat b/sojorn_app/run_chrome.bat index 3041a2f..a64b6be 100644 --- a/sojorn_app/run_chrome.bat +++ b/sojorn_app/run_chrome.bat @@ -5,6 +5,4 @@ echo Starting Sojorn on Chrome... echo. flutter run -d chrome ^ - --dart-define=SUPABASE_URL=https://zwkihedetedlatyvplyz.supabase.co ^ - --dart-define=SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inp3a2loZWRldGVkbGF0eXZwbHl6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njc2Nzk3OTUsImV4cCI6MjA4MzI1NTc5NX0.7YyYOABjm7cpKa1DiefkI9bH8r6SICJ89nDK9sgUa0M ^ --dart-define=API_BASE_URL=https://api.gosojorn.com/api/v1 diff --git a/sojorn_app/run_chrome.ps1 b/sojorn_app/run_chrome.ps1 index 3548725..4377084 100644 --- a/sojorn_app/run_chrome.ps1 +++ b/sojorn_app/run_chrome.ps1 @@ -26,7 +26,7 @@ Get-Content $EnvPath | ForEach-Object { $values[$key] = $value } -$required = @('SUPABASE_URL', 'SUPABASE_ANON_KEY', 'API_BASE_URL') +$required = @('API_BASE_URL') $missing = $required | Where-Object { -not $values.ContainsKey($_) -or [string]::IsNullOrWhiteSpace($values[$_]) } @@ -37,24 +37,9 @@ if ($missing.Count -gt 0) { } $defineArgs = @( - "--dart-define=SUPABASE_URL=$($values['SUPABASE_URL'])", - "--dart-define=SUPABASE_ANON_KEY=$($values['SUPABASE_ANON_KEY'])", "--dart-define=API_BASE_URL=$($values['API_BASE_URL'])" ) -$optionalDefines = @( - 'SUPABASE_PUBLISHABLE_KEY', - 'SUPABASE_SECRET_KEY', - 'SUPABASE_JWT_KID', - 'SUPABASE_JWKS_URI' -) - -foreach ($opt in $optionalDefines) { - if ($values.ContainsKey($opt) -and -not [string]::IsNullOrWhiteSpace($values[$opt])) { - $defineArgs += "--dart-define=$opt=$($values[$opt])" - } -} - Write-Host "Starting Sojorn on Chrome..." -ForegroundColor Green Write-Host "" diff --git a/sojorn_app/run_dev.bat b/sojorn_app/run_dev.bat index 7498538..9659e5b 100644 --- a/sojorn_app/run_dev.bat +++ b/sojorn_app/run_dev.bat @@ -5,6 +5,4 @@ echo Starting Sojorn in development mode... echo. flutter run ^ - --dart-define=SUPABASE_URL=https://zwkihedetedlatyvplyz.supabase.co ^ - --dart-define=SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inp3a2loZWRldGVkbGF0eXZwbHl6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njc2Nzk3OTUsImV4cCI6MjA4MzI1NTc5NX0.7YyYOABjm7cpKa1DiefkI9bH8r6SICJ89nDK9sgUa0M ^ --dart-define=API_BASE_URL=https://api.gosojorn.com/api/v1 diff --git a/sojorn_app/run_dev.ps1 b/sojorn_app/run_dev.ps1 index 3ff2826..758db56 100644 --- a/sojorn_app/run_dev.ps1 +++ b/sojorn_app/run_dev.ps1 @@ -26,7 +26,7 @@ Get-Content $EnvPath | ForEach-Object { $values[$key] = $value } -$required = @('SUPABASE_URL', 'SUPABASE_ANON_KEY', 'API_BASE_URL') +$required = @('API_BASE_URL') $missing = $required | Where-Object { -not $values.ContainsKey($_) -or [string]::IsNullOrWhiteSpace($values[$_]) } @@ -37,24 +37,9 @@ if ($missing.Count -gt 0) { } $defineArgs = @( - "--dart-define=SUPABASE_URL=$($values['SUPABASE_URL'])", - "--dart-define=SUPABASE_ANON_KEY=$($values['SUPABASE_ANON_KEY'])", "--dart-define=API_BASE_URL=$($values['API_BASE_URL'])" ) -$optionalDefines = @( - 'SUPABASE_PUBLISHABLE_KEY', - 'SUPABASE_SECRET_KEY', - 'SUPABASE_JWT_KID', - 'SUPABASE_JWKS_URI' -) - -foreach ($opt in $optionalDefines) { - if ($values.ContainsKey($opt) -and -not [string]::IsNullOrWhiteSpace($values[$opt])) { - $defineArgs += "--dart-define=$opt=$($values[$opt])" - } -} - Write-Host "Starting Sojorn in development mode..." -ForegroundColor Green Write-Host ""