import 'package:flutter/material.dart'; import 'package:yx_only_office_flutter/yx_only_office_flutter.dart'; const _serverUrl = String.fromEnvironment( 'ONLYOFFICE_SERVER_URL', defaultValue: '', ); const _fileUrl = String.fromEnvironment( 'ONLYOFFICE_FILE_URL', defaultValue: '', ); const _jwtSecret = String.fromEnvironment( 'ONLYOFFICE_JWT_SECRET', defaultValue: '', ); void main() { runApp(const OnlyOfficeDemoApp()); } class OnlyOfficeDemoApp extends StatelessWidget { const OnlyOfficeDemoApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'ONLYOFFICE Demo', debugShowCheckedModeBanner: false, theme: ThemeData(colorSchemeSeed: Colors.blue, useMaterial3: true), home: const DemoHomePage(), ); } } class DemoHomePage extends StatefulWidget { const DemoHomePage({super.key}); @override State createState() => _DemoHomePageState(); } class _DemoHomePageState extends State { String _mode = 'view'; // 'view' or 'edit' bool _allowDownload = true; bool _allowPrint = false; bool _lockNavigation = true; String? _lastError; bool _hasUnsavedChanges = false; @override Widget build(BuildContext context) { if (_serverUrl.isEmpty || _fileUrl.isEmpty) { return Scaffold( appBar: AppBar(title: const Text('ONLYOFFICE Demo')), body: const _MissingConfigHint(), ); } return Scaffold( appBar: AppBar( title: Text('ONLYOFFICE Demo - ${_mode == 'edit' ? '编辑' : '查看'}模式'), actions: [ if (_hasUnsavedChanges) Padding( padding: const EdgeInsets.only(right: 8), child: Chip( label: const Text('未保存', style: TextStyle(fontSize: 12)), backgroundColor: Colors.orange.shade100, avatar: const Icon(Icons.edit, size: 16), ), ), IconButton( tooltip: '刷新', onPressed: () => setState(() {}), icon: const Icon(Icons.refresh), ), ], ), body: Column( children: [ _buildControls(), if (_lastError != null) Material( color: Theme.of(context).colorScheme.errorContainer, child: ListTile( leading: const Icon(Icons.error_outline), title: Text( _lastError!, style: TextStyle( color: Theme.of(context).colorScheme.onErrorContainer, ), ), trailing: IconButton( icon: const Icon(Icons.close), onPressed: () => setState(() => _lastError = null), ), ), ), Expanded( child: YxOnlyOfficeViewer.create( serverUrl: _serverUrl, fileUrl: _fileUrl, mode: _mode, allowDownload: _allowDownload, allowPrint: _allowPrint, user: const OnlyOfficeUser( id: 'demo-user-001', name: '演示用户', email: 'demo@example.com', ), customization: const OnlyOfficeCustomization( compactToolbar: true, ), tokenFactory: _jwtSecret.isNotEmpty ? const OnlyOfficeJwtSigner(_jwtSecret) : null, restrictNavigationToInitialPage: _lockNavigation, loadingBuilder: (_) => const ColoredBox( color: Colors.white, child: Center(child: CircularProgressIndicator()), ), onError: _handleError, onAppClose: () => _showSnackBar('用户请求关闭编辑器'), onDownloadAs: (type, url) => _showSnackBar('下载完成: $type -> $url'), onRequestSaveAs: (data) { _showSnackBar('用户请求另存为: $data'); debugPrint('onRequestSaveAs: $data'); }, onRequestInsertImage: (data) { _showSnackBar('用户请求插入图片'); debugPrint('onRequestInsertImage: $data'); // 这里可以打开 Flutter 图片选择器 }, onDocumentStateChange: (data) { final isModified = data == true; setState(() => _hasUnsavedChanges = isModified); debugPrint('文档状态变化: ${isModified ? "已修改" : "未修改"}'); }, onMetaChange: (data) { debugPrint('文档元数据变化: $data'); }, onEvent: (event, data) { // 通用事件处理器 debugPrint('📡 事件: $event, 数据: $data'); }, ), ), ], ), ); } Widget _buildControls() { return Card( margin: const EdgeInsets.all(8), child: Column( children: [ ListTile( title: const Text('文档模式'), trailing: SegmentedButton( segments: const [ ButtonSegment(value: 'view', label: Text('查看'), icon: Icon(Icons.visibility)), ButtonSegment(value: 'edit', label: Text('编辑'), icon: Icon(Icons.edit)), ], selected: {_mode}, onSelectionChanged: (Set newSelection) { setState(() { _mode = newSelection.first; _hasUnsavedChanges = false; }); }, ), ), const Divider(height: 1), SwitchListTile( title: const Text('允许下载'), subtitle: Text(_mode == 'edit' ? '编辑模式下建议开启' : '控制下载按钮显示'), value: _allowDownload, onChanged: (value) => setState(() => _allowDownload = value), ), SwitchListTile.adaptive( title: const Text('允许打印'), subtitle: const Text('控制打印功能'), value: _allowPrint, onChanged: (value) => setState(() => _allowPrint = value), ), SwitchListTile.adaptive( title: const Text('限制导航'), subtitle: const Text('防止 WebView 跳转到其他页面'), value: _lockNavigation, onChanged: (value) => setState(() => _lockNavigation = value), ), ], ), ); } void _handleError(String message) { setState(() => _lastError = message); _showSnackBar('错误: $message'); } void _showSnackBar(String message) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), behavior: SnackBarBehavior.floating, ), ); } } class _MissingConfigHint extends StatelessWidget { const _MissingConfigHint(); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '⚠️ 尚未配置 Document Server', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), const SizedBox(height: 16), const Text( '运行示例前请通过 --dart-define 传入以下环境变量:', style: TextStyle(fontSize: 14), ), const SizedBox(height: 12), _buildConfigItem('ONLYOFFICE_SERVER_URL', 'ONLYOFFICE 服务器地址', required: true), _buildConfigItem('ONLYOFFICE_FILE_URL', '文档文件地址', required: true), _buildConfigItem('ONLYOFFICE_JWT_SECRET', 'JWT 签名密钥(可选)', required: false), const SizedBox(height: 20), const Text( '示例命令:', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey.shade300), ), child: const SelectableText( 'flutter run \\\n' ' --dart-define ONLYOFFICE_SERVER_URL=https://doc.example.com \\\n' ' --dart-define ONLYOFFICE_FILE_URL=https://doc.example.com/demo.docx \\\n' ' --dart-define ONLYOFFICE_JWT_SECRET=your-secret-key', style: TextStyle(fontFamily: 'monospace', fontSize: 12), ), ), const SizedBox(height: 20), const Divider(), const SizedBox(height: 12), const Text( '📚 参考文档:', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), _buildLink('ONLYOFFICE Docs API', 'https://api.onlyoffice.com/docs/docs-api/'), _buildLink('Android 官方项目', 'https://github.com/ONLYOFFICE/documents-app-android'), _buildLink('iOS 官方项目', 'https://github.com/ONLYOFFICE/documents-app-ios'), ], ), ); } Widget _buildConfigItem(String key, String description, {required bool required}) { return Padding( padding: const EdgeInsets.only(bottom: 8), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon( required ? Icons.check_circle : Icons.info_outline, size: 16, color: required ? Colors.red : Colors.blue, ), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( key, style: const TextStyle(fontWeight: FontWeight.bold, fontFamily: 'monospace'), ), Text( description, style: TextStyle(fontSize: 12, color: Colors.grey.shade700), ), ], ), ), ], ), ); } Widget _buildLink(String title, String url) { return Padding( padding: const EdgeInsets.only(bottom: 4), child: Row( children: [ const Icon(Icons.link, size: 14), const SizedBox(width: 4), Expanded( child: SelectableText( '$title: $url', style: const TextStyle(fontSize: 12, color: Colors.blue), ), ), ], ), ); } }