sojorn/sojorn_app/lib/widgets/modals/sanctuary_sheet.dart
2026-02-15 00:33:24 -06:00

322 lines
10 KiB
Dart

import 'dart:ui';
import 'package:flutter/material.dart';
import '../../models/post.dart';
import '../../theme/app_theme.dart';
import '../../theme/tokens.dart';
import '../../services/api_service.dart';
class SanctuarySheet extends StatefulWidget {
final Post post;
const SanctuarySheet({super.key, required this.post});
static Future<void> show(BuildContext context, Post post) {
return showModalBottomSheet(
context: context,
backgroundColor: SojornColors.transparent,
isScrollControlled: true,
builder: (_) => SanctuarySheet(post: post),
);
}
@override
State<SanctuarySheet> createState() => _SanctuarySheetState();
}
class _SanctuarySheetState extends State<SanctuarySheet> {
int _step = 0; // 0: Options, 1: Report Type, 2: Report Description, 3: Block Confirmation
String? _violationType;
final TextEditingController _descriptionController = TextEditingController();
bool _isProcessing = false;
@override
Widget build(BuildContext context) {
return BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Container(
decoration: BoxDecoration(
color: AppTheme.scaffoldBg,
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
border: Border.all(
color: AppTheme.egyptianBlue.withValues(alpha: 0.1),
width: 1.5,
),
),
padding: EdgeInsets.fromLTRB(24, 12, 24, MediaQuery.of(context).viewInsets.bottom + 24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Handle
Container(
width: 40,
height: 5,
decoration: BoxDecoration(
color: AppTheme.egyptianBlue.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(10),
),
),
const SizedBox(height: 24),
_buildContent(),
],
),
),
);
}
Widget _buildContent() {
if (_isProcessing) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 40),
child: CircularProgressIndicator(),
);
}
switch (_step) {
case 0:
return _buildOptions();
case 1:
return _buildReportTypes();
case 2:
return _buildReportDescription();
case 3:
return _buildBlockConfirmation();
default:
return const SizedBox.shrink();
}
}
Widget _buildOptions() {
return Column(
children: [
Text(
"The Sanctuary",
style: AppTheme.headlineSmall,
),
const SizedBox(height: 8),
Text(
"Protect the harmony of your Circle.",
style: AppTheme.labelSmall.copyWith(
color: AppTheme.navyText.withValues(alpha: 0.6),
),
),
const SizedBox(height: 32),
_buildActionTile(
icon: Icons.flag_outlined,
title: "Report Violation",
subtitle: "Harassment, Scam, or Misinformation detected",
onTap: () => setState(() => _step = 1),
),
const SizedBox(height: 16),
_buildActionTile(
icon: Icons.block_flipped,
title: "Exclude User",
subtitle: "Stop all interactions structurally",
color: SojornColors.destructive.withValues(alpha: 0.8),
onTap: () => setState(() => _step = 3),
),
],
);
}
Widget _buildReportTypes() {
final types = [
{'id': 'harassment', 'label': 'Harassment', 'desc': 'Hostility or aggression'},
{'id': 'scam', 'label': 'Scam / Fraud', 'desc': 'Fraudulent or manipulative content'},
{'id': 'misinformation', 'label': 'Misinformation', 'desc': 'False or harmful ignorance'},
];
return Column(
children: [
Text(
"Natures of Violation",
style: AppTheme.headlineSmall.copyWith(fontSize: 20),
),
const SizedBox(height: 24),
...types.map((t) => Padding(
padding: const EdgeInsets.only(bottom: 12),
child: _buildActionTile(
title: t['label']!,
subtitle: t['desc']!,
onTap: () {
setState(() {
_violationType = t['id'];
_step = 2;
});
},
),
)),
TextButton(
onPressed: () => setState(() => _step = 0),
child: Text("Back", style: TextStyle(color: AppTheme.egyptianBlue)),
),
],
);
}
Widget _buildReportDescription() {
return Column(
children: [
Text(
"Detail the Disturbance",
style: AppTheme.headlineSmall.copyWith(fontSize: 20),
),
const SizedBox(height: 24),
TextField(
controller: _descriptionController,
maxLines: 4,
style: TextStyle(color: AppTheme.navyText),
decoration: InputDecoration(
hintText: "Briefly describe the violation...",
hintStyle: TextStyle(color: AppTheme.navyText.withValues(alpha: 0.4)),
filled: true,
fillColor: AppTheme.egyptianBlue.withValues(alpha: 0.05),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
borderSide: BorderSide.none,
),
),
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.brightNavy,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
),
onPressed: _submitReport,
child: const Text("Submit Report", style: TextStyle(color: SojornColors.basicWhite)),
),
),
TextButton(
onPressed: () => setState(() => _step = 1),
child: Text("Back", style: TextStyle(color: AppTheme.egyptianBlue)),
),
],
);
}
Widget _buildBlockConfirmation() {
return Column(
children: [
const Icon(Icons.warning_amber_rounded, color: SojornColors.destructive, size: 64),
const SizedBox(height: 16),
Text(
"Exclude from Circle?",
style: AppTheme.headlineSmall.copyWith(fontSize: 22, color: AppTheme.error),
),
const SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Text(
"This will structurally separate you and @${widget.post.author?.handle ?? 'this user'}. You will both be invisible to each other across Sojorn.",
textAlign: TextAlign.center,
style: AppTheme.bodyMedium.copyWith(color: AppTheme.navyText.withValues(alpha: 0.7)),
),
),
const SizedBox(height: 32),
SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: SojornColors.destructive,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
),
onPressed: _confirmBlock,
child: const Text("Yes, Exclude structurally", style: TextStyle(color: SojornColors.basicWhite)),
),
),
const SizedBox(height: 8),
TextButton(
onPressed: () => Navigator.pop(context),
child: Text("Cancel", style: TextStyle(color: AppTheme.egyptianBlue)),
),
],
);
}
Widget _buildActionTile({
required String title,
required String subtitle,
required VoidCallback onTap,
IconData? icon,
Color? color,
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(15),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: (color ?? AppTheme.navyText).withValues(alpha: 0.05),
borderRadius: BorderRadius.circular(15),
border: Border.all(color: (color ?? AppTheme.navyText).withValues(alpha: 0.1)),
),
child: Row(
children: [
if (icon != null) ...[
Icon(icon, color: color ?? AppTheme.navyText.withValues(alpha: 0.7), size: 28),
const SizedBox(width: 16),
],
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: AppTheme.labelLarge.copyWith(color: color ?? AppTheme.navyText)),
const SizedBox(height: 2),
Text(subtitle, style: AppTheme.labelSmall.copyWith(color: (color ?? AppTheme.navyText).withValues(alpha: 0.6))),
],
),
),
Icon(Icons.chevron_right, color: AppTheme.egyptianBlue.withValues(alpha: 0.3)),
],
),
),
);
}
Future<void> _submitReport() async {
setState(() => _isProcessing = true);
try {
await ApiService.instance.callGoApi(
'/users/report',
method: 'POST',
body: {
'target_user_id': widget.post.authorId,
'post_id': widget.post.id,
'violation_type': _violationType,
'description': _descriptionController.text,
},
);
if (mounted) {
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Report submitted. Thank you for maintaining harmony.")),
);
}
} catch (e) {
if (mounted) setState(() => _isProcessing = false);
}
}
Future<void> _confirmBlock() async {
setState(() => _isProcessing = true);
try {
await ApiService.instance.callGoApi(
'/users/${widget.post.authorId}/block',
method: 'POST',
);
if (mounted) {
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Structural exclusion complete.")),
);
}
} catch (e) {
if (mounted) setState(() => _isProcessing = false);
}
}
}