diff --git a/sojorn_app/lib/screens/compose/compose_screen.dart b/sojorn_app/lib/screens/compose/compose_screen.dart index e45cf12..e688d32 100644 --- a/sojorn_app/lib/screens/compose/compose_screen.dart +++ b/sojorn_app/lib/screens/compose/compose_screen.dart @@ -39,6 +39,7 @@ class _ComposeScreenState extends ConsumerState { bool _isLoading = false; bool _isUploadingImage = false; String? _errorMessage; + String? _blockedMessage; final int _maxCharacters = 500; bool _allowChain = true; bool _isBold = false; @@ -69,6 +70,10 @@ class _ComposeScreenState extends ConsumerState { _bodyController.addListener(() { _charCountNotifier.value = _bodyController.text.length; _handleHashtagSuggestions(); + // Clear blocked banner when user edits their text + if (_blockedMessage != null) { + setState(() => _blockedMessage = null); + } }); WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; @@ -326,7 +331,7 @@ class _ComposeScreenState extends ConsumerState { // Layer 0: Client-side hard blocklist — never even send to server final blockMessage = ContentFilter.instance.check(_bodyController.text.trim()); if (blockMessage != null) { - await _showBlockedDialog(blockMessage); + setState(() => _blockedMessage = blockMessage); return; } @@ -411,7 +416,7 @@ class _ComposeScreenState extends ConsumerState { final msg = e.toString().replaceAll('Exception: ', ''); // Server-side blocklist catch (422 with blocked content message) if (msg.contains("isn't allowed on Sojorn") || msg.contains('not allowed')) { - if (mounted) await _showBlockedDialog(msg); + if (mounted) setState(() => _blockedMessage = msg); } else { setState(() { _errorMessage = msg; @@ -454,35 +459,60 @@ class _ComposeScreenState extends ConsumerState { return result ?? false; } - Future _showBlockedDialog(String message) async { - await showDialog( - context: context, - barrierDismissible: false, - builder: (context) => AlertDialog( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - title: Row( - children: [ - Icon(Icons.block, color: AppTheme.error, size: 24), - const SizedBox(width: 8), - const Text('Not Allowed'), - ], - ), - content: Text( - message, - style: AppTheme.textTheme.bodyMedium, - ), - actions: [ - ElevatedButton( - onPressed: () => Navigator.pop(context), - style: ElevatedButton.styleFrom( - backgroundColor: AppTheme.brightNavy, - foregroundColor: AppTheme.white, - shape: const StadiumBorder(), - ), - child: const Text('Edit My Post'), - ), - ], - ), + Widget _buildBlockedBanner() { + return AnimatedSize( + duration: const Duration(milliseconds: 250), + curve: Curves.easeOut, + child: _blockedMessage != null + ? Container( + width: double.infinity, + padding: const EdgeInsets.symmetric( + horizontal: AppTheme.spacingMd, + vertical: 12, + ), + decoration: BoxDecoration( + color: const Color(0xFFFEF2F2), + border: Border( + bottom: BorderSide( + color: AppTheme.error.withValues(alpha: 0.2), + ), + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(Icons.info_outline, color: AppTheme.error, size: 20), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _blockedMessage!, + style: AppTheme.textTheme.bodySmall?.copyWith( + color: const Color(0xFF991B1B), + fontWeight: FontWeight.w500, + height: 1.4, + ), + ), + ], + ), + ), + GestureDetector( + onTap: () => setState(() => _blockedMessage = null), + child: Padding( + padding: const EdgeInsets.only(left: 8), + child: Icon( + Icons.close, + size: 16, + color: AppTheme.error.withValues(alpha: 0.6), + ), + ), + ), + ], + ), + ) + : const SizedBox.shrink(), ); } @@ -515,6 +545,7 @@ class _ComposeScreenState extends ConsumerState { body: SafeArea( child: Column( children: [ + _buildBlockedBanner(), if (_errorMessage != null) Container( width: double.infinity,