first commit

This commit is contained in:
Lucas Saburido
2026-05-13 16:26:45 +01:00
commit cabf2025cd
252 changed files with 13524 additions and 0 deletions

View File

@@ -0,0 +1,94 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../../core/router/app_routes.dart';
import '../../../../core/theme/app_colors.dart';
import '../../../../core/widgets/riotz_scaffold.dart';
import '../../../auth/presentation/providers/auth_provider.dart';
import '../providers/post_providers.dart';
import '../widgets/post_card.dart';
class FeedScreen extends ConsumerWidget {
const FeedScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final postsAsync = ref.watch(feedPostsProvider);
final currentUser = ref.watch(authServiceProvider).currentUser;
return RiotzScaffold(
appBar: AppBar(
title: const Text('RIOTZ // FEED'),
actions: [
IconButton(
icon: const Icon(Icons.add_box_outlined, color: AppColors.white),
onPressed: () => context.push(AppRoutes.uploadPost),
),
],
),
body: RefreshIndicator(
color: AppColors.neonRed,
backgroundColor: AppColors.black,
onRefresh: () async => ref.refresh(feedPostsProvider),
child: postsAsync.when(
data: (posts) {
if (posts.isEmpty) {
return const Center(
child: Text('NO CHAOS YET. START A RIOT.'),
);
}
return ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) {
final post = posts[index];
return PostCard(
post: post,
isOwnPost: post.userId == currentUser?.id,
onLike: () => ref.read(postControllerProvider.notifier).toggleLike(post),
onDelete: () => _confirmDelete(context, ref, post.id),
);
},
);
},
loading: () => const Center(
child: CircularProgressIndicator(color: AppColors.neonRed),
),
error: (error, _) => Center(
child: Text('SIGNAL LOST: $error'),
),
),
),
floatingActionButton: FloatingActionButton(
backgroundColor: AppColors.neonRed,
child: const Icon(Icons.add, color: AppColors.white),
onPressed: () => context.push(AppRoutes.uploadPost),
),
);
}
void _confirmDelete(BuildContext context, WidgetRef ref, String postId) {
showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: AppColors.surface,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero),
title: const Text('ERASE TRANSMISSION?'),
content: const Text('THIS ACTION CANNOT BE UNDONE.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('CANCEL', style: TextStyle(color: AppColors.white)),
),
TextButton(
onPressed: () {
ref.read(postControllerProvider.notifier).deletePost(postId);
Navigator.pop(context);
},
child: const Text('DELETE', style: TextStyle(color: AppColors.neonRed)),
),
],
),
);
}
}

View File

@@ -0,0 +1,134 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
import '../../../../core/theme/app_colors.dart';
import '../../../../core/widgets/riotz_button.dart';
import '../../../../core/widgets/riotz_scaffold.dart';
import '../providers/post_providers.dart';
class UploadPostScreen extends ConsumerStatefulWidget {
const UploadPostScreen({super.key});
@override
ConsumerState<UploadPostScreen> createState() => _UploadPostScreenState();
}
class _UploadPostScreenState extends ConsumerState<UploadPostScreen> {
final _captionController = TextEditingController();
final _picker = ImagePicker();
Uint8List? _imageBytes;
String? _extension;
@override
void dispose() {
_captionController.dispose();
super.dispose();
}
Future<void> _pickImage() async {
final image = await _picker.pickImage(
source: ImageSource.gallery,
imageQuality: 80,
maxWidth: 1080,
);
if (image != null) {
final bytes = await image.readAsBytes();
setState(() {
_imageBytes = bytes;
_extension = image.name.split('.').last.toLowerCase();
});
}
}
Future<void> _upload() async {
if (_imageBytes == null) return;
await ref.read(postControllerProvider.notifier).createPost(
caption: _captionController.text,
imageBytes: _imageBytes!,
extension: _extension ?? 'jpg',
);
if (mounted) {
context.pop();
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final loading = ref.watch(postControllerProvider).isLoading;
return RiotzScaffold(
appBar: AppBar(
title: const Text('NEW TRANSMISSION'),
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () => context.pop(),
),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Image Picker Area
GestureDetector(
onTap: loading ? null : _pickImage,
child: AspectRatio(
aspectRatio: 1,
child: Container(
decoration: BoxDecoration(
color: AppColors.surface,
border: Border.all(color: AppColors.border, width: 2),
),
child: _imageBytes != null
? Image.memory(_imageBytes!, fit: BoxFit.cover)
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.add_a_photo_outlined, size: 48, color: AppColors.grey),
const SizedBox(height: 12),
Text(
'SELECT VISUALS',
style: theme.textTheme.labelLarge?.copyWith(color: AppColors.grey),
),
],
),
),
),
),
const SizedBox(height: 32),
// Caption Field
Text(
'MANIFESTO',
style: theme.textTheme.labelLarge?.copyWith(color: AppColors.neonRed),
),
const SizedBox(height: 8),
TextField(
controller: _captionController,
maxLines: 4,
decoration: const InputDecoration(
hintText: 'DESCRIBE THE CHAOS...',
),
),
const SizedBox(height: 48),
// Post Button
RiotzButton(
label: 'PROPAGATE',
isLoading: loading,
onPressed: _imageBytes == null ? null : _upload,
),
],
),
),
);
}
}