claude_session_viewer/lib/widgets/navigation/sidebar.dart
Mathias Beaulieu-Duncan aa484f6409 Rename to Claude Session Analysis and prepare for Mac App Store
- 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>
2026-03-12 16:57:20 -04:00

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,
),
),
],
),
),
),
);
}
}