- Rename package, bundle ID, and all display strings from "Claude Session Viewer" to "Claude Session Analysis" - Bundle ID: com.svrnty.claudeSessionAnalysis - Add App Store category (developer-tools) to Info.plist - Add PrivacyInfo.xcprivacy privacy manifest (required by Apple) - Bump deployment target from macOS 10.15 to 13.0 - Fix App Sandbox: resolve real home directory so ~/.claude/projects is accessible in sandboxed release builds - Make project name parsing dynamic (no hardcoded username) - Auto-detect Claude Code data directory at ~/.claude/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
189 lines
5.6 KiB
Dart
189 lines
5.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../../theme/app_theme.dart';
|
|
|
|
enum SidebarScreen { home, timeline, agents, toolbelt, tokens }
|
|
|
|
class Sidebar extends StatelessWidget {
|
|
final SidebarScreen selected;
|
|
final ValueChanged<SidebarScreen> onSelect;
|
|
final bool hasSession;
|
|
|
|
const Sidebar({
|
|
super.key,
|
|
required this.selected,
|
|
required this.onSelect,
|
|
required this.hasSession,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
width: 220,
|
|
decoration: const BoxDecoration(
|
|
color: AppColors.surface,
|
|
border: Border(
|
|
right: BorderSide(color: AppColors.surfaceBorder, width: 1),
|
|
),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
const SizedBox(height: 40),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 28,
|
|
height: 28,
|
|
decoration: BoxDecoration(
|
|
color: AppColors.assistant.withAlpha(30),
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
child: const Icon(
|
|
Icons.terminal,
|
|
size: 16,
|
|
color: AppColors.assistant,
|
|
),
|
|
),
|
|
const SizedBox(width: 10),
|
|
const Text(
|
|
'Session Analysis',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.textPrimary,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
_SidebarItem(
|
|
icon: Icons.folder_open,
|
|
label: 'Open Session',
|
|
isSelected: selected == SidebarScreen.home,
|
|
onTap: () => onSelect(SidebarScreen.home),
|
|
),
|
|
if (hasSession) ...[
|
|
const Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
child: Text(
|
|
'SESSION',
|
|
style: TextStyle(
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.textMuted,
|
|
letterSpacing: 1.2,
|
|
),
|
|
),
|
|
),
|
|
_SidebarItem(
|
|
icon: Icons.timeline,
|
|
label: 'Timeline',
|
|
isSelected: selected == SidebarScreen.timeline,
|
|
onTap: () => onSelect(SidebarScreen.timeline),
|
|
),
|
|
_SidebarItem(
|
|
icon: Icons.smart_toy_outlined,
|
|
label: 'Agents',
|
|
isSelected: selected == SidebarScreen.agents,
|
|
onTap: () => onSelect(SidebarScreen.agents),
|
|
),
|
|
_SidebarItem(
|
|
icon: Icons.build_outlined,
|
|
label: 'Toolbelt',
|
|
isSelected: selected == SidebarScreen.toolbelt,
|
|
onTap: () => onSelect(SidebarScreen.toolbelt),
|
|
),
|
|
_SidebarItem(
|
|
icon: Icons.data_usage,
|
|
label: 'Token Usage',
|
|
isSelected: selected == SidebarScreen.tokens,
|
|
onTap: () => onSelect(SidebarScreen.tokens),
|
|
),
|
|
],
|
|
const Spacer(),
|
|
Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Text(
|
|
'Claude Session Analysis v1.0',
|
|
style: TextStyle(
|
|
fontSize: 10,
|
|
color: AppColors.textMuted.withAlpha(128),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SidebarItem extends StatefulWidget {
|
|
final IconData icon;
|
|
final String label;
|
|
final bool isSelected;
|
|
final VoidCallback onTap;
|
|
|
|
const _SidebarItem({
|
|
required this.icon,
|
|
required this.label,
|
|
required this.isSelected,
|
|
required this.onTap,
|
|
});
|
|
|
|
@override
|
|
State<_SidebarItem> createState() => _SidebarItemState();
|
|
}
|
|
|
|
class _SidebarItemState extends State<_SidebarItem> {
|
|
bool _hovered = false;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MouseRegion(
|
|
onEnter: (_) => setState(() => _hovered = true),
|
|
onExit: (_) => setState(() => _hovered = false),
|
|
child: GestureDetector(
|
|
onTap: widget.onTap,
|
|
child: Container(
|
|
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 1),
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: widget.isSelected
|
|
? AppColors.assistant.withAlpha(20)
|
|
: _hovered
|
|
? AppColors.surfaceLight
|
|
: Colors.transparent,
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
widget.icon,
|
|
size: 18,
|
|
color: widget.isSelected
|
|
? AppColors.assistant
|
|
: AppColors.textSecondary,
|
|
),
|
|
const SizedBox(width: 10),
|
|
Text(
|
|
widget.label,
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
fontWeight:
|
|
widget.isSelected ? FontWeight.w600 : FontWeight.w400,
|
|
color: widget.isSelected
|
|
? AppColors.textPrimary
|
|
: AppColors.textSecondary,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|