Compare commits
No commits in common. "main" and "0.1.0-rc1" have entirely different histories.
@ -22,8 +22,8 @@ class ContentBlock {
|
||||
class TextBlock extends ContentBlock {
|
||||
final String text;
|
||||
|
||||
TextBlock({required this.text, required super.raw})
|
||||
: super(type: 'text');
|
||||
TextBlock({required this.text, required Map<String, dynamic> raw})
|
||||
: super(type: 'text', raw: raw);
|
||||
|
||||
factory TextBlock.fromJson(Map<String, dynamic> json) {
|
||||
return TextBlock(
|
||||
@ -40,8 +40,8 @@ class ThinkingBlock extends ContentBlock {
|
||||
ThinkingBlock({
|
||||
required this.thinking,
|
||||
this.signature,
|
||||
required super.raw,
|
||||
}) : super(type: 'thinking');
|
||||
required Map<String, dynamic> raw,
|
||||
}) : super(type: 'thinking', raw: raw);
|
||||
|
||||
factory ThinkingBlock.fromJson(Map<String, dynamic> json) {
|
||||
return ThinkingBlock(
|
||||
@ -63,8 +63,8 @@ class ToolUseBlock extends ContentBlock {
|
||||
required this.name,
|
||||
required this.input,
|
||||
this.linkedResult,
|
||||
required super.raw,
|
||||
}) : super(type: 'tool_use');
|
||||
required Map<String, dynamic> raw,
|
||||
}) : super(type: 'tool_use', raw: raw);
|
||||
|
||||
factory ToolUseBlock.fromJson(Map<String, dynamic> json) {
|
||||
return ToolUseBlock(
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import 'dart:io';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../providers/session_provider.dart';
|
||||
import '../../services/native_picker.dart';
|
||||
import '../../theme/app_theme.dart';
|
||||
|
||||
// ─── Data models ─────────────────────────────────────────────
|
||||
@ -100,27 +100,15 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
bool _scanning = true;
|
||||
String _searchQuery = '';
|
||||
_Project? _selectedProject;
|
||||
_Project? _customFolderProject;
|
||||
final _pathController = TextEditingController();
|
||||
final _pathFocusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final home = _getRealHome() ?? '';
|
||||
_pathController.text = '$home/.claude/projects';
|
||||
_scanProjects();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pathController.dispose();
|
||||
_pathFocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// Returns the real user home directory, even inside App Sandbox.
|
||||
/// In sandbox, HOME points to `~/Library/Containers/bundleid/Data`.
|
||||
/// In sandbox, HOME points to ~/Library/Containers/<bundleid>/Data.
|
||||
String? _getRealHome() {
|
||||
final home = Platform.environment['HOME'];
|
||||
if (home == null) return null;
|
||||
@ -302,130 +290,15 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a path string, expanding ~ to the home directory.
|
||||
String _resolvePath(String input) {
|
||||
final home = _getRealHome() ?? Platform.environment['HOME'] ?? '';
|
||||
var path = input.trim();
|
||||
if (path.startsWith('~/')) {
|
||||
path = '$home${path.substring(1)}';
|
||||
} else if (path == '~') {
|
||||
path = home;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
Future<void> _scanFromPathInput() async {
|
||||
final resolved = _resolvePath(_pathController.text);
|
||||
final dir = Directory(resolved);
|
||||
if (await dir.exists()) {
|
||||
await _scanFolder(resolved);
|
||||
} else {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Folder not found: $resolved'),
|
||||
backgroundColor: AppColors.error,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _pickFolder() async {
|
||||
try {
|
||||
Future<void> _pickFile() async {
|
||||
final home = _getRealHome() ?? '';
|
||||
final result = await NativePicker.getDirectoryPath(
|
||||
initialDirectory: '$home/.claude/projects',
|
||||
);
|
||||
if (result != null) {
|
||||
_pathController.text = result;
|
||||
await _scanFolder(result);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('[Browse] Error: $e');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Could not open folder picker: $e'),
|
||||
backgroundColor: AppColors.error,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _scanFolder(String folderPath) async {
|
||||
setState(() {
|
||||
_scanning = true;
|
||||
_selectedProject = null;
|
||||
});
|
||||
|
||||
final sessions = <_SessionFile>[];
|
||||
|
||||
// Recursively scan for .jsonl files
|
||||
final dir = Directory(folderPath);
|
||||
if (await dir.exists()) {
|
||||
await _scanFolderRecursive(dir, sessions);
|
||||
}
|
||||
|
||||
sessions.sort((a, b) => b.stat.modified.compareTo(a.stat.modified));
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_customFolderProject = _Project(
|
||||
dirPath: folderPath,
|
||||
rawDirName: folderPath.split('/').last,
|
||||
displayName: folderPath.split('/').last,
|
||||
fullPath: folderPath,
|
||||
sessions: sessions,
|
||||
lastModified: sessions.isNotEmpty ? sessions.first.stat.modified : DateTime.now(),
|
||||
);
|
||||
_selectedProject = _customFolderProject;
|
||||
_scanning = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _scanFolderRecursive(Directory dir, List<_SessionFile> sessions) async {
|
||||
try {
|
||||
await for (final entity in dir.list()) {
|
||||
if (entity is File && entity.path.endsWith('.jsonl')) {
|
||||
try {
|
||||
final stat = entity.statSync();
|
||||
final fileName = entity.path.split('/').last;
|
||||
sessions.add(_SessionFile(
|
||||
file: entity,
|
||||
stat: stat,
|
||||
sessionId: fileName.replaceAll('.jsonl', ''),
|
||||
));
|
||||
} catch (_) {}
|
||||
} else if (entity is Directory) {
|
||||
await _scanFolderRecursive(entity, sessions);
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
Future<void> _loadSingleFile() async {
|
||||
try {
|
||||
final home = _getRealHome() ?? '';
|
||||
final result = await NativePicker.pickFile(
|
||||
final result = await FilePicker.platform.pickFiles(
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ['jsonl'],
|
||||
initialDirectory: '$home/.claude/projects',
|
||||
);
|
||||
if (result != null) {
|
||||
await _loadFile(result);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('[Load] Error: $e');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Could not open file picker: $e'),
|
||||
backgroundColor: AppColors.error,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (result != null && result.files.single.path != null) {
|
||||
await _loadFile(result.files.single.path!);
|
||||
}
|
||||
}
|
||||
|
||||
@ -516,13 +389,11 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
// ─── Header with breadcrumb ──────────────────────────────
|
||||
|
||||
Widget _buildHeader(SessionProvider provider) {
|
||||
return Column(
|
||||
children: [
|
||||
// Row 1: breadcrumb + count
|
||||
Row(
|
||||
return Row(
|
||||
children: [
|
||||
const Icon(Icons.terminal, size: 24, color: AppColors.assistant),
|
||||
const SizedBox(width: 10),
|
||||
// Breadcrumb
|
||||
InkWell(
|
||||
onTap: () => setState(() => _selectedProject = null),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
@ -565,95 +436,15 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
: '${_projects.length} projects',
|
||||
style: const TextStyle(fontSize: 12, color: AppColors.textMuted),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Row 2: path input + Load + Browse
|
||||
Row(
|
||||
children: [
|
||||
// Path input field — flexible to fill available space
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 36,
|
||||
child: TextField(
|
||||
controller: _pathController,
|
||||
focusNode: _pathFocusNode,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.textPrimary,
|
||||
fontFamily: 'JetBrains Mono',
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Path to scan (e.g. ~/.claude/projects)',
|
||||
hintStyle: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.textMuted,
|
||||
fontFamily: 'JetBrains Mono',
|
||||
),
|
||||
isDense: true,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 8,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: AppColors.surface,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
borderSide: const BorderSide(color: AppColors.surfaceBorder),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
borderSide: const BorderSide(color: AppColors.surfaceBorder),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
borderSide: const BorderSide(color: AppColors.assistant),
|
||||
),
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.arrow_forward, size: 14,
|
||||
color: AppColors.textMuted),
|
||||
tooltip: 'Scan this folder',
|
||||
onPressed: _scanFromPathInput,
|
||||
),
|
||||
),
|
||||
onSubmitted: (_) => _scanFromPathInput(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
SizedBox(
|
||||
height: 36,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _loadSingleFile,
|
||||
icon: const Icon(Icons.insert_drive_file_outlined, size: 14),
|
||||
label: const Text('Load'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.surfaceLight,
|
||||
foregroundColor: AppColors.textPrimary,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 0),
|
||||
minimumSize: Size.zero,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
side: const BorderSide(color: AppColors.surfaceBorder),
|
||||
),
|
||||
textStyle: const TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
SizedBox(
|
||||
height: 36,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _pickFolder,
|
||||
const SizedBox(width: 12),
|
||||
ElevatedButton.icon(
|
||||
onPressed: _pickFile,
|
||||
icon: const Icon(Icons.folder_open, size: 14),
|
||||
label: const Text('Browse'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.surfaceLight,
|
||||
foregroundColor: AppColors.textPrimary,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 0),
|
||||
minimumSize: Size.zero,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
side: const BorderSide(color: AppColors.surfaceBorder),
|
||||
@ -661,17 +452,6 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
textStyle: const TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Tooltip(
|
||||
message: 'Type a path with hidden folders directly in the input field',
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 4),
|
||||
child: Icon(Icons.info_outline, size: 14, color: AppColors.textMuted),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,41 +0,0 @@
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// Native file picker that shows hidden files/folders on macOS.
|
||||
/// Uses a custom method channel to NSOpenPanel with showsHiddenFiles = true,
|
||||
/// which the file_picker package hardcodes to false.
|
||||
class NativePicker {
|
||||
static const _channel = MethodChannel('com.svrnty.native_picker');
|
||||
|
||||
/// Opens a folder picker that shows hidden directories.
|
||||
/// Returns the selected folder path, or null if cancelled.
|
||||
static Future<String?> getDirectoryPath({String? initialDirectory}) async {
|
||||
try {
|
||||
return _channel.invokeMethod<String?>('getDirectoryPath', {
|
||||
'initialDirectory': initialDirectory,
|
||||
});
|
||||
} on PlatformException catch (e) {
|
||||
throw Exception('Native picker error: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens a file picker that shows hidden files, filtered to specific extensions.
|
||||
/// Returns the selected file path, or null if cancelled.
|
||||
static Future<String?> pickFile({
|
||||
List<String> allowedExtensions = const [],
|
||||
String? initialDirectory,
|
||||
}) async {
|
||||
try {
|
||||
final result = await _channel.invokeMethod<List?>('pickFiles', {
|
||||
'allowMultiple': false,
|
||||
'allowedExtensions': allowedExtensions,
|
||||
'initialDirectory': initialDirectory,
|
||||
});
|
||||
if (result != null && result.isNotEmpty) {
|
||||
return result.first as String;
|
||||
}
|
||||
return null;
|
||||
} on PlatformException catch (e) {
|
||||
throw Exception('Native picker error: ${e.message}');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
|
||||
@ -1 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
|
||||
42
macos/Podfile
Normal file
42
macos/Podfile
Normal file
@ -0,0 +1,42 @@
|
||||
platform :osx, '13.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
project 'Runner', {
|
||||
'Debug' => :debug,
|
||||
'Profile' => :release,
|
||||
'Release' => :release,
|
||||
}
|
||||
|
||||
def flutter_root
|
||||
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
|
||||
unless File.exist?(generated_xcode_build_settings_path)
|
||||
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
|
||||
end
|
||||
|
||||
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||
return matches[1].strip if matches
|
||||
end
|
||||
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
|
||||
end
|
||||
|
||||
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||
|
||||
flutter_macos_podfile_setup
|
||||
|
||||
target 'Runner' do
|
||||
use_frameworks!
|
||||
|
||||
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
|
||||
target 'RunnerTests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_macos_build_settings(target)
|
||||
end
|
||||
end
|
||||
16
macos/Podfile.lock
Normal file
16
macos/Podfile.lock
Normal file
@ -0,0 +1,16 @@
|
||||
PODS:
|
||||
- FlutterMacOS (1.0.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
FlutterMacOS:
|
||||
:path: Flutter/ephemeral
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||
|
||||
PODFILE CHECKSUM: 89c84cf5c2351c1e554c6dea18d31a879fc3a19e
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
@ -21,15 +21,16 @@
|
||||
/* End PBXAggregateTarget section */
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
130C39CAC9CC7AA143C489DF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C8D4F4D6A14DBC06CABB730 /* Pods_Runner.framework */; };
|
||||
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
|
||||
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
|
||||
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
|
||||
AABB001B1B1B1B1B1B1B /* NativePickerRegistrar.swift in Sources */ = {isa = PBXBuildFile; fileRef = AABB001A1A1A1A1A1A1A /* NativePickerRegistrar.swift */; };
|
||||
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
|
||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
||||
33CC10FF2044A3C60003C045 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10FE2044A3C60003C045 /* PrivacyInfo.xcprivacy */; };
|
||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
||||
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
|
||||
79F3DEC2140214566E19F388 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6CB8C54BF040E1E6BF05BCBD /* Pods_RunnerTests.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -63,27 +64,34 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
153C2DDE069AD579210ED2C4 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
|
||||
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
|
||||
33CC10ED2044A3C60003C045 /* Claude Session Analysis.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Claude Session Analysis.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
AABB001A1A1A1A1A1A1A /* NativePickerRegistrar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativePickerRegistrar.swift; sourceTree = "<group>"; };
|
||||
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
|
||||
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||
33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
|
||||
33CC10FE2044A3C60003C045 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
|
||||
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
|
||||
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
|
||||
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
|
||||
33CC10FE2044A3C60003C045 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
|
||||
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
|
||||
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
|
||||
4BD835AFFC7FFFD2AC416CAE /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
4C8D4F4D6A14DBC06CABB730 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
4EB19377A730AE2C095D7A54 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
6CB8C54BF040E1E6BF05BCBD /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||
881127F55C6D6CFE2534841B /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||
A5398467BE6DA3EC050E790F /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
C0A0ADC8E8ED2668AF72715E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -91,6 +99,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
79F3DEC2140214566E19F388 /* Pods_RunnerTests.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -99,6 +108,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
|
||||
130C39CAC9CC7AA143C489DF /* Pods_Runner.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -131,6 +141,8 @@
|
||||
33CEB47122A05771004F2AC0 /* Flutter */,
|
||||
331C80D6294CF71000263BE5 /* RunnerTests */,
|
||||
33CC10EE2044A3C60003C045 /* Products */,
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */,
|
||||
8208943E7AB9C5C3402F88ED /* Pods */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@ -170,7 +182,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
|
||||
AABB001A1A1A1A1A1A1A /* NativePickerRegistrar.swift */,
|
||||
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
|
||||
33CC10FE2044A3C60003C045 /* PrivacyInfo.xcprivacy */,
|
||||
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
|
||||
@ -181,6 +192,29 @@
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
8208943E7AB9C5C3402F88ED /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
153C2DDE069AD579210ED2C4 /* Pods-Runner.debug.xcconfig */,
|
||||
C0A0ADC8E8ED2668AF72715E /* Pods-Runner.release.xcconfig */,
|
||||
4BD835AFFC7FFFD2AC416CAE /* Pods-Runner.profile.xcconfig */,
|
||||
881127F55C6D6CFE2534841B /* Pods-RunnerTests.debug.xcconfig */,
|
||||
4EB19377A730AE2C095D7A54 /* Pods-RunnerTests.release.xcconfig */,
|
||||
A5398467BE6DA3EC050E790F /* Pods-RunnerTests.profile.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4C8D4F4D6A14DBC06CABB730 /* Pods_Runner.framework */,
|
||||
6CB8C54BF040E1E6BF05BCBD /* Pods_RunnerTests.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@ -188,6 +222,7 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
2D5E86AC9DD4210FFD8C9DE8 /* [CP] Check Pods Manifest.lock */,
|
||||
331C80D1294CF70F00263BE5 /* Sources */,
|
||||
331C80D2294CF70F00263BE5 /* Frameworks */,
|
||||
331C80D3294CF70F00263BE5 /* Resources */,
|
||||
@ -206,6 +241,7 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
DDC6FD45F59CD5AA33826D72 /* [CP] Check Pods Manifest.lock */,
|
||||
33CC10E92044A3C60003C045 /* Sources */,
|
||||
33CC10EA2044A3C60003C045 /* Frameworks */,
|
||||
33CC10EB2044A3C60003C045 /* Resources */,
|
||||
@ -300,6 +336,28 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
2D5E86AC9DD4210FFD8C9DE8 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
3399D490228B24CF009A79C7 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
@ -338,6 +396,28 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
|
||||
};
|
||||
DDC6FD45F59CD5AA33826D72 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@ -355,7 +435,6 @@
|
||||
files = (
|
||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
|
||||
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
|
||||
AABB001B1B1B1B1B1B1B /* NativePickerRegistrar.swift in Sources */,
|
||||
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -390,6 +469,7 @@
|
||||
/* Begin XCBuildConfiguration section */
|
||||
331C80DB294CF71000263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 881127F55C6D6CFE2534841B /* Pods-RunnerTests.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
@ -404,6 +484,7 @@
|
||||
};
|
||||
331C80DC294CF71000263BE5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 4EB19377A730AE2C095D7A54 /* Pods-RunnerTests.release.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
@ -418,6 +499,7 @@
|
||||
};
|
||||
331C80DD294CF71000263BE5 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = A5398467BE6DA3EC050E790F /* Pods-RunnerTests.profile.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
@ -637,19 +719,18 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = LD76P8L42W;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
OTHER_CODE_SIGN_FLAGS = "--timestamp --options runtime";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
OTHER_CODE_SIGN_FLAGS = "--timestamp --options runtime";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
|
||||
@ -10,13 +10,4 @@ class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
// Register our custom native picker that shows hidden files
|
||||
// This is called before Flutter engine starts
|
||||
if let controller = mainFlutterWindow?.contentViewController as? FlutterViewController {
|
||||
NativePickerRegistrar.register(with: controller.registrar(forPlugin: "NativePickerRegistrar"))
|
||||
}
|
||||
super.applicationDidFinishLaunching(notification)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,84 +0,0 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
/// Registers a method channel for native file picking with hidden files visible.
|
||||
/// Called from Dart via `ensureInitialized`.
|
||||
class NativePickerRegistrar: NSObject, FlutterPlugin {
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
let channel = FlutterMethodChannel(
|
||||
name: "com.svrnty.native_picker",
|
||||
binaryMessenger: registrar.messenger)
|
||||
let instance = NativePickerRegistrar()
|
||||
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||
}
|
||||
|
||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
switch call.method {
|
||||
case "getDirectoryPath":
|
||||
let args = call.arguments as? [String: Any] ?? [:]
|
||||
let dialog = NSOpenPanel()
|
||||
|
||||
if let initial = args["initialDirectory"] as? String, !initial.isEmpty {
|
||||
dialog.directoryURL = URL(fileURLWithPath: initial)
|
||||
}
|
||||
dialog.showsHiddenFiles = true
|
||||
dialog.canChooseDirectories = true
|
||||
dialog.canChooseFiles = false
|
||||
dialog.allowsMultipleSelection = false
|
||||
dialog.treatsFilePackagesAsDirectories = true
|
||||
dialog.message = "Select a folder containing session files"
|
||||
|
||||
guard let window = NSApp.keyWindow else {
|
||||
result(FlutterError(code: "NO_WINDOW", message: "No key window found", details: nil))
|
||||
return
|
||||
}
|
||||
|
||||
dialog.beginSheetModal(for: window) { response in
|
||||
if response == .OK, let url = dialog.url {
|
||||
result(url.path)
|
||||
} else {
|
||||
result(nil)
|
||||
}
|
||||
}
|
||||
|
||||
case "pickFiles":
|
||||
let args = call.arguments as? [String: Any] ?? [:]
|
||||
let dialog = NSOpenPanel()
|
||||
|
||||
if let initial = args["initialDirectory"] as? String, !initial.isEmpty {
|
||||
dialog.directoryURL = URL(fileURLWithPath: initial)
|
||||
}
|
||||
dialog.showsHiddenFiles = true
|
||||
dialog.canChooseDirectories = false
|
||||
dialog.canChooseFiles = true
|
||||
dialog.allowsMultipleSelection = false
|
||||
|
||||
if let extensions = args["allowedExtensions"] as? [String], !extensions.isEmpty {
|
||||
if #available(macOS 11.0, *) {
|
||||
let contentTypes = extensions.compactMap { UTType(filenameExtension: $0) }
|
||||
dialog.allowedContentTypes = contentTypes
|
||||
} else {
|
||||
dialog.allowedFileTypes = extensions
|
||||
}
|
||||
}
|
||||
dialog.message = "Select a session file"
|
||||
|
||||
guard let window = NSApp.keyWindow else {
|
||||
result(FlutterError(code: "NO_WINDOW", message: "No key window found", details: nil))
|
||||
return
|
||||
}
|
||||
|
||||
dialog.beginSheetModal(for: window) { response in
|
||||
if response == .OK, let url = dialog.url {
|
||||
result([url.path])
|
||||
} else {
|
||||
result(nil)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,9 +2,11 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.get-task-allow</key>
|
||||
<false/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.home-directory.read-only</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user