Files
Forge/lib/src/ui/main_layout.dart
T

346 lines
14 KiB
Dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:macos_ui/macos_ui.dart';
import '../rust/api/packages.dart';
import '../rust/frb_generated.dart';
import '../services/app_badge_controller.dart';
import '../services/app_settings_service.dart';
import 'theme/window_theme_style.dart';
import 'screens/cleaner_screen.dart';
import 'screens/discover_screen.dart';
import 'screens/doctor_screen.dart';
import 'screens/history_screen.dart';
import 'screens/installed_screen.dart';
import 'screens/settings_screen.dart';
import 'screens/starter_packs_screen.dart';
import 'screens/updates_screen.dart';
import 'widgets/keyboard_shortcuts_dialog.dart';
import 'widgets/package_inspector.dart';
import 'widgets/setup_wizard_dialog.dart';
import 'widgets/spotlight_search_dialog.dart';
class MainLayout extends StatefulWidget {
const MainLayout({super.key});
@override
State<MainLayout> createState() => _MainLayoutState();
}
class _MainLayoutState extends State<MainLayout> {
int _pageIndex = 0;
@override
void initState() {
super.initState();
_performInitialBadgeScan();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!AppSettingsService.instance.hasCompletedSetupWizard && mounted) {
SetupWizardDialog.show(context);
}
});
}
Future<void> _performInitialBadgeScan() async {
try {
final updates = await RustBridgeApi.checkUpdates();
AppBadgeController.instance.setUpdatesCount(updates.length);
} catch (_) {}
try {
final scan = await RustBridgeApi.scanCleaner();
AppBadgeController.instance.setCleanerBytes(scan.totalReclaimableBytes);
} catch (_) {}
}
void _onInspectPackage(String name, BackendType backend) {
PackageInspectorSheet.show(
context,
packageName: name,
backend: backend,
);
}
void _openSpotlight() {
SpotlightSearchDialog.show(
context,
onSelectPackage: (name, backend) {
_onInspectPackage(name, backend);
},
);
}
@override
Widget build(BuildContext context) {
final macosTheme = MacosTheme.of(context);
return CallbackShortcuts(
bindings: {
const SingleActivator(LogicalKeyboardKey.keyK, control: true): _openSpotlight,
const SingleActivator(LogicalKeyboardKey.keyK, meta: true): _openSpotlight,
const SingleActivator(LogicalKeyboardKey.slash, control: true): () => KeyboardShortcutsDialog.show(context),
const SingleActivator(LogicalKeyboardKey.f1): () => KeyboardShortcutsDialog.show(context),
const SingleActivator(LogicalKeyboardKey.keyR, control: true): _performInitialBadgeScan,
const SingleActivator(LogicalKeyboardKey.f5): _performInitialBadgeScan,
const SingleActivator(LogicalKeyboardKey.digit1, control: true): () => setState(() => _pageIndex = 0),
const SingleActivator(LogicalKeyboardKey.digit2, control: true): () => setState(() => _pageIndex = 1),
const SingleActivator(LogicalKeyboardKey.digit3, control: true): () => setState(() => _pageIndex = 2),
const SingleActivator(LogicalKeyboardKey.digit4, control: true): () => setState(() => _pageIndex = 3),
const SingleActivator(LogicalKeyboardKey.digit5, control: true): () => setState(() => _pageIndex = 4),
const SingleActivator(LogicalKeyboardKey.digit6, control: true): () => setState(() => _pageIndex = 5),
const SingleActivator(LogicalKeyboardKey.digit7, control: true): () => setState(() => _pageIndex = 6),
},
child: Focus(
autofocus: true,
child: MacosWindow(
sidebar: Sidebar(
topOffset: 0.0, // Убираем пустой отступ macOS traffic lights на Linux
minWidth: 200,
startWidth: 220,
maxWidth: 260,
top: Padding(
padding: const EdgeInsets.fromLTRB(16, 14, 16, 10),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: macosTheme.primaryColor,
borderRadius: BorderRadius.circular(
AppSettingsService.instance.effectiveWindowThemeStyle == WindowThemeStyle.kde ? 4 : 8,
),
boxShadow: [
BoxShadow(
color: macosTheme.primaryColor.withValues(alpha: 0.35),
blurRadius: 6,
offset: const Offset(0, 2),
),
],
),
child: Center(
child: MacosIcon(
AppSettingsService.instance.effectiveWindowThemeStyle == WindowThemeStyle.kde
? CupertinoIcons.gear_alt_fill
: AppSettingsService.instance.effectiveWindowThemeStyle == WindowThemeStyle.gnome
? CupertinoIcons.app_badge_fill
: CupertinoIcons.cube_box_fill,
color: CupertinoColors.white,
size: 17,
),
),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'Forge',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
letterSpacing: -0.2,
),
overflow: TextOverflow.ellipsis,
),
Text(
AppSettingsService.instance.effectiveWindowThemeStyle == WindowThemeStyle.kde
? 'KDE Breeze Edition'
: AppSettingsService.instance.effectiveWindowThemeStyle == WindowThemeStyle.gnome
? 'GNOME Adwaita'
: 'macOS Cupertino',
style: const TextStyle(
fontSize: 10.5,
color: CupertinoColors.systemGrey,
),
overflow: TextOverflow.ellipsis,
),
],
),
),
MacosTooltip(
message: 'Горячие клавиши (F1 / Ctrl+/)',
child: MacosIconButton(
icon: const MacosIcon(CupertinoIcons.keyboard, size: 14),
onPressed: () => KeyboardShortcutsDialog.show(context),
),
),
],
),
const SizedBox(height: 12),
// Spotlight Search Trigger Button
CupertinoButton(
padding: EdgeInsets.zero,
minimumSize: Size.zero,
onPressed: _openSpotlight,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6.5),
decoration: BoxDecoration(
color: CupertinoColors.white.withValues(alpha: 0.05),
borderRadius: BorderRadius.circular(7),
border: Border.all(
color: CupertinoColors.white.withValues(alpha: 0.07),
),
),
child: const Row(
children: [
MacosIcon(
CupertinoIcons.search,
size: 13,
color: CupertinoColors.systemGrey,
),
SizedBox(width: 8),
Expanded(
child: Text(
'Поиск пакетов...',
style: TextStyle(
fontSize: 12,
color: CupertinoColors.systemGrey,
),
),
),
Text(
'⌘K',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: CupertinoColors.systemGrey,
),
),
],
),
),
),
],
),
),
builder: (context, scrollController) {
return ListenableBuilder(
listenable: AppBadgeController.instance,
builder: (context, _) {
final badge = AppBadgeController.instance;
return SidebarItems(
currentIndex: _pageIndex,
onChanged: (index) {
setState(() {
_pageIndex = index;
});
},
scrollController: scrollController,
itemSize: SidebarItemSize.large,
items: [
const SidebarItem(
label: Text('УПРАВЛЕНИЕ'),
section: true,
),
SidebarItem(
leading: const MacosIcon(CupertinoIcons.arrow_down_circle_fill),
label: const Text('Обновления'),
trailing: badge.updatesCount > 0
? Container(
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 1.5),
decoration: BoxDecoration(
color: const Color(0xFF0A84FF),
borderRadius: BorderRadius.circular(10),
),
child: Text(
'${badge.updatesCount}',
style: const TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: CupertinoColors.white,
),
),
)
: null,
),
const SidebarItem(
leading: MacosIcon(CupertinoIcons.square_stack_3d_up_fill),
label: Text('Установленные'),
),
const SidebarItem(
leading: MacosIcon(CupertinoIcons.compass_fill),
label: Text('Каталог'),
),
const SidebarItem(
leading: MacosIcon(CupertinoIcons.square_grid_2x2_fill),
label: Text('Наборы ПО'),
),
const SidebarItem(
label: Text('СИСТЕМА И ОПТИМИЗАЦИЯ'),
section: true,
),
SidebarItem(
leading: const MacosIcon(CupertinoIcons.sparkles),
label: const Text('Очистка'),
trailing: badge.cleanerBytes > 0
? Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1.5),
decoration: BoxDecoration(
color: CupertinoColors.systemOrange.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(10),
),
child: Text(
AppBadgeController.formatBytes(badge.cleanerBytes),
style: const TextStyle(
fontSize: 9.5,
fontWeight: FontWeight.bold,
color: CupertinoColors.systemOrange,
),
),
)
: null,
),
const SidebarItem(
leading: MacosIcon(CupertinoIcons.bandage_fill),
label: Text('Диагностика'),
),
const SidebarItem(
leading: MacosIcon(CupertinoIcons.clock_fill),
label: Text('История'),
),
const SidebarItem(
leading: MacosIcon(CupertinoIcons.gear_alt_fill),
label: Text('Настройки'),
),
],
);
},
);
},
bottom: MacosListTile(
leading: const MacosIcon(
CupertinoIcons.command,
color: CupertinoColors.systemGrey,
),
title: const Text(
'Arch / Flatpak / AUR',
style: TextStyle(fontSize: 12, color: CupertinoColors.systemGrey),
),
),
),
child: IndexedStack(
index: _pageIndex.clamp(0, 7),
children: [
UpdatesScreen(onInspectPackage: _onInspectPackage),
InstalledScreen(onInspectPackage: _onInspectPackage),
DiscoverScreen(onInspectPackage: _onInspectPackage),
const StarterPacksScreen(),
const CleanerScreen(),
const DoctorScreen(),
const HistoryScreen(),
const SettingsScreen(),
],
),
),
),
);
}
}