Skip to main content

ecp_file_view 3.1.1 copy "ecp_file_view: ^3.1.1" to clipboard
ecp_file_view: ^3.1.1 copied to clipboard

A file viewer plugin for Flutter, support local file and network link of Android, iOS.

example/lib/main.dart

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";
  }
}
0
likes
130
points
103
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A file viewer plugin for Flutter, support local file and network link of Android, iOS.

Repository

License

Apache-2.0 (license)

Dependencies

dict_flutter_extension, dio, ecp_net, ecp_theme, flutter, flutter_smart_dialog, path_provider, permission_handler

More

Packages that depend on ecp_file_view

Packages that implement ecp_file_view