feat: UI for creating posts

This commit is contained in:
sBubshait 2025-07-23 13:36:53 +03:00
parent 2733ce8269
commit e146b18f1d
5 changed files with 385 additions and 13 deletions

View File

@ -1,5 +1,5 @@
class ApiConstants { class ApiConstants {
static const String baseUrl = 'https://api.wesal.online'; static const String baseUrl = 'http://localhost:8080';
// Auth endpoints // Auth endpoints
static const String loginEndpoint = '/login'; static const String loginEndpoint = '/login';
@ -7,10 +7,13 @@ class ApiConstants {
// User endpoints // User endpoints
static const String getUserEndpoint = '/getUser'; static const String getUserEndpoint = '/getUser';
static const String updateUserEndpoint = '/updateUser'; static const String updateUserEndpoint = '/updateUser';
// Invitation endpoints // Invitation endpoints
static const String invitationsEndpoint = '/invitations'; static const String invitationsEndpoint = '/invitations';
static const String getAllInvitationsEndpoint = '/invitations/all'; static const String getAllInvitationsEndpoint = '/invitations/all';
static const String acceptInvitationEndpoint = '/invitations/accept'; static const String acceptInvitationEndpoint = '/invitations/accept';
static const String createInvitationEndpoint = '/invitations/create'; static const String createInvitationEndpoint = '/invitations/create';
// Post endpoints
static const String createPostEndpoint = '/posts/create';
} }

View File

@ -0,0 +1,55 @@
class Post {
final String id;
final String body;
final String userId;
final String username;
final String displayName;
final DateTime createdAt;
final DateTime updatedAt;
Post({
required this.id,
required this.body,
required this.userId,
required this.username,
required this.displayName,
required this.createdAt,
required this.updatedAt,
});
factory Post.fromJson(Map<String, dynamic> json) {
return Post(
id: json['id'] ?? '',
body: json['body'] ?? '',
userId: json['userId'] ?? '',
username: json['username'] ?? '',
displayName: json['displayName'] ?? '',
createdAt: DateTime.parse(json['createdAt'] ?? DateTime.now().toIso8601String()),
updatedAt: DateTime.parse(json['updatedAt'] ?? DateTime.now().toIso8601String()),
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'body': body,
'userId': userId,
'username': username,
'displayName': displayName,
'createdAt': createdAt.toIso8601String(),
'updatedAt': updatedAt.toIso8601String(),
};
}
}
class CreatePostRequest {
final String body;
CreatePostRequest({required this.body});
Map<String, dynamic> toJson() {
return {
'body': body,
};
}
}

View File

@ -0,0 +1,251 @@
import 'package:flutter/material.dart';
import '../services/post_service.dart';
class CreatePostScreen extends StatefulWidget {
@override
_CreatePostScreenState createState() => _CreatePostScreenState();
}
class _CreatePostScreenState extends State<CreatePostScreen> {
final TextEditingController _bodyController = TextEditingController();
bool _isLoading = false;
final int _maxCharacters = 280;
@override
void dispose() {
_bodyController.dispose();
super.dispose();
}
Future<void> _createPost() async {
if (_bodyController.text.trim().isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Please write something before posting'),
backgroundColor: Colors.red,
),
);
return;
}
setState(() {
_isLoading = true;
});
try {
final result = await PostService.createPost(_bodyController.text.trim());
if (result['success']) {
Navigator.of(context).pop(true); // Return true to indicate success
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Post created successfully!'),
backgroundColor: Color(0xFF6A4C93),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(result['message']),
backgroundColor: Colors.red,
),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to create post. Please try again.'),
backgroundColor: Colors.red,
),
);
} finally {
setState(() {
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
final remainingChars = _maxCharacters - _bodyController.text.length;
final isOverLimit = remainingChars < 0;
return Scaffold(
appBar: AppBar(
title: Text('Create Post', style: TextStyle(fontWeight: FontWeight.w600)),
backgroundColor: Colors.white,
foregroundColor: Color(0xFF6A4C93),
elevation: 0,
bottom: PreferredSize(
preferredSize: Size.fromHeight(1),
child: Container(height: 1, color: Colors.grey[200]),
),
actions: [
TextButton(
onPressed: _isLoading || isOverLimit || _bodyController.text.trim().isEmpty
? null
: _createPost,
child: _isLoading
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFF6A4C93)),
),
)
: Text(
'Post',
style: TextStyle(
color: _bodyController.text.trim().isEmpty || isOverLimit
? Colors.grey
: Color(0xFF6A4C93),
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
),
SizedBox(width: 16),
],
),
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFF32B0A5).withOpacity(0.05),
Color(0xFF4600B9).withOpacity(0.05),
],
),
),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: Offset(0, 2),
),
],
),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"What's on your mind?",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Color(0xFF6A4C93),
),
),
SizedBox(height: 16),
TextField(
controller: _bodyController,
maxLines: 6,
decoration: InputDecoration(
hintText: 'Share your thoughts...',
hintStyle: TextStyle(color: Colors.grey[500]),
border: InputBorder.none,
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
),
style: TextStyle(
fontSize: 16,
height: 1.4,
color: Colors.black87,
),
onChanged: (text) {
setState(() {});
},
),
SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Share your thoughts with the community',
style: TextStyle(
color: Colors.grey[600],
fontSize: 12,
),
),
Text(
'$remainingChars',
style: TextStyle(
color: isOverLimit ? Colors.red : Colors.grey[600],
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
],
),
if (isOverLimit)
Padding(
padding: EdgeInsets.only(top: 8),
child: Text(
'Post is too long. Please keep it under $_maxCharacters characters.',
style: TextStyle(
color: Colors.red,
fontSize: 12,
),
),
),
],
),
),
),
SizedBox(height: 24),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: Offset(0, 2),
),
],
),
child: Padding(
padding: EdgeInsets.all(16),
child: Row(
children: [
Icon(
Icons.lightbulb_outline,
color: Color(0xFF6A4C93),
size: 20,
),
SizedBox(width: 12),
Expanded(
child: Text(
'Keep it friendly and respectful. Your post will be visible to everyone in the community.',
style: TextStyle(
color: Colors.grey[700],
fontSize: 13,
height: 1.3,
),
),
),
],
),
),
),
],
),
),
),
);
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../create_post_screen.dart';
class FeedPage extends StatefulWidget { class FeedPage extends StatefulWidget {
@override @override
@ -6,6 +7,7 @@ class FeedPage extends StatefulWidget {
} }
class _FeedPageState extends State<FeedPage> { class _FeedPageState extends State<FeedPage> {
bool _isRefreshing = false;
final List<Map<String, dynamic>> mockPosts = [ final List<Map<String, dynamic>> mockPosts = [
{ {
'id': '1', 'id': '1',
@ -79,12 +81,7 @@ class _FeedPageState extends State<FeedPage> {
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
), ),
body: RefreshIndicator( body: RefreshIndicator(
onRefresh: () async { onRefresh: _refreshFeed,
await Future.delayed(Duration(seconds: 1));
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Feed refreshed!')));
},
child: ListView.builder( child: ListView.builder(
padding: EdgeInsets.symmetric(vertical: 8), padding: EdgeInsets.symmetric(vertical: 8),
itemCount: mockPosts.length, itemCount: mockPosts.length,
@ -107,16 +104,44 @@ class _FeedPageState extends State<FeedPage> {
), ),
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: () { onPressed: _navigateToCreatePost,
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Create post functionality coming soon!')),
);
},
backgroundColor: Color(0xFF6A4C93), backgroundColor: Color(0xFF6A4C93),
child: Icon(Icons.edit, color: Colors.white), child: Icon(Icons.edit, color: Colors.white),
), ),
); );
} }
Future<void> _refreshFeed() async {
setState(() {
_isRefreshing = true;
});
// Simulate API call for refreshing feed
await Future.delayed(Duration(seconds: 1));
setState(() {
_isRefreshing = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Feed refreshed!'),
backgroundColor: Color(0xFF6A4C93),
),
);
}
Future<void> _navigateToCreatePost() async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => CreatePostScreen()),
);
// If post was created successfully, refresh the feed
if (result == true) {
_refreshFeed();
}
}
} }
class PostCard extends StatefulWidget { class PostCard extends StatefulWidget {

View File

@ -0,0 +1,38 @@
import 'dart:convert';
import '../models/post_models.dart';
import 'http_service.dart';
class PostService {
static Future<Map<String, dynamic>> createPost(String body) async {
try {
final createPostRequest = CreatePostRequest(body: body);
final response = await HttpService.post(
'/posts/create',
createPostRequest.toJson(),
);
final responseData = jsonDecode(response.body);
if (response.statusCode == 200 || response.statusCode == 201) {
return {
'success': responseData['status'] ?? false,
'message': responseData['message'] ?? '',
'post': responseData['data'] != null ? Post.fromJson(responseData['data']) : null,
};
} else {
return {
'success': false,
'message': responseData['message'] ?? 'Failed to create post',
'post': null,
};
}
} catch (e) {
print('Error creating post: $e');
return {
'success': false,
'message': 'Network error: $e',
'post': null,
};
}
}
}