diff --git a/frontend/lib/constants/api_constants.dart b/frontend/lib/constants/api_constants.dart index 26e058d..e0f177f 100644 --- a/frontend/lib/constants/api_constants.dart +++ b/frontend/lib/constants/api_constants.dart @@ -1,5 +1,5 @@ class ApiConstants { - static const String baseUrl = 'https://api.wesal.online'; + static const String baseUrl = 'http://localhost:8080'; // Auth endpoints static const String loginEndpoint = '/login'; @@ -7,10 +7,13 @@ class ApiConstants { // User endpoints static const String getUserEndpoint = '/getUser'; static const String updateUserEndpoint = '/updateUser'; - + // Invitation endpoints static const String invitationsEndpoint = '/invitations'; static const String getAllInvitationsEndpoint = '/invitations/all'; static const String acceptInvitationEndpoint = '/invitations/accept'; static const String createInvitationEndpoint = '/invitations/create'; + + // Post endpoints + static const String createPostEndpoint = '/posts/create'; } diff --git a/frontend/lib/models/post_models.dart b/frontend/lib/models/post_models.dart new file mode 100644 index 0000000..ebcdb8c --- /dev/null +++ b/frontend/lib/models/post_models.dart @@ -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 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 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 toJson() { + return { + 'body': body, + }; + } +} \ No newline at end of file diff --git a/frontend/lib/screens/create_post_screen.dart b/frontend/lib/screens/create_post_screen.dart new file mode 100644 index 0000000..57d0e1a --- /dev/null +++ b/frontend/lib/screens/create_post_screen.dart @@ -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 { + final TextEditingController _bodyController = TextEditingController(); + bool _isLoading = false; + final int _maxCharacters = 280; + + @override + void dispose() { + _bodyController.dispose(); + super.dispose(); + } + + Future _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(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, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/frontend/lib/screens/pages/feed_page.dart b/frontend/lib/screens/pages/feed_page.dart index 855bd7b..9d28087 100644 --- a/frontend/lib/screens/pages/feed_page.dart +++ b/frontend/lib/screens/pages/feed_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import '../create_post_screen.dart'; class FeedPage extends StatefulWidget { @override @@ -6,6 +7,7 @@ class FeedPage extends StatefulWidget { } class _FeedPageState extends State { + bool _isRefreshing = false; final List> mockPosts = [ { 'id': '1', @@ -79,12 +81,7 @@ class _FeedPageState extends State { automaticallyImplyLeading: false, ), body: RefreshIndicator( - onRefresh: () async { - await Future.delayed(Duration(seconds: 1)); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('Feed refreshed!'))); - }, + onRefresh: _refreshFeed, child: ListView.builder( padding: EdgeInsets.symmetric(vertical: 8), itemCount: mockPosts.length, @@ -107,16 +104,44 @@ class _FeedPageState extends State { ), ), floatingActionButton: FloatingActionButton( - onPressed: () { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Create post functionality coming soon!')), - ); - }, + onPressed: _navigateToCreatePost, backgroundColor: Color(0xFF6A4C93), child: Icon(Icons.edit, color: Colors.white), ), ); } + + Future _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 _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 { diff --git a/frontend/lib/services/post_service.dart b/frontend/lib/services/post_service.dart new file mode 100644 index 0000000..bfb672f --- /dev/null +++ b/frontend/lib/services/post_service.dart @@ -0,0 +1,38 @@ +import 'dart:convert'; +import '../models/post_models.dart'; +import 'http_service.dart'; + +class PostService { + static Future> 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, + }; + } + } +} \ No newline at end of file