v_chat_bubbles 1.2.7
v_chat_bubbles: ^1.2.7 copied to clipboard
A comprehensive Flutter chat bubble library supporting Telegram, WhatsApp, Messenger, and iMessage styles with full theming, callbacks, text formatting, and custom bubble support.
v_chat_bubbles #
A comprehensive Flutter package for building chat interfaces with multiple messaging platform styles (Telegram, WhatsApp, Messenger, iMessage). Provides highly customizable bubble widgets, theming, callbacks, text formatting, and an extensible architecture for custom message types.
📸 Screenshots #
Preview |
Telegram |
|
Reactions |
Polls & Location |
RTL Support |
Web & Desktop Support |
||
Table of Contents #
- Installation
- Quick Start
- Architecture Overview
- VBubbleScope
- VBubbleConfig
- VBubbleTheme
- VBubbleCallbacks
- Available Bubble Widgets
- Text Formatting & Patterns
- Custom Bubbles
- Context Extensions
- Selection Mode
- Performance Optimizations
Installation #
Add to your pubspec.yaml:
dependencies:
v_chat_bubbles: ^latest_version
Then run:
flutter pub get
Quick Start #
import 'package:v_chat_bubbles/v_chat_bubbles.dart';
class ChatScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return VBubbleScope(
style: VBubbleStyle.telegram,
config: VBubbleConfig(),
callbacks: VBubbleCallbacks(
onTap: (messageId) => print('Tapped: $messageId'),
onPatternTap: (match) => handlePatternTap(match),
),
child: ListView(
children: [
VTextBubble(
messageId: 'msg_1',
isMeSender: true,
time: '12:30',
text: 'Hello! How are you?',
status: VMessageStatus.read,
),
VTextBubble(
messageId: 'msg_2',
isMeSender: false,
time: '12:31',
text: 'I am fine, thanks!',
senderName: 'John',
avatar: VPlatformFile.fromUrl(networkUrl: 'https://example.com/avatar.jpg'),
),
],
),
);
}
}
Architecture Overview #
VBubbleScope (InheritedWidget - provides config to all children)
│
├── VBubbleStyle (telegram, whatsapp, messenger, imessage, custom)
├── VBubbleTheme (colors, typography, gradients)
├── VBubbleConfig (nested configuration objects)
├── VBubbleCallbacks (event handlers)
└── Child Widgets
│
└── BaseBubble (abstract base class)
│
├── VTextBubble
├── VImageBubble
├── VVideoBubble
├── VVoiceBubble
├── VFileBubble
├── VLocationBubble
├── VContactBubble
├── VPollBubble
├── VCallBubble
├── VGalleryBubble
├── VCustomBubble<T>
└── ... more
VBubbleScope #
The root widget that provides configuration to all bubble descendants via InheritedWidget.
Constructor Parameters #
| Parameter | Type | Default | Description |
|---|---|---|---|
style |
VBubbleStyle |
telegram |
Visual style preset |
theme |
VBubbleTheme? |
null |
Custom theme (auto-generated if null) |
config |
VBubbleConfig |
VBubbleConfig() |
Configuration object |
callbacks |
VBubbleCallbacks |
VBubbleCallbacks() |
Event callbacks |
isSelectionMode |
bool |
false |
Enable multi-selection |
selectedIds |
Set<String> |
{} |
Currently selected message IDs |
menuItemsBuilder |
VMenuItemsBuilder? |
null |
Dynamic context menu items |
child |
Widget |
required | Child widget tree |
Available Styles #
enum VBubbleStyle {
telegram, // Gradient bubbles with distinctive tails
whatsapp, // Green outgoing, white incoming
messenger, // Blue gradient outgoing, gray incoming
imessage, // Blue outgoing, gray incoming, minimal tails
custom, // Fully custom styling
}
Example #
VBubbleScope(
style: VBubbleStyle.whatsapp,
theme: VBubbleTheme.whatsappLight(),
config: VBubbleConfig.groupChat(),
callbacks: _buildCallbacks(),
isSelectionMode: _isSelectionMode,
selectedIds: _selectedIds,
menuItemsBuilder: (messageId, messageType, isMeSender) {
return isMeSender
? [VDefaultMenuItems.edit, VDefaultMenuItems.delete]
: [VDefaultMenuItems.reply, VDefaultMenuItems.forward];
},
child: ListView.builder(...),
)
VBubbleConfig #
Nested configuration object controlling all aspects of bubble behavior.
Constructor #
VBubbleConfig({
VPatternConfig patterns,
VGestureConfig gestures,
VAvatarConfig avatar,
VSizingConfig sizing,
VSpacingConfig spacing,
VMediaConfig media,
VTextExpansionConfig textExpansion,
VAnimationConfig animation,
VAccessibilityConfig accessibility,
VTranslationConfig translations,
})
Config Presets #
VBubbleConfig() // Default configuration
VBubbleConfig.compact() // Dense layout, smaller elements
VBubbleConfig.desktop() // Wider bubbles, more spacing
VBubbleConfig.readOnly() // No gestures (view-only mode)
VBubbleConfig.directChat() // Hidden avatars (1:1 chat)
VBubbleConfig.groupChat() // Always show avatars
VBubbleConfig.minimal() // No tails, clean look
VBubbleConfig.accessible() // Enhanced accessibility
VBubbleConfig.performance() // No animations, minimal processing
VPatternConfig #
Controls text pattern detection and formatting.
| Property | Type | Default | Description |
|---|---|---|---|
enableLinks |
bool |
true |
Detect URLs |
enableEmails |
bool |
true |
Detect email addresses |
enablePhones |
bool |
true |
Detect phone numbers |
enableMentions |
bool |
false |
Detect @username |
enableHashtags |
bool |
false |
Detect #hashtag |
enableFormatting |
bool |
false |
Enable bold, italic, |
enableCodeBlocks |
bool |
false |
Enable ```code blocks``` |
enableBlockquotes |
bool |
false |
Enable > blockquotes |
enableBulletLists |
bool |
false |
Enable - bullet lists |
enableNumberedLists |
bool |
false |
Enable 1. numbered lists |
customPatterns |
List<VCustomPattern>? |
null |
Custom regex patterns |
Presets:
VPatternConfig.standard // Links, emails, phones
VPatternConfig.none // No detection
VPatternConfig.linksOnly // Only URLs
VPatternConfig.withFormatting // Standard + inline formatting
VPatternConfig.markdown // Full markdown support (all patterns)
VPatternConfig.blocksOnly // Only block-level patterns
Custom Pattern Example:
VPatternConfig(
enableLinks: true,
customPatterns: [
VCustomPattern(
id: 'ticket',
pattern: RegExp(r'TKT-\d+'),
style: TextStyle(color: Colors.purple, fontWeight: FontWeight.bold),
isTappable: true,
),
VCustomPattern(
id: 'order',
pattern: RegExp(r'ORD#\d+'),
style: TextStyle(color: Colors.orange),
isTappable: true,
),
],
)
VGestureConfig #
Controls gesture interactions.
| Property | Type | Default | Description |
|---|---|---|---|
enableSwipeToReply |
bool |
true |
Swipe right to reply |
enableLongPress |
bool |
true |
Long press for context menu |
enableDoubleTapToReact |
bool |
false |
Double tap to add reaction |
enableHapticFeedback |
bool |
true |
Vibration on interactions |
swipeThreshold |
double |
64 |
Swipe distance to trigger reply |
Presets:
VGestureConfig.all // All gestures enabled
VGestureConfig.none // No gestures (read-only)
VAvatarConfig #
Controls avatar display.
| Property | Type | Default | Description |
|---|---|---|---|
show |
bool |
true |
Show avatars |
position |
VAvatarPosition |
bottom |
Avatar position (top/bottom) |
size |
double |
32 |
Avatar diameter |
Presets:
VAvatarConfig.visible // Show avatars
VAvatarConfig.hidden // Hide avatars
VAvatarConfig.large // Larger avatars (40px)
VSizingConfig #
Controls bubble dimensions.
| Property | Type | Default | Description |
|---|---|---|---|
maxWidthFraction |
double |
0.75 |
Max width as fraction of screen |
maxWidth |
double? |
null |
Absolute max width |
minWidth |
double |
80 |
Minimum bubble width |
Presets:
VSizingConfig.standard // 75% max width
VSizingConfig.compact // 65% max width
VSizingConfig.wide // 85% max width
VSpacingConfig #
Controls spacing and padding.
| Property | Type | Default | Description |
|---|---|---|---|
bubbleRadius |
double |
16 |
Bubble corner radius |
tailSize |
double |
8 |
Bubble tail size |
sameSenderSpacing |
double |
2 |
Space between same-sender messages |
differentSenderSpacing |
double |
8 |
Space between different-sender messages |
contentPaddingHorizontal |
double |
12 |
Horizontal content padding |
contentPaddingVertical |
double |
8 |
Vertical content padding |
VMediaConfig #
Controls media message display.
| Property | Type | Default | Description |
|---|---|---|---|
cornerRadius |
double |
12 |
Media corner radius |
imageMaxHeight |
double |
300 |
Max image height |
videoMaxHeight |
double |
250 |
Max video height |
voiceWaveformHeight |
double |
32 |
Voice waveform height |
fileMessageWidth |
double |
240 |
File bubble width |
gallerySpacing |
double |
2 |
Gallery grid spacing |
VTextExpansionConfig #
Controls expandable text behavior.
| Property | Type | Default | Description |
|---|---|---|---|
enabled |
bool |
true |
Enable text expansion |
characterThreshold |
int |
500 |
Characters before truncation |
VAnimationConfig #
Controls animation durations.
| Property | Type | Default | Description |
|---|---|---|---|
fadeIn |
Duration |
200ms |
Fade in duration |
fadeOut |
Duration |
150ms |
Fade out duration |
expand |
Duration |
300ms |
Expand animation |
collapse |
Duration |
250ms |
Collapse animation |
highlight |
Duration |
1500ms |
Highlight animation |
swipe |
Duration |
600ms |
Swipe animation |
defaultCurve |
Curve |
easeOutCubic |
Animation curve |
Presets:
VAnimationConfig.standard // Default timings
VAnimationConfig.fast // Snappy animations
VAnimationConfig.slow // Smooth animations
VAnimationConfig.none // No animations (instant)
VAccessibilityConfig #
Controls accessibility features.
| Property | Type | Default | Description |
|---|---|---|---|
enableSemanticLabels |
bool |
true |
Screen reader labels |
minTapTargetSize |
double |
48 |
Minimum tap target (44+ recommended) |
enableHighContrast |
bool |
false |
High contrast mode |
semanticLabelBuilder |
Function? |
null |
Custom semantic label builder |
VTranslationConfig #
Controls localized strings.
VTranslationConfig(
seeMore: 'See more',
seeLess: 'See less',
edited: 'edited',
forwarded: 'Forwarded',
repliedTo: 'Replied to',
you: 'You',
// ... more
)
// Preset for locale
VTranslationConfig.forLocale(Locale('ar')) // Arabic translations
VTranslationConfig.forLocale(Locale('en')) // English translations
VBubbleTheme #
Controls colors, typography, and visual styling.
Factory Constructors #
// Style + brightness combinations
VBubbleTheme.telegramLight()
VBubbleTheme.telegramDark()
VBubbleTheme.whatsappLight()
VBubbleTheme.whatsappDark()
VBubbleTheme.messengerLight()
VBubbleTheme.messengerDark()
VBubbleTheme.imessageLight()
VBubbleTheme.imessageDark()
// Get theme by enum
VBubbleTheme.fromStyle(VBubbleStyle.telegram, brightness: Brightness.light)
// Custom theme
VBubbleTheme.custom(
outgoingBubbleColor: Colors.blue,
incomingBubbleColor: Colors.grey[200]!,
accentColor: Colors.blue,
brightness: Brightness.light,
)
Key Properties #
| Property | Type | Description |
|---|---|---|
outgoingBubbleColor |
Color |
Sent message bubble color |
incomingBubbleColor |
Color |
Received message bubble color |
outgoingTextColor |
Color |
Sent message text color |
incomingTextColor |
Color |
Received message text color |
outgoingBubbleGradient |
Gradient? |
Gradient for sent bubbles |
incomingBubbleGradient |
Gradient? |
Gradient for received bubbles |
outgoingLinkColor |
Color |
Link color in sent messages |
incomingLinkColor |
Color |
Link color in received messages |
messageTextStyle |
TextStyle |
Message body text style |
timeTextStyle |
TextStyle |
Timestamp text style |
senderNameTextStyle |
TextStyle |
Sender name text style |
captionTextStyle |
TextStyle |
Caption text style |
linkTextStyle |
TextStyle |
Link text style |
availableReactions |
List<String> |
Emoji reactions for menu |
readIconColor |
Color |
Read receipt checkmark color |
VBubbleCallbacks #
Event handlers for bubble interactions.
Context Menu Behavior #
By default, long-pressing a bubble opens the built-in iOS-style context menu (CupertinoContextMenu). You can customize this behavior:
| Scenario | Behavior |
|---|---|
onLongPress not set |
Built-in context menu opens |
onLongPress is set |
Your custom callback is called, built-in menu does NOT open |
// Option 1: Use built-in context menu (default)
VBubbleCallbacks(
// No onLongPress - built-in menu will open
onMenuItemSelected: (messageId, item) {
// Handle menu item selection
},
)
// Option 2: Custom long press handler (replaces built-in menu)
VBubbleCallbacks(
onLongPress: (messageId, position) {
// Show your own menu at position
showMenu(
context: context,
position: RelativeRect.fromLTRB(
position.dx, position.dy, position.dx, position.dy,
),
items: [
PopupMenuItem(child: Text('Reply')),
PopupMenuItem(child: Text('Copy')),
PopupMenuItem(child: Text('Delete')),
],
);
},
)
All Callbacks #
VBubbleCallbacks(
// === Core Callbacks ===
onTap: (String messageId) { },
onLongPress: (String messageId, Offset position) { }, // Replaces built-in menu when set
onSwipeReply: (String messageId) { },
onSelectionChanged: (String messageId, bool isSelected) { },
onAvatarTap: (String senderId) { },
onReplyPreviewTap: (String originalMessageId) { },
// === Grouped Callbacks ===
onReaction: (String messageId, String emoji, ReactionAction action) { },
onReactionTap: (String messageId, String emoji, Offset position) { },
onPatternTap: (VPatternMatch match) { },
onMediaTap: (VMediaTapData data) { },
onMenuItemSelected: (String messageId, VBubbleMenuItem item) { },
// === Type-Specific Callbacks ===
onPollVote: (String messageId, String optionId) { },
onLocationTap: (VLocationTapData data) { },
onContactTap: (VContactTapData data) { },
onCallTap: (String messageId, bool isVideo) { },
onExpandToggle: (String messageId, bool isExpanded) { },
onDownload: (String messageId) { },
onTransferStateChanged: (String messageId, VMediaTransferAction action) { },
)
Pattern Match Object #
class VPatternMatch {
final String patternId; // 'url', 'email', 'phone', 'mention', or custom ID
final String matchedText; // The matched text (transformed if applicable)
final String rawText; // Original matched text
final String? messageId; // Parent message ID
}
Example Handler #
onPatternTap: (match) {
switch (match.patternId) {
case 'url':
launchUrl(Uri.parse(match.matchedText));
break;
case 'email':
launchUrl(Uri.parse('mailto:${match.matchedText}'));
break;
case 'phone':
launchUrl(Uri.parse('tel:${match.matchedText}'));
break;
case 'mention':
navigateToUser(match.matchedText);
break;
case 'ticket': // Custom pattern
openTicket(match.matchedText);
break;
}
}
Available Bubble Widgets #
Common Properties (BaseBubble) #
All bubble widgets share these properties:
| Property | Type | Required | Description |
|---|---|---|---|
messageId |
String |
Yes | Unique message identifier |
isMeSender |
bool |
Yes | True if current user sent |
time |
String |
Yes | Display time (e.g., "12:30") |
status |
VMessageStatus? |
No | Delivery status |
isSameSender |
bool |
No | Same sender as previous message |
avatar |
VPlatformFile? |
No | Sender avatar image |
senderName |
String? |
No | Sender display name |
senderColor |
Color? |
No | Sender name color |
replyTo |
VReplyData? |
No | Reply preview data |
forwardedFrom |
VForwardData? |
No | Forward info |
reactions |
List<VBubbleReaction> |
No | Message reactions |
isEdited |
bool |
No | Show "edited" label |
isPinned |
bool |
No | Show pin indicator |
isStarred |
bool |
No | Show star indicator |
isHighlighted |
bool |
No | Highlight animation |
VTextBubble #
VTextBubble(
messageId: 'msg_1',
isMeSender: true,
time: '12:30',
text: 'Hello *world*! Check https://flutter.dev',
linkPreview: VLinkPreviewData(
url: 'https://flutter.dev',
title: 'Flutter',
description: 'Build apps for any screen',
image: VPlatformFile.fromUrl(networkUrl: '...'),
),
status: VMessageStatus.read,
)
VImageBubble #
VImageBubble(
messageId: 'msg_2',
isMeSender: false,
time: '12:31',
imageFile: VPlatformFile.fromUrl(networkUrl: 'https://example.com/image.jpg'),
caption: 'Beautiful sunset!',
aspectRatio: 16/9,
)
VVideoBubble #
VVideoBubble(
messageId: 'msg_3',
isMeSender: true,
time: '12:32',
videoFile: VPlatformFile.fromUrl(
networkUrl: 'https://example.com/video.mp4',
fileSize: 15728640, // 15 MB
),
thumbnailFile: VPlatformFile.fromUrl(networkUrl: '...'),
duration: Duration(minutes: 2, seconds: 30),
caption: 'Check this out!',
)
VVoiceBubble #
VVoiceBubble(
messageId: 'msg_4',
isMeSender: false,
time: '12:33',
controller: VVoiceMessageController(
audioSrc: 'https://example.com/voice.mp3',
maxDuration: Duration(minutes: 1, seconds: 30),
waveform: [0.2, 0.5, 0.8, 0.3, 0.6, ...], // Optional waveform data
),
)
VFileBubble #
VFileBubble(
messageId: 'msg_5',
isMeSender: true,
time: '12:34',
file: VPlatformFile.fromUrl(
networkUrl: 'https://example.com/document.pdf',
fileSize: 2457600, // 2.4 MB
),
transferState: VTransferState.completed,
)
VLocationBubble #
VLocationBubble(
messageId: 'msg_6',
isMeSender: false,
time: '12:35',
locationData: VLocationData(
latitude: 40.7128,
longitude: -74.0060,
address: 'New York, NY',
staticMapUrl: 'https://maps.example.com/static/...',
),
)
VContactBubble #
VContactBubble(
messageId: 'msg_7',
isMeSender: true,
time: '12:36',
contactData: VContactData(
name: 'John Smith',
phoneNumber: '+1-555-123-4567',
avatar: VPlatformFile.fromUrl(networkUrl: '...'),
),
)
VPollBubble #
VPollBubble(
messageId: 'msg_8',
isMeSender: false,
time: '12:37',
pollData: VPollData(
question: 'What is your favorite framework?',
options: [
VPollOption(id: '1', text: 'Flutter', voteCount: 150, percentage: 60),
VPollOption(id: '2', text: 'React Native', voteCount: 75, percentage: 30),
VPollOption(id: '3', text: 'Other', voteCount: 25, percentage: 10),
],
totalVotes: 250,
hasVoted: true,
mode: VPollMode.single,
),
)
VCallBubble #
VCallBubble(
messageId: 'msg_9',
isMeSender: true,
time: '12:38',
callData: VCallData(
type: VCallType.video,
status: VCallStatus.completed,
duration: Duration(minutes: 5, seconds: 30),
),
)
VGalleryBubble #
VGalleryBubble(
messageId: 'msg_10',
isMeSender: false,
time: '12:39',
items: [
VGalleryItemData(messageId: 'img_1', file: VPlatformFile.fromUrl(...)),
VGalleryItemData(messageId: 'img_2', file: VPlatformFile.fromUrl(...)),
VGalleryItemData(messageId: 'img_3', file: VPlatformFile.fromUrl(...)),
],
)
VSystemBubble #
VSystemBubble(text: 'John joined the group')
VDateChip #
VDateChip(date: 'Today')
VDeletedBubble #
VDeletedBubble(
isMeSender: true,
time: '12:40',
)
Text Formatting & Patterns #
Supported Syntax #
| Format | Syntax | Output |
|---|---|---|
| Bold | *text* |
text |
| Italic | _text_ |
text |
| Strikethrough | ~text~ |
|
| Inline Code | `code` |
code |
| Code Block | ```code``` |
Syntax highlighted block |
| Blockquote | > text |
Indented quote |
| Bullet List | - item |
Bulleted list |
| Numbered List | 1. item |
Numbered list |
Enable Formatting #
VBubbleConfig(
patterns: VPatternConfig(
enableFormatting: true, // Bold, italic, etc.
enableCodeBlocks: true, // ```code```
enableBlockquotes: true, // > quotes
enableBulletLists: true, // - items
enableNumberedLists: true, // 1. items
),
)
// Or use preset
VBubbleConfig(patterns: VPatternConfig.markdown)
Custom Bubbles #
Create custom bubble widgets using VCustomBubble or by extending BaseBubble.
Using VCustomBubble (Quick & Easy) #
// 1. Define your data model
@immutable
class VPaymentData extends VCustomBubbleData {
final double amount;
final String currency;
const VPaymentData({required this.amount, this.currency = 'USD'});
@override
String get contentType => 'payment';
}
// 2. Use VCustomBubble with a builder
VCustomBubble<VPaymentData>(
messageId: 'msg_payment_1',
isMeSender: true,
time: '12:30',
data: VPaymentData(amount: 99.99),
builder: (context, data) {
final theme = context.bubbleTheme;
final textColor = theme.outgoingTextColor;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.payment, color: Colors.green),
SizedBox(width: 8),
Text('Payment', style: TextStyle(color: textColor, fontWeight: FontWeight.bold)),
],
),
SizedBox(height: 8),
Text('\$${data.amount}', style: TextStyle(color: textColor, fontSize: 24)),
],
);
},
)
Extending BaseBubble (Full Control) #
class VPaymentBubble extends BaseBubble {
final VPaymentData paymentData;
@override
String get messageType => 'payment';
const VPaymentBubble({
super.key,
required super.messageId,
required super.isMeSender,
required super.time,
required this.paymentData,
super.status,
super.isSameSender,
super.avatar,
super.senderName,
super.senderColor,
});
@override
Widget buildContent(BuildContext context) {
final theme = context.bubbleTheme;
final textColor = selectTextColor(theme);
return buildBubbleContainer(
context: context,
showTail: !isSameSender,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Icon(Icons.payment, color: textColor),
SizedBox(width: 8),
Text('Payment', style: TextStyle(color: textColor, fontWeight: FontWeight.bold)),
],
),
SizedBox(height: 8),
Text(
'\$${paymentData.amount.toStringAsFixed(2)}',
style: TextStyle(color: textColor, fontSize: 24, fontWeight: FontWeight.w600),
),
SizedBox(height: 8),
buildMeta(context),
],
),
);
}
}
Available Helper Methods in BaseBubble #
| Method | Description |
|---|---|
buildBubbleContainer() |
Wraps content with styled bubble shape |
buildBubbleHeader() |
Forward header + sender name + reply preview |
buildMeta() |
Timestamp + status + flags |
buildTimestamp() |
Just timestamp widget |
buildStatusIcon() |
Just status indicator |
buildReactionsWidget() |
Reaction pills row |
selectTextColor(theme) |
Get correct text color |
selectSecondaryTextColor(theme) |
Get secondary text color |
selectLinkColor(theme) |
Get link/accent color |
Context Extensions #
Access scope data from any descendant widget:
// Full scope
final scope = context.bubbleScope;
// Individual properties
final theme = context.bubbleTheme;
final config = context.bubbleConfig;
final callbacks = context.bubbleCallbacks;
final style = context.bubbleStyle;
// Expansion state
final expandManager = context.expandStateManager;
final isExpanded = expandManager.isExpanded('msg_123');
// Custom builders
final hasBuilder = context.hasCustomBubbleBuilder('payment');
final builder = context.getCustomBubbleBuilder('payment');
Selection Mode #
Enable multi-selection for bulk operations:
class _ChatState extends State<ChatScreen> {
bool _isSelectionMode = false;
final Set<String> _selectedIds = {};
@override
Widget build(BuildContext context) {
return VBubbleScope(
isSelectionMode: _isSelectionMode,
selectedIds: _selectedIds,
callbacks: VBubbleCallbacks(
onSelectionChanged: (messageId, isSelected) {
setState(() {
if (isSelected) {
_selectedIds.add(messageId);
_isSelectionMode = true;
} else {
_selectedIds.remove(messageId);
if (_selectedIds.isEmpty) _isSelectionMode = false;
}
});
},
),
menuItemsBuilder: (messageId, messageType, isMeSender) {
return [...VDefaultMenuItems.textDefaults]; // Includes select option
},
child: ...,
);
}
}
Default Menu Items #
VDefaultMenuItems.reply
VDefaultMenuItems.forward
VDefaultMenuItems.copy
VDefaultMenuItems.delete
VDefaultMenuItems.download
VDefaultMenuItems.pin
VDefaultMenuItems.star
VDefaultMenuItems.select // Triggers selection mode
VDefaultMenuItems.edit
VDefaultMenuItems.report
// Preset lists
VDefaultMenuItems.textDefaults // reply, forward, copy, edit, pin, delete, select
VDefaultMenuItems.mediaDefaults // reply, forward, download, pin, delete
Performance Optimizations #
The package includes several built-in optimizations:
Caching System #
- Span caching: Parsed text spans cached per message
- Pattern caching: Built pattern lists cached per config
- Block widget caching: Block-level widgets cached
- Text direction caching: RTL/LTR detection cached
Efficient Rendering #
- shouldRepaint optimization: Bubble painters only repaint when properties change
- IntrinsicWidth wrapping: Bubbles shrink-wrap to content
- Lazy parsing: Block patterns only parsed when enabled
- Fast path detection: Quick checks before expensive regex operations
Best Practices #
// Use performance preset for very long lists
VBubbleConfig.performance()
// Disable unused patterns
VPatternConfig(
enableLinks: true,
enableEmails: false, // Disable if not needed
enablePhones: false,
enableFormatting: false,
)
// Use appropriate image sizes
VPlatformFile.fromUrl(
networkUrl: thumbnailUrl, // Use thumbnails, not full images
)
License #
MIT License - see LICENSE file for details.
Author #
Hatem Ragap 📧 Email: hatemragapdev@gmail.com 🔗 GitHub: github.com/hatemragab
Support #
For issues, feature requests, or questions:
- 📧 Email: hatemragapdev@gmail.com
- 🐛 GitHub Issues: Create an issue
- ⭐ Star us on GitHub if you find this package useful!
