showWithCustomHeader static method
- required BuildContext context,
- required String title,
- String? message,
- List<
CNSheetItem> items = const [], - List<
CNSheetItemRow> itemRows = const [], - List<
CNSheetInlineActions> inlineActions = const [], - List<
CNSheetDetent> detents = const [CNSheetDetent.large], - bool prefersGrabberVisible = true,
- bool isModal = true,
- bool prefersEdgeAttachedInCompactHeight = false,
- bool widthFollowsPreferredContentSizeWhenEdgeAttached = false,
- double? preferredCornerRadius,
- double? headerTitleSize,
- FontWeight? headerTitleWeight,
- Color? headerTitleColor,
- String headerTitleAlignment = 'left',
- String? subtitle,
- double? subtitleSize,
- Color? subtitleColor,
- double? headerHeight,
- Color? headerBackgroundColor,
- bool showHeaderDivider = true,
- Color? headerDividerColor,
- String closeButtonPosition = 'trailing',
- String closeButtonIcon = 'xmark',
- double? closeButtonSize,
- Color? closeButtonColor,
- Color? itemBackgroundColor,
- Color? itemTextColor,
- Color? itemTintColor,
- void onInlineActionSelected()?,
- void onItemSelected(
- int index
- void onItemRowSelected()?,
Shows a native sheet with custom header (title + close button).
This is like the Apple Notes formatting sheet - it has a custom header bar with the title on the left and a close button (X) on the right.
Key differences from show():
- Custom header with title and close button (like Notes app)
- Title is displayed in the header bar, not in the content area
- Close button allows manual dismissal
- Still supports nonmodal behavior with
isModal: false - Full control over header styling
Example:
await CNNativeSheet.showWithCustomHeader(
context: context,
title: 'Format',
headerTitleSize: 20,
headerTitleWeight: FontWeight.w600,
headerHeight: 56,
items: [
CNSheetItem(title: 'Bold', icon: 'bold'),
CNSheetItem(title: 'Italic', icon: 'italic'),
],
detents: [CNSheetDetent.custom(280)],
isModal: false, // Nonmodal - can interact with background
);
title - Title displayed in the header (required for custom header)
message - Optional message below the header
items - List of items to display
detents - Heights at which the sheet can rest
prefersGrabberVisible - Whether to show the grabber handle
isModal - Whether the sheet blocks background interaction
Header Styling Options:
headerTitleSize - Font size for the title (default: 20)
headerTitleWeight - Font weight for the title (default: semibold/600)
headerTitleColor - Color for the title (default: label color)
headerTitleAlignment - Alignment of title: 'left', 'center' (default: 'left')
subtitle - Optional subtitle/subheading displayed below the title
subtitleSize - Font size for the subtitle (default: 13)
subtitleColor - Color for the subtitle (default: secondary label)
headerHeight - Height of the header bar (default: 56)
headerBackgroundColor - Background color of the header (default: system background)
showHeaderDivider - Whether to show divider below header (default: true)
headerDividerColor - Color of the header divider (default: separator color)
closeButtonPosition - Position of close button: 'leading' or 'trailing' (default: 'trailing')
closeButtonIcon - SF Symbol name for close button (default: 'xmark')
closeButtonSize - Size of the close button icon (default: 17)
closeButtonColor - Color of the close button (default: label color)
itemBackgroundColor - Background color for sheet item buttons (default: clear)
itemTextColor - Text color for sheet item buttons (default: system label)
itemTintColor - Tint color for icons in sheet item buttons (default: system tint)
onInlineActionSelected - Callback invoked when an inline action is tapped, receives row and action indices
onItemSelected - Callback invoked when a regular vertical item is tapped, receives the item index
onItemRowSelected - Callback invoked when an item row item is tapped, receives row index and item index
Implementation
static Future<int?> showWithCustomHeader({
required BuildContext context,
required String title,
String? message,
List<CNSheetItem> items = const [],
List<CNSheetItemRow> itemRows = const [],
List<CNSheetInlineActions> inlineActions = const [],
List<CNSheetDetent> detents = const [CNSheetDetent.large],
bool prefersGrabberVisible = true,
bool isModal = true,
bool prefersEdgeAttachedInCompactHeight = false,
bool widthFollowsPreferredContentSizeWhenEdgeAttached = false,
double? preferredCornerRadius,
// Header styling
double? headerTitleSize,
FontWeight? headerTitleWeight,
Color? headerTitleColor,
String headerTitleAlignment = 'left',
String? subtitle,
double? subtitleSize,
Color? subtitleColor,
double? headerHeight,
Color? headerBackgroundColor,
bool showHeaderDivider = true,
Color? headerDividerColor,
String closeButtonPosition = 'trailing',
String closeButtonIcon = 'xmark',
double? closeButtonSize,
Color? closeButtonColor,
// Item styling
Color? itemBackgroundColor,
Color? itemTextColor,
Color? itemTintColor,
// Callbacks
void Function(int rowIndex, int actionIndex)? onInlineActionSelected,
void Function(int index)? onItemSelected,
void Function(int rowIndex, int itemIndex)? onItemRowSelected,
}) async {
try {
// Initialize handlers on first call
_initializeHandlers();
// Create a completer to wait for the native dismissal callback
final completer = Completer<int?>();
final sheetId = _generateSheetId();
_pendingSheets[sheetId] = completer;
// Serialize inline actions
final inlineActionsList = <Map<String, dynamic>>[];
for (final actionGroup in inlineActions) {
final actionMaps = actionGroup.actions.map((action) {
final map = <String, dynamic>{
'label': action.label,
'icon': action.icon,
'enabled': action.enabled,
'isToggled': action.isToggled,
'dismissOnTap': action.dismissOnTap,
};
// Add backgroundColor if provided (convert to ARGB int)
if (action.backgroundColor != null) {
map['backgroundColor'] = action.backgroundColor!.value;
}
// Add styling properties if provided
if (action.width != null) map['width'] = action.width;
if (action.iconSize != null) map['iconSize'] = action.iconSize;
if (action.labelSize != null) map['labelSize'] = action.labelSize;
if (action.cornerRadius != null) map['cornerRadius'] = action.cornerRadius;
if (action.iconLabelSpacing != null) map['iconLabelSpacing'] = action.iconLabelSpacing;
return map;
}).toList();
// Add row-level styling properties
final rowMap = <String, dynamic>{'actions': actionMaps};
if (actionGroup.spacing != null) rowMap['spacing'] = actionGroup.spacing;
if (actionGroup.horizontalPadding != null) rowMap['horizontalPadding'] = actionGroup.horizontalPadding;
if (actionGroup.verticalPadding != null) rowMap['verticalPadding'] = actionGroup.verticalPadding;
if (actionGroup.height != null) rowMap['height'] = actionGroup.height;
inlineActionsList.add(rowMap);
}
// Set up callback handler if provided - merge with the existing onDismiss handler
if (onInlineActionSelected != null || onItemSelected != null || onItemRowSelected != null) {
_customChannel.setMethodCallHandler((call) async {
if (call.method == 'onDismiss') {
final args = call.arguments as Map;
final selectedIndex = args['selectedIndex'] as int?;
// Complete the pending sheet completer for onDismiss
if (_pendingSheets.isNotEmpty) {
final key = _pendingSheets.keys.first;
final completer = _pendingSheets.remove(key);
completer?.complete(selectedIndex == -1 ? null : selectedIndex);
}
} else if (call.method == 'onInlineActionSelected' && onInlineActionSelected != null) {
final rowIndex = call.arguments['rowIndex'] as int;
final actionIndex = call.arguments['actionIndex'] as int;
onInlineActionSelected(rowIndex, actionIndex);
} else if (call.method == 'onItemSelected' && onItemSelected != null) {
final index = call.arguments['index'] as int;
onItemSelected(index);
} else if (call.method == 'onItemRowSelected' && onItemRowSelected != null) {
final rowIndex = call.arguments['rowIndex'] as int;
final itemIndex = call.arguments['itemIndex'] as int;
onItemRowSelected(rowIndex, itemIndex);
}
});
}
// Serialize item rows
final itemRowsList = itemRows.map((row) {
final rowMap = <String, dynamic>{
'items': row.items.map((item) => item.toMap()).toList(),
};
// Add row-level styling properties
if (row.spacing != null) rowMap['spacing'] = row.spacing;
if (row.height != null) rowMap['height'] = row.height;
return rowMap;
}).toList();
// Call native method (result will be null from this call)
await _customChannel.invokeMethod('showSheet', {
'title': title,
'message': message,
'items': items.map((item) => item.toMap()).toList(),
'itemRows': itemRowsList,
'inlineActions': inlineActionsList,
'detents': detents.map((d) => d.toMap()).toList(),
'prefersGrabberVisible': prefersGrabberVisible,
'isModal': isModal,
'prefersEdgeAttachedInCompactHeight':
prefersEdgeAttachedInCompactHeight,
'widthFollowsPreferredContentSizeWhenEdgeAttached':
widthFollowsPreferredContentSizeWhenEdgeAttached,
'preferredCornerRadius': preferredCornerRadius,
// Header styling parameters
if (headerTitleSize != null) 'headerTitleSize': headerTitleSize,
if (headerTitleWeight != null)
'headerTitleWeight': _fontWeightToString(headerTitleWeight),
if (headerTitleColor != null)
'headerTitleColor': headerTitleColor.value,
'headerTitleAlignment': headerTitleAlignment,
if (subtitle != null) 'subtitle': subtitle,
if (subtitleSize != null) 'subtitleSize': subtitleSize,
if (subtitleColor != null) 'subtitleColor': subtitleColor.value,
if (headerHeight != null) 'headerHeight': headerHeight,
if (headerBackgroundColor != null)
'headerBackgroundColor': headerBackgroundColor.value,
'showHeaderDivider': showHeaderDivider,
if (headerDividerColor != null)
'headerDividerColor': headerDividerColor.value,
'closeButtonPosition': closeButtonPosition,
'closeButtonIcon': closeButtonIcon,
if (closeButtonSize != null) 'closeButtonSize': closeButtonSize,
if (closeButtonColor != null)
'closeButtonColor': closeButtonColor.value,
// Item styling parameters
if (itemBackgroundColor != null)
'itemBackgroundColor': itemBackgroundColor.value,
if (itemTextColor != null) 'itemTextColor': itemTextColor.value,
if (itemTintColor != null) 'itemTintColor': itemTintColor.value,
});
// Wait for the sheet dismissal callback to complete the completer
return completer.future;
} catch (e) {
debugPrint('Error showing native custom header sheet: $e');
return null;
}
}