ecp_file_view 3.1.1
ecp_file_view: ^3.1.1 copied to clipboard
A file viewer plugin for Flutter, support local file and network link of Android, iOS.
import 'dart:convert';
import 'dart:io';
import 'package:ecp_file_view/ecp_file_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:path_provider/path_provider.dart';
import 'page_local_file_viewer.dart';
void main() async {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
builder: FlutterSmartDialog.init(),
localizationsDelegates: const <LocalizationsDelegate<dynamic>>[
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
ViewerLocalizationsDelegate.delegate,
],
supportedLocales: const [Locale('en', 'US'), Locale('zh', 'CN')],
debugShowCheckedModeBanner: false,
theme: ThemeData(colorSchemeSeed: const Color(0xFF3B82F6), useMaterial3: true),
home: const HomePage(),
);
}
}
/// 示例文件条目
class DemoFile {
final String? asset; // assets 内文件名;网络链接时为 null
final String? url; // 网络下载地址;本地文件时为 null
final String title; // 展示名
DemoFile.asset(this.asset, {String? title})
: url = null,
title = title ?? asset!;
const DemoFile.network(this.url, {required this.title})
: asset = null;
bool get isNetwork => url != null;
String get type => isNetwork
? 'txt'
: asset!.substring(asset!.lastIndexOf('.') + 1).toLowerCase();
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final List<DemoFile> files = <DemoFile>[
DemoFile.asset('docx.docx'),
DemoFile.asset('doc.doc'),
DemoFile.asset('xlsx.xlsx'),
DemoFile.asset('xls.xls'),
DemoFile.asset('pptx.pptx'),
DemoFile.asset('ppt.ppt'),
DemoFile.asset('pdf.pdf'),
DemoFile.asset('txt.txt'),
DemoFile.network(
'http://www.xqb5.cc/api/txt_down.php?articleid=75255&articlename=%E4%BF%AE%E4%BB%99%EF%BC%8C%E4%BB%8E%E5%87%A1%E4%BA%BA%E4%BF%AE%E4%BB%99%E5%BC%80%E5%A7%8B',
title: '仙逆.txt'),
];
bool _loading = false;
IconData _typeIcon(String type) => switch (type) {
'pdf' => Icons.picture_as_pdf_outlined,
'doc' || 'docx' => Icons.description_outlined,
'xls' || 'xlsx' => Icons.table_chart_outlined,
'ppt' || 'pptx' => Icons.slideshow_outlined,
_ => Icons.insert_drive_file_outlined,
};
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('文件预览示例')),
body: _buildBodyWidget(),
);
}
Widget _buildBodyWidget() {
return Stack(
children: [
ListView.separated(
padding: const EdgeInsets.symmetric(vertical: 8),
itemCount: files.length,
separatorBuilder: (_, __) => const Divider(height: 1, indent: 16, endIndent: 16),
itemBuilder: (context, index) {
final DemoFile file = files[index];
return ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).colorScheme.primaryContainer,
child: Icon(
_typeIcon(file.type),
color: Theme.of(context).colorScheme.primary,
),
),
title: Text(file.title, maxLines: 1, overflow: TextOverflow.ellipsis),
subtitle: Text(
file.isNetwork
? '网络文件 · ${file.type.toUpperCase()}'
: '本地演示文件 · ${file.type.toUpperCase()}',
style: Theme.of(context).textTheme.bodySmall,
),
trailing: const Icon(Icons.chevron_right),
onTap: () => file.isNetwork ? onNetworkTap(file) : onLocalTap(file),
);
},
),
if (_loading)
const Positioned.fill(
child: ColoredBox(
color: Color(0x66000000),
child: Center(child: CircularProgressIndicator()),
),
),
],
);
}
/// 本地文件:iOS 进内联预览页;Android 无内联内核,调系统应用打开
Future<void> onLocalTap(DemoFile file) async {
final String assetPath = 'assets/files/${file.asset}';
final String filePath = await setFilePath(file.type, assetPath);
setState(() => _loading = true);
try {
await asset2Local(filePath, assetPath);
} catch (e) {
debugPrint('释放演示文件失败:$e');
} finally {
if (mounted) setState(() => _loading = false);
}
if (!mounted) return;
if (Platform.isIOS) {
Navigator.of(context).push(MaterialPageRoute(builder: (ctx) {
return LocalFileViewerPage(filePath: filePath, title: file.title);
}));
} else {
// Android:LocalFileViewer 无内联内核(X5 已废弃),
// 使用插件原生 openFile(Intent.ACTION_VIEW + FileProvider)调系统应用;
// 失败默认自动弹人性化 toast(showToast: true,SmartDialog),不抛异常,无需 try-catch
await OpenFile.open(filePath);
}
}
Future<void> onNetworkTap(DemoFile file) async {
Navigator.of(context).push(MaterialPageRoute(builder: (ctx) {
return NetworkFileViewer(
url: file.url!, name: '${file.title}.${file.type}');
}));
}
/// 将 assets 演示文件释放到临时目录,返回写入的文件路径
Future<void> asset2Local(String filePath, String assetPath) async {
await File(filePath).parent.create(recursive: true);
final ByteData bd = await rootBundle.load(assetPath);
await File(filePath).writeAsBytes(bd.buffer.asUint8List(), flush: true);
}
Future<String> setFilePath(String type, String assetPath) async {
final directory = await getTemporaryDirectory();
return "${directory.path}/fileview/${base64.encode(utf8.encode(assetPath))}.$type";
}
}