Files
ai-contract-platform/mobile/lib/features/tools/presentation/tools_hub_screen.dart
T
yuqei 3df85fdb04 feat: land phase15 legal docs and commercial ops
Includes legal document generation workflows, prompt governance runtime wiring, legal change impact scans, operation KPI dashboards, invoice files, sales follow-up events, mobile review polish, deployment automation, and updated project docs.
2026-05-21 14:42:44 +08:00

222 lines
6.9 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import '../../../app/theme/brand_assets.dart';
import '../../chat/presentation/review_chat_screen.dart';
import '../../contracts/presentation/contract_template_list_screen.dart';
class ToolsHubScreen extends StatelessWidget {
const ToolsHubScreen({super.key});
Future<void> _openContractsWorkspace(BuildContext context) async {
await Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const ContractTemplateListScreen(),
),
);
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final isCompact = constraints.maxHeight < 820;
return Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
child: _ToolsHeader(
isCompact: isCompact,
onOpenContracts: () => _openContractsWorkspace(context),
),
),
const Expanded(
child: ReviewChatScreen(
key: ValueKey('tools-hub-legal-chat'),
embedded: true,
),
),
],
);
},
);
}
}
class _ToolsHeader extends StatelessWidget {
const _ToolsHeader({
required this.isCompact,
required this.onOpenContracts,
});
final bool isCompact;
final Future<void> Function() onOpenContracts;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: EdgeInsets.all(isCompact ? 16 : 20),
decoration: BoxDecoration(
gradient: BrandAssets.surfaceGradient,
borderRadius: BorderRadius.circular(BrandAssets.radius),
border: Border.all(color: Colors.white.withValues(alpha: 0.86)),
boxShadow: const <BoxShadow>[BrandAssets.cardShadow],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
BrandMark(size: isCompact ? 40 : 48),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'AI工具',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: BrandAssets.ink,
),
),
const SizedBox(height: 4),
Text(
'把法律问答留在当前页持续追问,把合同生成切到独立工作台处理。',
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
const BrandChip(
label: 'Tools',
color: BrandAssets.primaryDark,
),
],
),
const SizedBox(height: 16),
_ToolLaunchTile(
icon: Icons.gavel_outlined,
title: '法律问答',
subtitle: '适合围绕条款风险、谈判策略和依据来源继续追问。',
actionLabel: '当前页继续',
onPressed: null,
emphasis: _ToolLaunchEmphasis.passive,
),
const SizedBox(height: 12),
_ToolLaunchTile(
icon: Icons.edit_note_outlined,
title: '合同生成',
subtitle: '打开独立全屏工作台,避免把模板、历史结果和编辑流塞进底部局部滚动区。',
actionLabel: '打开合同工作台',
onPressed: onOpenContracts,
emphasis: _ToolLaunchEmphasis.primary,
),
],
),
);
}
}
enum _ToolLaunchEmphasis {
primary,
passive,
}
class _ToolLaunchTile extends StatelessWidget {
const _ToolLaunchTile({
required this.icon,
required this.title,
required this.subtitle,
required this.actionLabel,
required this.emphasis,
this.onPressed,
});
final IconData icon;
final String title;
final String subtitle;
final String actionLabel;
final Future<void> Function()? onPressed;
final _ToolLaunchEmphasis emphasis;
@override
Widget build(BuildContext context) {
final isPrimary = emphasis == _ToolLaunchEmphasis.primary;
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isPrimary ? BrandAssets.surfaceInk : BrandAssets.surface,
borderRadius: BorderRadius.circular(BrandAssets.radius),
border: Border.all(
color: isPrimary ? Colors.transparent : BrandAssets.line),
boxShadow:
isPrimary ? const <BoxShadow>[BrandAssets.panelShadow] : null,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: isPrimary
? Colors.white.withValues(alpha: 0.1)
: BrandAssets.accentCyan.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(BrandAssets.radius),
),
child: Icon(
icon,
color:
isPrimary ? BrandAssets.accentLime : BrandAssets.accentCyan,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: isPrimary ? Colors.white : BrandAssets.ink,
),
),
const SizedBox(height: 4),
Text(
subtitle,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: isPrimary
? Colors.white.withValues(alpha: 0.72)
: BrandAssets.muted,
),
),
const SizedBox(height: 12),
if (onPressed == null)
Text(
actionLabel,
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: isPrimary
? BrandAssets.accentLime
: Theme.of(context).colorScheme.primary,
),
)
else
FilledButton.icon(
onPressed: () => unawaited(onPressed!.call()),
icon: const Icon(Icons.open_in_new, size: 18),
label: Text(actionLabel),
),
],
),
),
],
),
);
}
}