จากเนื้อหาจากสองบทความที่แล้ว ที่เราดูเรื่องการบันทึกไฟล์เสียง
การบันทักไฟล์ภาพและไฟล์วิดีโอ เหล่านี้ล้วนเป็นการจัดการเกี่ยว
กับไฟล์ในแอปของเรา เนื้อหาตอนต่อไปนี้เราจะมาเพิ่มความสามารถ
ให้แอปของเราสามารถส่งไฟล์ต่างๆ ที่เราได้สร้างขึ้นไปใช้ยังภายนอก
แอปได้ นั่นคือ เราจะใช้ plugin ที่ชื่อว่า share_plus ในการส่งไฟล์
หรือแชร์ไฟล์ไปใช้งานนอกแอปหรือไปใช้งานยังแอปอื่นหรือส่งให้ผู้ใช้คน
อื่นๆ เหล่านี้เป็นต้น
เนื้อหานี้ต่อยอดจากตอนที่แล้ว มีโค้ดท้ายบทความ
การบันทึกรูปภาพ Image หรือวิดีโอ Video ใน Flutter http://niik.in/1118
ติดตั้ง package ที่จำเป็นเพิ่มเติม ตามรายการด้านล่าง
แพ็กเก็จที่จำเป็นต้องติดตั้งเพิ่มเติม สำหรับการทำงานมีดังนี้
share_plus: ^10.0.2
การใช้งาน Share Plus เพื่อแชร์ไฟล์
สำหรับการใช้งาน Share Plus นั้น มีรูปแบบและวิธีการใช้งานที่ไม่ยุ่งยากซับซ้อน ใช้คำสั่ง
เพียงไม่กี่บรรทัดก็สามารถทำงานได้ รองรับการแชร์เฉพาะข้อความ การแชร์ไฟล์หนึ่งไฟล์ หรือ
หลายๆ ไฟล์พร้อมกัน
ตัวอย่างการแชร์ข้อความ
ใช้รูปแบบดังนี้
ElevatedButton( onPressed: () async { final result = await Share.share('check out my website https://www.ninenik.com'); if (result.status == ShareResultStatus.success) { print('xdebug: Thank you for sharing my website!'); } }, child: Text('Share Text'), ),
กรณีให้รองรับหัวข้อ สำหรับส่งผ่านอีเมล
สามารถเพิ่มส่วนของ Subject เข้าไปดังนี้
ElevatedButton( onPressed: () async { final result = await Share.share( 'check out my website https://www.ninenik.com', subject: 'Look what I made!'); if (result.status == ShareResultStatus.success) { print('xdebug: Thank you for sharing my website!'); } }, child: Text('Share Text'), ),
ตัวอย่างการแชร์ไปยังอีเมล
การแชร์ไฟล์ต่างๆ
ElevatedButton( onPressed: () async { final result = await Share.shareXFiles( [XFile('${directory.path}/image.jpg')], text: 'Great picture'); if (result.status == ShareResultStatus.success) { print('xdebug: Thank you for sharing the picture!'); } }, child: Text('Share File'), ),
กรณีแชร์พร้อมกันหลายไฟล์ เราก็ส่งข้อมูลไฟล์เป็น List หรือ array เข้าไปตัวอย่าง
final result = await Share.shareXFiles( [ XFile('${directory.path}/image.jpg'), XFile('${directory.path}/image2.jpg'), XFile('${directory.path}/video1.mp4') XFile('${directory.path}/video1.mp4') ], text: 'Great File');
หรือกรณีเรามีการกับ path ของไฟล์ที่เลือกเป็น List ไว้อยู่แล้ว สามารถวนลูป รวมเป็น List
รายการ XFile เพื่อใช้แชร์ได้ ตัวอย่างเช่น
// สรัางตัวแปรเก็บรายการไฟล์ที่จะแขร์ List<XFile> _fileShare = <XFile>[]; // วนลูปจากรายการไฟล์ที่เลือก _selectedItems // _folders![index]! สมมติเป็นตัวอ้างอิงไฟล์ _selectedItems.forEach((index) async { _fileShare.add(XFile(_folders![index]!.path)); }); if(_fileShare.isNotEmpty){ final result = await Share.shareXFiles(_fileShare); if (result.status == ShareResultStatus.success) { print('xdebug: Thank you for sharing!'); } }
สถานะการแชร์ ShareResultStatus
ประกอบด้วยสถานะดังนี้:
- success: การแชร์สำเร็จ ผู้ใช้ได้แชร์ข้อมูลเรียบร้อยแล้ว
- dismissed: การแชร์ถูกยกเลิก (ผู้ใช้เปิดหน้าแชร์แต่ไม่ได้แชร์ข้อมูล และปิดออกไป)
- unavailable: ฟีเจอร์การแชร์ไม่สามารถใช้งานได้บนอุปกรณ์นี้
เราสามารถใช้งาน ShareResultStatus เพื่อจัดการสถานะของการแชร์ได้ดังนี้
final result = await Share.share( 'Check out my website https://www.ninenik.com', subject: 'Look what I made!', ); if (result.status == ShareResultStatus.success) { print('xdebug: Sharing successful'); } else if (result.status == ShareResultStatus.dismissed) { print('xdebug: Sharing dismissed'); } else if (result.status == ShareResultStatus.unavailable) { print('xdebug: Sharing unavailable on this device'); }
การแชร์จากข้อมูลไฟล์ Share Data
เราสามารถสร้างไฟล์ที่แชร์ได้จากข้อมูลที่สร้างขึ้นแบบไดนามิกโดยไม่จำเป็นต้องมีไฟล์จริงอยู่ในระบบ
ไฟล์ก่อน โดยใช้คลาส XFile และเมธอด fromData จากแพ็กเกจ share_plus
ตัวอย่างแชร์จากไฟล์ใน assets
final data = await rootBundle.load('assets/flutter_logo.png'); final buffer = data.buffer; final shareResult = await Share.shareXFiles( [ XFile.fromData( buffer.asUint8List(data.offsetInBytes, data.lengthInBytes), name: 'flutter_logo.png', mimeType: 'image/png', ), ], sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, );
ตัวอย่างแชร์ข้อมูลไฟล์ โดยสร้างเป็น pdf เพื่อส่งออก
// สร้างไฟล์จากข้อมูลที่เป็นไบต์ final XFile xFile = XFile.fromData( fileData, mimeType: 'application/pdf', // ระบุประเภทของไฟล์ ); // แชร์ไฟล์ final shareResult = await Share.shareXFiles([xFile], text: 'Check out this file!' fileNameOverrides: [fileName], );
ตัวอย่างการแชร์จากข้อมูลที่เป็นข้อความ
final data = utf8.encode(text); final shareResult = await Share.shareXFiles( [ XFile.fromData( data, mimeType: 'text/plain', ), ], fileNameOverrides: [fileName], );
เราเพิ่มตัวอย่างการประยุกต์ใช้งานเข้าไปในไฟล์ explorer.dart โดยเมื่อกดค้างเพื่อเลือกไฟล์
จะมีไอคอนแชร์ขึ้นมา เราสามารถได้หลายไฟล์หรือไฟล์เดียวก็ได้เพื่อแชร์ไฟล์จากแอปของเรา
โค้ดส่วนที่เพิ่มเข้าไป เมื่อมีรายการเลือกไฟล์ และกดปุ่มแชร์ไฟล์
if (_selectedItems.isNotEmpty) IconButton( onPressed: () async { print("xdebug: share"); try { List<XFile> _fileShare = <XFile>[]; _selectedItems.forEach((index) async { _fileShare.add(XFile(_folders![index]!.path)); }); if(_fileShare.isNotEmpty){ final result = await Share.shareXFiles(_fileShare); if (result.status == ShareResultStatus.success) { print('xdebug: Thank you for sharing!'); } } } catch (e) { print(e); } }, icon: FaIcon(FontAwesomeIcons.share), ),
ไฟล์ explorer.dart
import 'dart:io'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:path_provider/path_provider.dart'; import 'package:file_picker/file_picker.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:share_plus/share_plus.dart'; import 'audioplayer.dart'; import 'fullscreenvideo.dart'; import 'viewphotoscreen.dart'; class Explorer extends StatefulWidget { static const routeName = '/explorer'; const Explorer({Key? key}) : super(key: key); @override State<StatefulWidget> createState() { return _ExplorerState(); } } class _ExplorerState extends State<Explorer> { List<FileSystemEntity?>? _folders; String _currentPath = ''; // เก็บ path ปัจจุบัน Directory? _currentFolder; // เก็บ โฟลเดอร์ที่กำลังใช้งาน // ตัวแปรเก็บ index รายการที่เลือก List<int> _selectedItems = []; @override void initState() { // TODO: implement initState super.initState(); _loadFolder(); } void _loadFolder() async { // ข้อมูลเกี่ยวกับโฟลเดอร์ Directory ต่างๆ // final tempDirectory = await getTemporaryDirectory(); // final appSupportDirectory = await getApplicationSupportDirectory(); final appDocumentsDirectory = await getApplicationDocumentsDirectory(); // final externalDocumentsDirectory = await getExternalStorageDirectory(); // final externalStorageDirectories = // await getExternalStorageDirectories(type: StorageDirectory.music); // final externalCacheDirectories = await getExternalCacheDirectories(); /* print(tempDirectory); print(appSupportDirectory); print(appDocumentsDirectory); print(externalDocumentsDirectory); print(externalCacheDirectories); print(externalStorageDirectories); */ // เมื่อโหลดขึ้นมา เาจะเปิดโฟลเดอร์ของ package เป้นโฟลเดอร์หลัก _currentFolder = appDocumentsDirectory.parent; _currentPath = appDocumentsDirectory.parent.path; final myDir = Directory(_currentPath); setState(() { _folders = myDir.listSync(recursive: false, followLinks: false); }); } // เปิดโฟลเดอร์ และแสดงรายการในโฟลเดอร์ void _setPath(dir) async { _currentFolder = dir; _currentPath = dir.path; final myDir = Directory(_currentPath); try { setState(() { _folders = myDir.listSync(recursive: false, followLinks: false); }); } catch (e) { print(e); } _selectedItems.clear(); // ล้างค่าการเลือกทั้งหมด } // คำสังลบไฟล์ void _deleteFile(path) async { final deletefile = File(path); // กำหนด file object final isExits = await deletefile.exists(); // เช็คว่ามีไฟล์หรือไม่ if (isExits) { // ถ้ามีไฟล์ try { await deletefile.delete(); } catch (e) { print(e); } } // โหลดข้อมูลใหม่อีกครั้ง setState(() { _setPath(_currentFolder!); }); } // คำสั่งลบโฟลเดอร์ void _deleteFolder(path) async { final deleteFolder = Directory(path); // สร้าง directory object var isExits = await deleteFolder.exists(); // เช็คว่ามีแล้วหรือไม่ if (isExits) { // ถ้ามีโฟลเดอร์ try { await deleteFolder.delete(recursive: true); } catch (e) { print(e); } } // โหลดข้อมูลใหม่อีกครั้ง setState(() { _setPath(_currentFolder!); }); } // ลบข้อมูลที่เลือกทั้งหมด void _deleteAll() async { bool _confirm; // สร้างตัวแปรรับค่า ยืนยันการลบ _confirm = await showDialog( context: context, builder: (context) { return AlertDialog( title: const Text("Confirm"), content: const Text("Are you sure you wish to delete selected item?"), actions: <Widget>[ ElevatedButton( onPressed: () => Navigator.of(context).pop(true), child: const Text("DELETE")), ElevatedButton( onPressed: () => Navigator.of(context).pop(false), child: const Text("CANCEL"), ), ], ); }, ); if (_confirm) { // ถ้ายืนยันการลบ เป็น true try { // วนลูป index แล้วอ้างอึงข้อมูลไฟล์ จากนั้นใช้คำสั่ง delete() แบบรองรับการลบข้อมูลด้านในถ้ามี // ในกรณีเป็นโฟลเดอร์ _selectedItems.forEach((index) async { await _folders![index]!.delete(recursive: true); }); } catch (e) { print(e); } // โหลดข้อมูลใหม่อีกครั้ง setState(() { _setPath(_currentFolder!); }); } } // จำลองสร้างไฟล์ใหม่ void _newFile() async { String newFile = "${_currentFolder!.path}/myfile.txt"; final myfile = File(newFile); // กำหนด file object final isExits = await myfile.exists(); // เช็คว่ามีไฟล์หรือไม่ if (!isExits) { // ถ้ายังไม่มีไฟล์ try { // สร้างไฟล์ text var file = await myfile.writeAsString('Hello World'); print(file); } catch (e) { print(e); } } // โหลดข้อมูลใหม่อีกครั้ง setState(() { _setPath(_currentFolder!); }); } // คำสั่งจำลองการสร้างโฟลเดอร์ void _newFolder() async { String newFolder = "${_currentFolder!.path}/mydir"; final myDir = Directory(newFolder); // สร้าง directory object var isExits = await myDir.exists(); // เช็คว่ามีแล้วหรือไม่ if (!isExits) { // ถ้ายังไม่มีสร้างโฟลเดอร์ขึ้นมาใหม่ try { var directory = await Directory(newFolder).create(recursive: true); print(directory); } catch (e) { print(e); } } // โหลดข้อมูลใหม่อีกครั้ง setState(() { _setPath(_currentFolder!); }); } // คำสั่ง import ไฟล์ผ่าน file_picker void _importFile() async { try { if (await Permission.storage.request().isGranted) { // FilePickerResult? result = await FilePicker.platform.pickFiles(); FilePickerResult? result = await FilePicker.platform.pickFiles(allowMultiple: true); if (result != null) { // File _file = File(result.files.single.path!); // List<File> _file = result.paths.map((path) => File(path!)).toList(); result.files.forEach((file) async { // วนลุป copy ไฟล์ไปยังโฟลเดอร์ที่ใช้งาน // กำหนด path ของไฟล์ใหม่ แล้วทำการ copy จาก cache ไปไว้ใน app String newPath = "${_currentFolder!.path}/${file.name}"; var cachefile = File(file.path!); // กำหนด file object await cachefile.copy(newPath); // var newFile = await cachefile.copy(newPath); // ข้อมูลไฟล์ print(file.name); print(file.bytes); print(file.size); print(file.extension); print(file.path); // ตัวสุดท้ายทำงานเสร็จ if (file == result.files.last) { // โหลดข้อมูลใหม่อีกครั้ง setState(() { _setPath(_currentFolder!); }); } }); } else { print("User canceled the picker"); } } else { print("Storage permission is denied."); } } catch (e) { print(e); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Explorer'), actions: <Widget>[ IconButton( onPressed: _importFile, // icon: FaIcon(FontAwesomeIcons.fileImport), ), IconButton( onPressed: _newFolder, // สร้างโฟลเดอร์ใหม่ icon: FaIcon(FontAwesomeIcons.folderPlus), ), IconButton( onPressed: _newFile, // สร้างไฟล์ใหม่ icon: FaIcon(FontAwesomeIcons.fileLines), ), if (_selectedItems.isNotEmpty) IconButton( onPressed: () async { print("xdebug: share"); try { List<XFile> _fileShare = <XFile>[]; _selectedItems.forEach((index) async { _fileShare.add(XFile(_folders![index]!.path)); }); if(_fileShare.isNotEmpty){ final result = await Share.shareXFiles(_fileShare); if (result.status == ShareResultStatus.success) { print('xdebug: Thank you for sharing!'); } } } catch (e) { print(e); } }, icon: FaIcon(FontAwesomeIcons.share), ), if (_selectedItems.isNotEmpty) IconButton( onPressed: _deleteAll, icon: FaIcon(FontAwesomeIcons.trashCan), ), ], ), body: Column( mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[ ListTile( leading: FaIcon(FontAwesomeIcons.angleLeft), title: Text( '${_currentPath.replaceAll('/data/user/0/com.example.demo_app', '/')}'), onTap: () { _setPath(_currentFolder!.parent); }), Expanded( child: _folders != null // เมื่อไม่ใช่ค่า null ? ListView.separated( // กรณีมีรายการ แสดงปกติ itemCount: _folders == null ? 0 : _folders!.length, itemBuilder: (context, index) { var isFolder = _folders![index] is Directory ? true : false; // เช็คว่าเป็นโฟลเดอร์ var isFile = _folders![index] is File ? true : false; // เช็คว่าเป็นไฟล์ if (_folders![index] != null) { // เอาเฉพาะชื่อหลัง / ตัวสุดท้าย String fileName = _folders![index]!.path.split('/').last; return Dismissible( direction: DismissDirection.horizontal, key: UniqueKey(), // dismissThresholds: const { DismissDirection.endToStart:1.0, DismissDirection.startToEnd:1.0}, confirmDismiss: (direction) async { return await showDialog( context: context, builder: (context) { return AlertDialog( title: const Text("Confirm"), content: const Text( "Are you sure you wish to delete this item?"), actions: <Widget>[ ElevatedButton( onPressed: () => Navigator.of(context).pop(true), child: const Text("DELETE")), ElevatedButton( onPressed: () => Navigator.of(context).pop(false), child: const Text("CANCEL"), ), ], ); }, ); }, onDismissed: (direction) { // ปัดไปทางขวา - บนลงล่าง if (direction == DismissDirection.startToEnd) {} // ปัดไปซ้าย - ล่างขึ้นบน if (direction == DismissDirection.endToStart) { try { setState(() { if (isFile) { // ถ้าเป็นไฟล์ ส่ง path ไฟล์ไปลบ _deleteFile(_folders![index]!.path); } if (isFolder) { // ถ้าเป็นโฟลเดอร์ส่ง path โฟลเดอร์ไปลบ _deleteFolder(_folders![index]!.path); } // ต้องลบข้อมูลก่อน แล้วค่อยลบรายการในลิส _folders!.removeAt(index); }); } catch (e) { print(e); } } ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('$index dismissed'))); }, background: Container( color: Colors.green, ), secondaryBackground: Container( color: Colors.red, child: Align( alignment: Alignment.centerRight, child: Padding( padding: EdgeInsets.symmetric( horizontal: 10.0), child: FaIcon(FontAwesomeIcons.trashAlt), ))), child: ListTile( selected: _selectedItems.contains(index) ? true : false, leading: isFolder ? FaIcon(FontAwesomeIcons.solidFolder) : FaIcon(FontAwesomeIcons.file), trailing: Visibility( visible: _selectedItems.contains(index) ? true : false, child: FaIcon(FontAwesomeIcons.checkCircle), ), title: Text('${fileName}'), onLongPress: () { if (!_selectedItems.contains(index)) { setState(() { _selectedItems.add(index); }); } }, onTap: (isFolder == true) ? () { // กรณีเป้นโฟลเดอร์ if (_selectedItems.contains(index)) { setState(() { _selectedItems.removeWhere( (val) => val == index); }); } else { if (_selectedItems.isNotEmpty) { setState(() { _selectedItems.add(index); }); } else { _setPath(_folders![ index]!); // ถ้ากด ให้ทำคำสั่งเปิดโฟลเดอร์ } } } : () { if (_selectedItems.contains(index)) { setState(() { _selectedItems.removeWhere( (val) => val == index); }); } else { if (_selectedItems.isNotEmpty) { setState(() { _selectedItems.add(index); }); } else { List<String> audioExtensions = [ '.mp3', '.wav', '.flac', '.aac', '.ogg' ]; List<String> videoExtensions = [ 'mp4' ]; List<String> photoExtensions = [ 'png', 'jpeg', 'jpg' ]; if (audioExtensions.any((ext) => fileName .toLowerCase() .endsWith(ext))) { /* if (fileName .toLowerCase() .endsWith('.wav')) { */ print( 'debug: The file is an audio file.'); print( "debug: path ${_currentPath}"); String fullPathFile = '$_currentPath/$fileName'; Navigator.push( context, MaterialPageRoute( builder: (context) => AudioPlayers( fileName: fullPathFile, ), ), ); } else if (videoExtensions.any( (ext) => fileName .toLowerCase() .endsWith(ext))) { print( 'debug: The file is a video file.'); print( "debug: path ${_currentPath}"); String fullPathFile = '$_currentPath/$fileName'; Navigator.push( context, MaterialPageRoute( builder: (context) => FullScreenVideo( fullPathFile), ), ); } else if (photoExtensions.any( (ext) => fileName .toLowerCase() .endsWith(ext))) { print( 'debug: The file is an image file.'); print( "debug: path ${_currentPath}"); String fullPathFile = '$_currentPath/$fileName'; Navigator.push( context, MaterialPageRoute( builder: (context) => ViewPhotoScreen( photos: fullPathFile,), ), ); } else { print( 'debug: The file is not an image audio or video file.'); } } } }, // กรณีเป็นไฟล์ )); } else { return Container(); } }, separatorBuilder: (BuildContext context, int index) => const Divider( height: 1, ), ) : const Center(child: Text('No items')), // กรณีไม่มีรายการ ), ], ), ); } }
ผลลัพธ์ที่ได้
จากตัวอย่างผลลัพธ์เราสามารถเลือกไฟล์ที่จะแชร์พร้อมกันได้หลายไฟล์ อย่างไรก็ดี เพื่อให้เห็นภาพ
หรือเป็นแนวทางการประยุกต์ เราจะสร้างหน้าสำหรับแสดงข้อมูลไฟล์ในโฟลเดอร์ app_flutter โดย
ที่ถ้าเป็นไฟล์ภาพ เสียง และ วิดีโอ ก็จะสามารถกด เพื่อเปิดไฟล์นั้นได้ และถ้ากดค้างที่ไฟล์นั้น ก็จะเป็น
การเริ่มต้นการเลือกไฟล์ เมื่อเลือก 1 รายการแล้ว สามารถกดไปที่รายการอื่นๆ เพื่อเลือกไฟล์เพิ่ม หรือ
กดซ้ำที่รายการที่เลือกแล้ว เพื่อยกเลิกการเลือกไฟล์ได้ โดยไฟล์ที่เราเลือก สามารถทำการแชร์ได้ และ
สามารถลบได้ โดยจะมีปุ่มแสดงตรงมุมขวา
ไฟล์ mediaexplorer.dart
import 'dart:io'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:share_plus/share_plus.dart'; import 'audioplayer.dart'; import 'fullscreenvideo.dart'; import 'viewphotoscreen.dart'; class MediaExplorer extends StatefulWidget { static const routeName = '/gallery'; const MediaExplorer({Key? key}) : super(key: key); @override State<StatefulWidget> createState() { return _MediaExplorerState(); } } class _MediaExplorerState extends State<MediaExplorer> { List<FileSystemEntity>? _files; List<int> _selectedItems = []; String _currentPath = ''; @override void initState() { super.initState(); _requestPermissionsAndLoadMedia(); } // ฟังก์ชันสำหรับขอสิทธิ์และโหลดไฟล์ void _requestPermissionsAndLoadMedia() async { // ขอสิทธิ์การเข้าถึงไฟล์สื่อ PermissionStatus status = await Permission.storage.request(); if (status.isGranted) { // ถ้าได้รับสิทธิ์แล้วให้โหลดไฟล์ _loadMediaFiles(); } else { // กรณีไม่ได้รับสิทธิ์ print("Permission Denied"); } } void _loadMediaFiles() async { Directory appDir = await getApplicationDocumentsDirectory(); String folderPath = '${appDir.path}'; // เปลี่ยนเป็นโฟลเดอร์ที่ต้องการ Directory folderDir = Directory(folderPath); setState(() { _currentPath = folderDir.path; _files = folderDir.listSync() .where((file) => file is File) .toList(); }); } Widget _buildIcon(FileSystemEntity file) { String ext = file.path.split('.').last.toLowerCase(); if (['jpg', 'jpeg', 'png', 'gif'].contains(ext)) { return FaIcon(FontAwesomeIcons.image, color: Colors.blue); } else if (['mp3', 'wav'].contains(ext)) { return FaIcon(FontAwesomeIcons.music, color: Colors.green); } else if (['mp4', 'avi'].contains(ext)) { return FaIcon(FontAwesomeIcons.video, color: Colors.red); } else { return FaIcon(FontAwesomeIcons.file, color: Colors.grey); } } void _onLongPress(int index) { setState(() { if (_selectedItems.contains(index)) { _selectedItems.remove(index); } else { _selectedItems.add(index); } }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Media Explorer'), actions: [ if (_selectedItems.isNotEmpty) IconButton( onPressed: () async { print("xdebug: share"); try { List<XFile> _fileShare = <XFile>[]; _selectedItems.forEach((index) async { _fileShare.add(XFile(_files![index]!.path)); }); if(_fileShare.isNotEmpty){ final result = await Share.shareXFiles(_fileShare); if (result.status == ShareResultStatus.success) { print('xdebug: Thank you for sharing!'); } } } catch (e) { print(e); } }, icon: FaIcon(FontAwesomeIcons.share), ), if(_selectedItems.isNotEmpty) IconButton( icon: FaIcon(FontAwesomeIcons.trashCan), onPressed: () async { bool _confirm; // สร้างตัวแปรรับค่า ยืนยันการลบ _confirm = await showDialog( context: context, builder: (context) { return AlertDialog( title: const Text("Confirm"), content: const Text("Are you sure you wish to delete selected item?"), actions: <Widget>[ ElevatedButton( onPressed: () => Navigator.of(context).pop(true), child: const Text("DELETE")), ElevatedButton( onPressed: () => Navigator.of(context).pop(false), child: const Text("CANCEL"), ), ], ); }, ); if (_confirm) { // ถ้ายืนยันการลบ เป็น true try { // โหลดข้อมูลใหม่อีกครั้ง setState(() { _selectedItems.forEach((index) { File file = _files![index] as File; file.deleteSync(); }); _selectedItems.clear(); _loadMediaFiles(); // Reload after deletion }); } catch (e) { print(e); } } }, ), ] ), body: _files != null ? GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, crossAxisSpacing: 4.0, mainAxisSpacing: 4.0, ), itemCount: _files?.length ?? 0, itemBuilder: (context, index) { FileSystemEntity file = _files![index]; bool isSelected = _selectedItems.contains(index); String ext = file.path.split('.').last.toLowerCase(); return GestureDetector( onTap: (){ // ถ้ามีการเลือกไฟล์ if(_selectedItems.isNotEmpty){ _onLongPress(index); }else{ // ถ้าเป็นการกดเปิดปกติ if (['jpg', 'jpeg', 'png', 'gif'].contains(ext)) { Navigator.push( context, MaterialPageRoute( builder: (context) => ViewPhotoScreen( photos: file.path,), ), ); } else if (['mp3', 'wav'].contains(ext)) { Navigator.push( context, MaterialPageRoute( builder: (context) => AudioPlayers( fileName: file.path, ), ), ); } else if (['mp4', 'avi'].contains(ext)) { Navigator.push( context, MaterialPageRoute( builder: (context) => FullScreenVideo( file.path), ), ); } else { } } }, onLongPress: () => _onLongPress(index), child: GridTile( footer: GridTileBar( backgroundColor: isSelected ? Colors.blue.withOpacity(0.5) : Colors.black54, title: Text( file.path.split('/').last, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 12), ), ), child: Container( color: isSelected ? Colors.blue.withOpacity(0.3) : Colors.white, child: Center(child: _buildIcon(file)), ), ), ); }, ) : Center(child: CircularProgressIndicator()), ); } }
ผลลัพธ์ที่ได้
เท่านี้เราก็สามารถทำระบบจัดการไฟล์ที่เราสร้างหรือโหลดเข้ามาใช้งานได้ และยังสามารถแชร์ไฟล์นั้น
ไปใช้งานยังแอปอื่นหรือส่งให้คนอื่นได้
หว้งว่าเนื้อหานี้จะเป็นแนวทางนำไปปรับประยุกต์ใช้งานต่อไปได้ไม่มากก็น้อย สำหรับในตอนหน้า
จะเป็นอะไรรอติดตาม