claude_session_viewer/lib/models/content_block.dart
Mathias Beaulieu-Duncan 780ef9378f feat: custom native file picker showing hidden files/folders
Replaced file_picker's Load/Browse with a custom NativePickerRegistrar
Swift plugin that opens NSOpenPanel with showsHiddenFiles = true.
The file_picker package hardcodes this to false, making hidden folders
like ~/.claude invisible in its dialogs.

Changes:
- New NativePickerRegistrar.swift: custom NSOpenPanel with hidden files
- New NativePicker Dart service using method channel
- Browse: only shows folders (canChooseFiles=false), hidden visible
- Load: only shows .jsonl files, hidden folders visible
- Registered via AppDelegate.applicationDidFinishLaunching
- Removed file_picker dependency from home_screen imports
- Fixed all info-level lint issues (super params, null-aware, doc comment)
- Signed, notarized, stapled DMG
2026-04-07 14:32:06 -04:00

117 lines
2.9 KiB
Dart

class ContentBlock {
final String type;
final Map<String, dynamic> raw;
ContentBlock({required this.type, required this.raw});
factory ContentBlock.fromJson(Map<String, dynamic> json) {
final type = json['type'] as String? ?? 'unknown';
switch (type) {
case 'text':
return TextBlock.fromJson(json);
case 'thinking':
return ThinkingBlock.fromJson(json);
case 'tool_use':
return ToolUseBlock.fromJson(json);
default:
return ContentBlock(type: type, raw: json);
}
}
}
class TextBlock extends ContentBlock {
final String text;
TextBlock({required this.text, required super.raw})
: super(type: 'text');
factory TextBlock.fromJson(Map<String, dynamic> json) {
return TextBlock(
text: json['text'] as String? ?? '',
raw: json,
);
}
}
class ThinkingBlock extends ContentBlock {
final String thinking;
final String? signature;
ThinkingBlock({
required this.thinking,
this.signature,
required super.raw,
}) : super(type: 'thinking');
factory ThinkingBlock.fromJson(Map<String, dynamic> json) {
return ThinkingBlock(
thinking: json['thinking'] as String? ?? '',
signature: json['signature'] as String?,
raw: json,
);
}
}
class ToolUseBlock extends ContentBlock {
final String id;
final String name;
final Map<String, dynamic> input;
ToolResultData? linkedResult;
ToolUseBlock({
required this.id,
required this.name,
required this.input,
this.linkedResult,
required super.raw,
}) : super(type: 'tool_use');
factory ToolUseBlock.fromJson(Map<String, dynamic> json) {
return ToolUseBlock(
id: json['id'] as String? ?? '',
name: json['name'] as String? ?? '',
input: (json['input'] as Map<String, dynamic>?) ?? {},
raw: json,
);
}
bool get isAgentCall => name == 'Agent';
String? get subagentType => isAgentCall ? input['subagent_type'] as String? : null;
String? get agentDescription => isAgentCall ? input['description'] as String? : null;
String? get agentPrompt => isAgentCall ? input['prompt'] as String? : null;
}
class ToolResultData {
final String toolUseId;
final dynamic content;
final bool isError;
final Map<String, dynamic> raw;
ToolResultData({
required this.toolUseId,
this.content,
this.isError = false,
required this.raw,
});
String get textContent {
if (content is String) return content;
if (content is List) {
return (content as List)
.where((c) => c is Map && c['type'] == 'text')
.map((c) => c['text'] as String? ?? '')
.join('\n');
}
return content?.toString() ?? '';
}
factory ToolResultData.fromJson(Map<String, dynamic> json) {
return ToolResultData(
toolUseId: json['tool_use_id'] as String? ?? '',
content: json['content'],
isError: json['is_error'] as bool? ?? false,
raw: json,
);
}
}