322 lines
11 KiB
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),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|