cdx_comments 0.0.6
cdx_comments: ^0.0.6 copied to clipboard
A Flutter package for managing comments with support for replies, likes, reporting, and user blocking.
cdx_comments #
A comprehensive Flutter package for managing comments with support for replies, likes, reporting, and user blocking. Built with a clean, extensible architecture that follows Flutter best practices and requires zero external dependencies (except provider).
Features #
- ๐ฌ Threaded Comments: Full support for nested replies and comment threads
- ๐ Like System: Like/unlike functionality with count display
- ๐ก๏ธ Content Validation: Built-in validation for bad words, dangerous content, and length limits
- ๐จ Reporting System: Report comments and users with customizable reasons
- ๐ซ User Blocking: Block users functionality with optional time-based restrictions
- ๐ Internationalization: Support for multiple languages (English, Italian) with extensible localization
- ๐จ Highly Customizable: Optional theme, text styles, and app actions customization
- ๐ง Feature Flags: Flexible feature and insight system for dynamic configuration
- ๐ฆ Clean Architecture: Service-based architecture with dependency injection support
- ๐งช Well Tested: Comprehensive unit tests included
Installation #
Add cdx_comments to your pubspec.yaml:
dependencies:
cdx_comments: ^0.0.1
provider: ^6.1.5+1
flutter_localizations:
sdk: flutter
Then run:
flutter pub get
Quick Start #
1. Setup Localization #
Add the localization delegates to your MaterialApp:
import 'package:cdx_comments/cdx_comments.dart';
MaterialApp(
localizationsDelegates: [
...CdxCommentsLocalizations.localizationsDelegates,
// Add your other delegates here
],
supportedLocales: CdxCommentsLocalizations.supportedLocales,
// ... rest of your app
)
2. Implement CommentService #
Implement the CommentService interface to connect with your backend:
class MyCommentService implements CommentService {
@override
Future<List<Comment>> fetchComments(
String entityId,
CommentConfig config,
int page,
) async {
// Fetch comments from your API
final response = await http.get('/api/comments/$entityId?page=$page');
// Parse and return comments
return parseComments(response.body);
}
@override
Future<Comment?> postComment(Comment comment, CommentConfig config) async {
// Post comment to your API
final response = await http.post('/api/comments', body: comment.toJson());
return Comment.fromJson(response.body);
}
@override
Future<Comment?> postReply(Comment comment) async {
// Post reply to your API
final response = await http.post('/api/replies', body: comment.toJson());
return Comment.fromJson(response.body);
}
@override
Future<Comment?> toggleLikeComment(String commentId) async {
// Toggle like on your API
final response = await http.post('/api/comments/$commentId/like');
return Comment.fromJson(response.body);
}
@override
Future<void> deleteComment(String commentId) async {
// Delete comment on your API
await http.delete('/api/comments/$commentId');
}
@override
Future<List<Comment>> fetchReplies(String commentId, int page) async {
// Fetch replies from your API
final response = await http.get('/api/comments/$commentId/replies?page=$page');
return parseComments(response.body);
}
@override
Future<void> sendReportComment(String commentId, String reasonId) async {
await http.post('/api/comments/$commentId/report', body: {'reason': reasonId});
}
@override
Future<void> sendReportUser(String userId) async {
await http.post('/api/users/$userId/report');
}
@override
Future<void> sendBlockUser(String userId) async {
await http.post('/api/users/$userId/block');
}
}
3. Implement FeatureChecker #
Control which features and insights are enabled:
class MyFeatureChecker implements FeatureChecker {
final bool commentsEnabled;
final bool likesEnabled;
MyFeatureChecker({
this.commentsEnabled = true,
this.likesEnabled = true,
});
@override
bool commentHasFeature(ModuleFeature feature) {
switch (feature) {
case ModuleFeature.comment:
return commentsEnabled;
case ModuleFeature.like:
return likesEnabled;
}
}
@override
bool commentHasInsight(ModuleInsight insight) {
switch (insight) {
case ModuleInsight.likeCount:
return likesEnabled; // Show count only if likes are enabled
}
}
}
4. Setup and Use #
import 'package:cdx_comments/cdx_comments.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class CommentsPage extends StatefulWidget {
final String postId;
const CommentsPage({super.key, required this.postId});
@override
State<CommentsPage> createState() => _CommentsPageState();
}
class _CommentsPageState extends State<CommentsPage> {
late final CommentService _service;
late final CommentConfig _config;
late final UserInfo _user;
late final CommentController _controller;
late final CommentValidator _validator;
late final FeatureChecker _featureChecker;
@override
void initState() {
super.initState();
// Setup services - in a real app, these might come from dependency injection
_service = MyCommentService();
_config = CommentConfig(
badWords: 'bad\nword\nlist', // Your bad words list
);
_user = UserInfo(
uuid: 'current-user-id',
name: 'Current User',
);
_controller = CommentController(
service: _service,
config: _config,
user: _user,
);
_validator = CommentValidator(badWordsData: _config.badWords);
_featureChecker = MyFeatureChecker();
}
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => CommentProvider(
controller: _controller,
postId: widget.postId,
validator: _validator,
),
child: Scaffold(
appBar: AppBar(title: const Text('Comments')),
body: Builder(
builder: (context) => ElevatedButton(
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (_) => CommentBottomSheet(
userBlockedUntil: null, // Set if user is blocked
featureChecker: _featureChecker,
service: _service,
user: _user,
),
);
},
child: const Text('Show Comments'),
),
),
),
);
}
}
Alternative using Dependency Injection (recommended for larger apps):
// Using a DI container (e.g., get_it, riverpod, etc.)
class CommentsPage extends StatelessWidget {
final String postId;
const CommentsPage({super.key, required this.postId});
@override
Widget build(BuildContext context) {
// Get services from DI container
final service = GetIt.instance<CommentService>();
final config = GetIt.instance<CommentConfig>();
final user = GetIt.instance<UserInfo>();
final controller = GetIt.instance<CommentController>();
final validator = GetIt.instance<CommentValidator>();
final featureChecker = GetIt.instance<FeatureChecker>();
return ChangeNotifierProvider(
create: (_) => CommentProvider(
controller: controller,
postId: postId,
validator: validator,
),
child: Scaffold(
// ... rest of the widget
),
);
}
}
Customization #
Theme Customization #
Customize colors and styling through the CommentsTheme interface:
class MyCustomTheme implements CommentsTheme {
@override
Color get primary => Colors.blue;
@override
Color get mainText => Colors.white;
@override
Color get mainBackground => Colors.black;
@override
Color get error => Colors.red;
@override
Color get minorText => Colors.grey;
@override
BorderRadius get cardRadius => BorderRadius.circular(20);
}
// Use it in CommentBottomSheet
CommentBottomSheet(
// ... other parameters
theme: MyCustomTheme(),
)
If not provided, the package uses DefaultCommentsTheme which automatically extracts colors from Theme.of(context).
App Actions Customization #
Customize snackbars and dialogs:
class MyCustomAppActions implements CommentsAppActions {
@override
void showErrorSnackbar(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.red,
action: SnackBarAction(
label: 'OK',
onPressed: () {},
),
),
);
}
@override
void showInfoSnackbar(BuildContext context, String message) {
// Your custom implementation
}
@override
void showConfirmationDialog({
required BuildContext context,
required String title,
required String message,
required String confirmText,
required String cancelText,
required void Function(bool) onConfirm,
}) {
// Your custom dialog implementation
}
}
// Use it in CommentBottomSheet
CommentBottomSheet(
// ... other parameters
appActions: MyCustomAppActions(),
)
Text Style Customization #
Customize text styles:
class MyCustomTextStyle implements CommentsTextStyle {
final BuildContext context;
const MyCustomTextStyle(this.context);
@override
TextStyle bold18({Color? color, TextAlign? align}) {
return TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: color ?? Theme.of(context).colorScheme.onSurface,
fontFamily: 'CustomFont',
);
}
// Implement other methods: normal14, normal15, normal12
}
// Use it in CommentBottomSheet
CommentBottomSheet(
// ... other parameters
textStyle: MyCustomTextStyle(context),
)
Architecture #
The package follows clean architecture principles:
- Models: Immutable data classes (
Comment,UserInfo,CommentConfig,CommentsTheme, etc.) - Services: Abstract interfaces (
CommentService,FeatureChecker) for dependency inversion - Controllers: Business logic layer (
CommentController) - Providers: State management (
CommentProvider,CommentReportProvider) using Provider pattern - Validators: Input validation (
CommentValidator) - Widgets: Reusable UI components (
CommentBottomSheet,CommentTile,ReportCommentBottomSheet)
API Reference #
Core Models #
Comment
Represents a comment with all its properties:
id: Unique identifiercontent: Comment textuserId: Author user IDusername: Author usernamedate: Creation dateparentId: Parent comment ID (null for root comments)entityId: ID of the entity being commented onreplies: List of reply commentsreplyCount: Total number of repliesisLiked: Whether current user liked itlikeCount: Number of likesisMine: Whether comment belongs to current user
UserInfo
Current user information:
uuid: User unique identifiername: User display nameinitials: Generated initials from name
CommentConfig
Configuration for the comments module:
badWords: String containing bad words list (one per line)
Services #
CommentService
Abstract interface for comment operations. You must implement:
fetchComments(): Fetch comments for an entitypostComment(): Post a new commentpostReply(): Post a replytoggleLikeComment(): Toggle like statusdeleteComment(): Delete a commentfetchReplies(): Fetch replies for a commentsendReportComment(): Report a commentsendReportUser(): Report a usersendBlockUser(): Block a user
FeatureChecker
Interface for feature/insight checking:
commentHasFeature(): Check if a feature is enabledcommentHasInsight(): Check if an insight should be shown
Widgets #
CommentBottomSheet
Main comments UI widget. Parameters:
userBlockedUntil: Optional DateTime if user is blockedfeatureChecker: Feature checker implementationservice: Comment service implementationuser: Current user informationtheme: Optional custom themeappActions: Optional custom app actionstextStyle: Optional custom text styles
CommentTile
Individual comment widget. Displays comment content, actions, and replies.
ReportCommentBottomSheet
Bottom sheet for reporting comments and users.
Adding Custom Translations #
To add support for additional languages:
- Create a new ARB file in
lib/l10n/(e.g.,app_fr.arb) - Copy the structure from
app_en.arb - Translate all the strings
- Regenerate localizations:
flutter gen-l10n
The delegate is extensible and can be combined with other localization delegates in your app.
Requirements #
- Flutter >= 1.17.0
- Dart >= 3.10.1
providerpackage (^6.1.5+1)
Example #
See the example directory for a complete working example.
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License #
This project is licensed under the MIT License - see the LICENSE file for details.