yx_only_office_flutter/example/lib/main.dart

322 lines
11 KiB
Dart

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<DemoHomePage> createState() => _DemoHomePageState();
}
class _DemoHomePageState extends State<DemoHomePage> {
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<String>(
segments: const [
ButtonSegment(value: 'view', label: Text('查看'), icon: Icon(Icons.visibility)),
ButtonSegment(value: 'edit', label: Text('编辑'), icon: Icon(Icons.edit)),
],
selected: {_mode},
onSelectionChanged: (Set<String> 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),
),
),
],
),
);
}
}