media_manager 0.0.7 copy "media_manager: ^0.0.7" to clipboard
media_manager: ^0.0.7 copied to clipboard

A Flutter plugin for managing media files on Android, iOS, and macOS with enhanced performance and latest OS compatibility.

example/lib/main.dart

// ignore_for_file: use_build_context_synchronously

import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:media_manager/media_manager.dart';

import 'optimized_media_tab.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Media Manager Demo',
      theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
      home: const MediaManagerScreen(),
    );
  }
}

class MediaManagerScreen extends StatefulWidget {
  const MediaManagerScreen({super.key});

  @override
  State<MediaManagerScreen> createState() => _MediaManagerScreenState();
}

class _MediaManagerScreenState extends State<MediaManagerScreen>
    with SingleTickerProviderStateMixin {
  final _mediaManager = MediaManager();
  late TabController _tabController;
  bool _hasPermission = false;
  List<Map<String, dynamic>> _directories = [];
  List<Map<String, dynamic>> _directoryContents = [];
  String? _selectedDirectory;
  bool _isolatesEnabled = true;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 7, vsync: this);
    _checkPermission();
  }

  @override
  void dispose() {
    _tabController.dispose();
    // Dispose isolates when the app is closing
    MediaManager.disposeIsolates();
    super.dispose();
  }

  Future<void> _checkPermission() async {
    final hasPermission = await _mediaManager.requestStoragePermission();
    setState(() {
      _hasPermission = hasPermission;
    });
    if (hasPermission) {
      // Permission granted, loading directories
      _loadDirectories();
    } else {
      // Permission denied
    }
  }

  Future<void> _loadDirectories() async {
    try {
      final directories = await _mediaManager.getDirectories();
      setState(() {
        _directories = directories;
      });
    } catch (e) {
      // Error loading directories
      ScaffoldMessenger.of(
        context,
      ).showSnackBar(SnackBar(content: Text('Error loading directories: $e')));
    }
  }

  Future<void> _loadDirectoryContents(String directoryPath) async {
    try {
      final contents = await _mediaManager.getDirectoryContents(directoryPath);
      setState(() {
        _directoryContents = contents;
        _selectedDirectory = directoryPath;
      });
    } catch (e) {
      // Error loading directory contents
    }
  }

  // Add this method to navigate up one level
  void _navigateUp() {
    if (_selectedDirectory == null) return;

    final pathParts = _selectedDirectory!.split('/');
    // Remove the last part of the path
    if (pathParts.length > 2) {
      // Ensure we don't go above root
      pathParts.removeLast();
      final parentPath = pathParts.join('/');
      _loadDirectoryContents(parentPath);
    } else {
      // If we're already at the root level, go back to directory list
      setState(() {
        _selectedDirectory = null;
        _directoryContents = [];
      });
    }
  }

  void _toggleIsolates() {
    setState(() {
      _isolatesEnabled = !_isolatesEnabled;
      MediaManager.setIsolateUsage(_isolatesEnabled);
    });
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(
          _isolatesEnabled
              ? 'Isolates enabled - Better performance for large file operations'
              : 'Isolates disabled - Operations run on main thread',
        ),
        duration: const Duration(seconds: 2),
      ),
    );
  }

  void _clearCache() async {
    await _mediaManager.clearImageCache();
    ScaffoldMessenger.of(
      context,
    ).showSnackBar(const SnackBar(content: Text('Image cache cleared')));
  }

  @override
  Widget build(BuildContext context) {
    if (!_hasPermission) {
      return Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text('Storage permission is required'),
              const SizedBox(height: 20),
              ElevatedButton(
                onPressed: _checkPermission,
                child: const Text('Request Permission'),
              ),
            ],
          ),
        ),
      );
    }

    return Scaffold(
      appBar: AppBar(
        title: const Text('Media Manager Demo'),
        actions: [
          IconButton(
            icon: Icon(_isolatesEnabled ? Icons.speed : Icons.speed_outlined),
            onPressed: _toggleIsolates,
            tooltip: _isolatesEnabled ? 'Disable Isolates' : 'Enable Isolates',
          ),
          IconButton(
            icon: const Icon(Icons.cleaning_services),
            onPressed: _clearCache,
            tooltip: 'Clear Cache',
          ),
        ],
        bottom: TabBar(
          controller: _tabController,
          isScrollable: true,
          tabs: const [
            Tab(text: 'Explorer', icon: Icon(Icons.folder)),
            Tab(text: 'Images', icon: Icon(Icons.image)),
            Tab(text: 'Videos', icon: Icon(Icons.video_file)),
            Tab(text: 'Audio', icon: Icon(Icons.audio_file)),
            Tab(text: 'Documents', icon: Icon(Icons.insert_drive_file)),
            Tab(text: 'Archives', icon: Icon(Icons.archive)),
            Tab(text: 'Custom Files', icon: Icon(Icons.search)),
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: [
          _buildDirectoriesTab(),
          OptimizedMediaTab(
            mediaManager: _mediaManager,
            mediaType: MediaType.image,
          ),
          OptimizedMediaTab(
            mediaManager: _mediaManager,
            mediaType: MediaType.video,
          ),
          OptimizedMediaTab(
            mediaManager: _mediaManager,
            mediaType: MediaType.audio,
          ),
          OptimizedMediaTab(
            mediaManager: _mediaManager,
            mediaType: MediaType.document,
          ),
          OptimizedMediaTab(
            mediaManager: _mediaManager,
            mediaType: MediaType.zip,
          ),
          CustomFormatTab(mediaManager: _mediaManager),
        ],
      ),
    );
  }

  Widget _buildDirectoriesTab() {
    return Column(
      children: [
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              Expanded(
                child: ElevatedButton(
                  onPressed: _loadDirectories,
                  child: const Text('Refresh Directories'),
                ),
              ),
            ],
          ),
        ),
        if (_selectedDirectory != null)
          Padding(
            padding: const EdgeInsets.symmetric(
              horizontal: 16.0,
              vertical: 8.0,
            ),
            child: Row(
              children: [
                IconButton(
                  icon: const Icon(Icons.arrow_back),
                  onPressed: _navigateUp,
                ),
                Expanded(
                  child: Text(
                    'Directory: ${_selectedDirectory?.split('/').last}',
                    style: const TextStyle(fontWeight: FontWeight.bold),
                  ),
                ),
                IconButton(
                  icon: const Icon(Icons.close),
                  onPressed: () {
                    setState(() {
                      _selectedDirectory = null;
                      _directoryContents = [];
                    });
                  },
                ),
              ],
            ),
          ),
        Expanded(
          child: _selectedDirectory == null
              ? _directories.isEmpty
                    ? const Center(
                        child: Text(
                          'No directories found. Tap Refresh button.',
                        ),
                      )
                    : ListView.builder(
                        padding: const EdgeInsets.all(8.0),
                        itemCount: _directories.length,
                        itemBuilder: (context, index) {
                          final directory = _directories[index];
                          return Card(
                            child: ListTile(
                              leading: const Icon(
                                Icons.folder,
                                color: Colors.amber,
                              ),
                              title: Text(directory['name'] as String),
                              onTap: () => _loadDirectoryContents(
                                directory['path'] as String,
                              ),
                            ),
                          );
                        },
                      )
              : _directoryContents.isEmpty
              ? const Center(child: Text('Directory is empty'))
              : ListView.builder(
                  padding: const EdgeInsets.all(8.0),
                  itemCount: _directoryContents.length,
                  itemBuilder: (context, index) {
                    final item = Map<String, dynamic>.from(
                      _directoryContents[index],
                    );
                    final name = item['name'] as String;
                    final isDirectory = item['isDirectory'] as bool;
                    final type = item['type'] as String;
                    final size = item['readableSize'] as String;
                    final extension = item['extension'] as String;

                    return Card(
                      child: ListTile(
                        leading: Icon(
                          isDirectory ? Icons.folder : _getFileIcon(type),
                          color: isDirectory ? Colors.amber : Colors.blue,
                        ),
                        title: Text(name),
                        subtitle: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(_truncatePath(item['path'] as String)),
                            if (!isDirectory) Text('$size • $extension'),
                          ],
                        ),
                        onTap: () {
                          if (isDirectory) {
                            _loadDirectoryContents(item['path'] as String);
                          }
                        },
                      ),
                    );
                  },
                ),
        ),
      ],
    );
  }

  IconData _getFileIcon(String type) {
    switch (type) {
      case 'image':
        return Icons.image;
      case 'video':
        return Icons.video_file;
      case 'audio':
        return Icons.audio_file;
      case 'document':
        return Icons.description;
      case 'zip':
        return Icons.folder_zip;
      default:
        return Icons.insert_drive_file;
    }
  }

  String _truncatePath(String path) {
    if (path.length <= 40) return path;
    final parts = path.split('/');
    if (parts.length <= 2) return path;
    return '.../${parts[parts.length - 2]}/${parts.last}';
  }
}

enum MediaType { image, video, audio, document, zip }

// Tab for custom file formats
class CustomFormatTab extends StatefulWidget {
  final MediaManager mediaManager;

  const CustomFormatTab({super.key, required this.mediaManager});

  @override
  State<CustomFormatTab> createState() => _CustomFormatTabState();
}

class _CustomFormatTabState extends State<CustomFormatTab>
    with AutomaticKeepAliveClientMixin {
  List<String> _filePaths = [];
  bool _isLoading = false;
  String _selectedCategory = 'Apps';

  final Map<String, List<String>> _formatCategories = {
    'Apps': ['apk', 'ipa', 'exe', 'msi', 'deb', 'rpm'],
    'Code': ['dart', 'java', 'kt', 'swift', 'py', 'js', 'ts', 'cpp', 'c', 'h'],
    'Archives': ['rar', '7z', 'tar', 'gz', 'bz2', 'xz'],
    'Config': ['json', 'xml', 'yaml', 'yml', 'ini', 'cfg', 'conf'],
    'Database': ['db', 'sqlite', 'sql', 'mdb'],
  };

  @override
  bool get wantKeepAlive => true;

  @override
  void initState() {
    super.initState();
    _loadFiles();
  }

  Future<void> _loadFiles() async {
    setState(() {
      _isLoading = true;
    });

    try {
      final formats = _formatCategories[_selectedCategory] ?? [];
      final filePaths = await widget.mediaManager.getAllFilesByFormat(formats);

      setState(() {
        _filePaths = filePaths;
      });

      // نمایش پیام موفقیت برای کاربر
      if (mounted && filePaths.isNotEmpty) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text(
              '${filePaths.length} فایل $_selectedCategory پیدا شد',
            ),
            backgroundColor: Colors.green,
            duration: const Duration(seconds: 2),
          ),
        );
      }
    } catch (e) {
      // Error loading files
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text(
              'خطا در بارگذاری فایل‌های $_selectedCategory: ${e.toString()}',
            ),
            backgroundColor: Colors.red,
            duration: const Duration(seconds: 4),
            action: SnackBarAction(
              label: 'تلاش مجدد',
              textColor: Colors.white,
              onPressed: _loadFiles,
            ),
          ),
        );
      }
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);

    return Column(
      children: [
        // Category selector
        Container(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text(
                'Select File Category:',
                style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 8),
              Wrap(
                spacing: 8.0,
                children: _formatCategories.keys.map((category) {
                  final isSelected = category == _selectedCategory;
                  return FilterChip(
                    label: Text(category),
                    selected: isSelected,
                    onSelected: (selected) {
                      if (selected) {
                        setState(() {
                          _selectedCategory = category;
                        });
                        _loadFiles();
                      }
                    },
                  );
                }).toList(),
              ),
              const SizedBox(height: 8),
              Text(
                'Formats: ${_formatCategories[_selectedCategory]?.join(", ") ?? ""}',
                style: TextStyle(
                  fontSize: 12,
                  color: Theme.of(
                    context,
                  ).colorScheme.onSurface.withValues(alpha: 0.6),
                ),
              ),
            ],
          ),
        ),

        // Refresh button
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16.0),
          child: Row(
            children: [
              Expanded(
                child: ElevatedButton.icon(
                  onPressed: _isLoading ? null : _loadFiles,
                  icon: _isLoading
                      ? const SizedBox(
                          width: 16,
                          height: 16,
                          child: CircularProgressIndicator(strokeWidth: 2),
                        )
                      : const Icon(Icons.refresh),
                  label: Text(
                    _isLoading ? 'Loading...' : 'Refresh $_selectedCategory',
                  ),
                ),
              ),
            ],
          ),
        ),

        const SizedBox(height: 16),

        // File list
        Expanded(
          child: _isLoading
              ? const Center(child: CircularProgressIndicator())
              : _filePaths.isEmpty
              ? Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Icon(
                        Icons.folder_open,
                        size: 64,
                        color: Theme.of(
                          context,
                        ).colorScheme.onSurface.withValues(alpha: 0.4),
                      ),
                      const SizedBox(height: 16),
                      Text(
                        'No $_selectedCategory files found',
                        style: TextStyle(
                          fontSize: 16,
                          color: Theme.of(
                            context,
                          ).colorScheme.onSurface.withValues(alpha: 0.6),
                        ),
                      ),
                      const SizedBox(height: 8),
                      Text(
                        'Supported formats: ${_formatCategories[_selectedCategory]?.join(", ") ?? ""}',
                        textAlign: TextAlign.center,
                        style: TextStyle(
                          fontSize: 12,
                          color: Theme.of(
                            context,
                          ).colorScheme.onSurface.withValues(alpha: 0.4),
                        ),
                      ),
                    ],
                  ),
                )
              : ListView.builder(
                  padding: const EdgeInsets.all(16.0),
                  itemCount: _filePaths.length,
                  itemBuilder: (context, index) {
                    final filePath = _filePaths[index];
                    final fileName = filePath.split('/').last;
                    final fileExtension = fileName
                        .split('.')
                        .last
                        .toLowerCase();

                    return Card(
                      margin: const EdgeInsets.only(bottom: 8.0),
                      child: ListTile(
                        leading: CircleAvatar(
                          backgroundColor: _getColorForExtension(fileExtension),
                          child: Text(
                            fileExtension.toUpperCase(),
                            style: const TextStyle(
                              color: Colors.white,
                              fontSize: 10,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                        ),
                        title: Text(
                          fileName,
                          style: const TextStyle(fontWeight: FontWeight.w500),
                        ),
                        subtitle: Text(
                          filePath,
                          style: TextStyle(
                            fontSize: 12,
                            color: Theme.of(
                              context,
                            ).colorScheme.onSurface.withValues(alpha: 0.6),
                          ),
                          maxLines: 2,
                          overflow: TextOverflow.ellipsis,
                        ),
                        trailing: Icon(
                          _getIconForCategory(_selectedCategory),
                          color: Theme.of(context).colorScheme.primary,
                        ),
                      ),
                    );
                  },
                ),
        ),
      ],
    );
  }

  Color _getColorForExtension(String extension) {
    switch (extension) {
      case 'apk':
      case 'ipa':
        return Colors.green;
      case 'exe':
      case 'msi':
        return Colors.blue;
      case 'dart':
      case 'java':
      case 'kt':
        return Colors.orange;
      case 'py':
      case 'js':
      case 'ts':
        return Colors.purple;
      case 'rar':
      case '7z':
      case 'tar':
        return Colors.brown;
      case 'json':
      case 'xml':
      case 'yaml':
        return Colors.teal;
      case 'db':
      case 'sqlite':
        return Colors.indigo;
      default:
        return Colors.grey;
    }
  }

  IconData _getIconForCategory(String category) {
    switch (category) {
      case 'Apps':
        return Icons.apps;
      case 'Code':
        return Icons.code;
      case 'Archives':
        return Icons.archive;
      case 'Config':
        return Icons.settings;
      case 'Database':
        return Icons.storage;
      default:
        return Icons.insert_drive_file;
    }
  }
}

// Tab for specific media types
class MediaTab extends StatefulWidget {
  final MediaManager mediaManager;
  final MediaType mediaType;

  const MediaTab({
    super.key,
    required this.mediaManager,
    required this.mediaType,
  });

  @override
  State<MediaTab> createState() => _MediaTabState();
}

class _MediaTabState extends State<MediaTab>
    with AutomaticKeepAliveClientMixin {
  List<String> _mediaPaths = [];
  bool _isLoading = true;

  @override
  void initState() {
    super.initState();
    // Delay loading to ensure widget tree is built
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _loadMedia();
    });
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    // Additional setup can be done here if needed
  }

  Future<void> _loadMedia() async {
    setState(() {
      _isLoading = true;
    });

    try {
      List<String> mediaPaths;
      switch (widget.mediaType) {
        case MediaType.image:
          mediaPaths = await widget.mediaManager.getAllImages();
          break;
        case MediaType.video:
          mediaPaths = await widget.mediaManager.getAllVideos();
          break;
        case MediaType.audio:
          mediaPaths = await widget.mediaManager.getAllAudio();
          break;
        case MediaType.document:
          try {
            mediaPaths = await widget.mediaManager.getAllDocuments();
            if (mediaPaths.isNotEmpty) {
              // Show success message after loading is complete
              if (mounted) {
                WidgetsBinding.instance.addPostFrameCallback((_) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(
                      content: Text(
                        'Found ${mediaPaths.length} document files',
                      ),
                      duration: const Duration(seconds: 2),
                      backgroundColor: Colors.green,
                    ),
                  );
                });
              }
            } else {
              // Show helpful message to user after widget is built
              if (mounted) {
                WidgetsBinding.instance.addPostFrameCallback((_) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(
                      content: Text(
                        'No documents found. Try placing some PDF, Word, or text files in Downloads or Documents folders.',
                      ),
                      duration: Duration(seconds: 5),
                      backgroundColor: Colors.orange,
                    ),
                  );
                });
              }
            }
          } catch (e) {
            rethrow;
          }
          break;
        case MediaType.zip:
          try {
            mediaPaths = await widget.mediaManager.getAllZipFiles();
          } catch (e) {
            rethrow;
          }
          break;
      }

      setState(() {
        _mediaPaths = mediaPaths;
        _isLoading = false;
      });
    } catch (e) {
      setState(() {
        _isLoading = false;
      });
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error loading ${_getMediaTypeTitle()}: $e')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);

    return Column(
      children: [
        // Header with refresh button
        Container(
          padding: const EdgeInsets.all(8.0),
          decoration: BoxDecoration(
            color: Theme.of(context).colorScheme.surface,
            border: Border(
              bottom: BorderSide(
                color: Theme.of(context).dividerColor,
                width: 0.5,
              ),
            ),
          ),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      _getMediaTypeTitle(),
                      style: Theme.of(context).textTheme.titleMedium?.copyWith(
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    if (_isLoading && widget.mediaType == MediaType.document)
                      const Text(
                        'Scanning directories... This may take a moment',
                        style: TextStyle(fontSize: 12, color: Colors.orange),
                      ),
                  ],
                ),
              ),
              _isLoading
                  ? const SizedBox(
                      width: 20,
                      height: 20,
                      child: CircularProgressIndicator(strokeWidth: 2),
                    )
                  : IconButton(
                      icon: const Icon(Icons.refresh),
                      onPressed: _loadMedia,
                      tooltip: 'Refresh ${_getMediaTypeTitle()}',
                    ),
            ],
          ),
        ),
        // Content
        Expanded(
          child: _isLoading
              ? Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      const CircularProgressIndicator(),
                      const SizedBox(height: 16),
                      Text(
                        'Loading ${_getMediaTypeTitle().toLowerCase()}...',
                        style: Theme.of(context).textTheme.bodyMedium,
                      ),
                      const SizedBox(height: 8),
                      Text(
                        'This may take a moment for large collections',
                        style: Theme.of(
                          context,
                        ).textTheme.bodySmall?.copyWith(color: Colors.grey),
                      ),
                    ],
                  ),
                )
              : _mediaPaths.isEmpty
              ? Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      _getMediaIconLarge(),
                      const SizedBox(height: 16),
                      Text(
                        'No ${_getMediaTypeTitle()} found',
                        style: Theme.of(
                          context,
                        ).textTheme.titleMedium?.copyWith(color: Colors.grey),
                      ),
                      const SizedBox(height: 8),
                      TextButton.icon(
                        onPressed: _loadMedia,
                        icon: const Icon(Icons.refresh),
                        label: const Text('Tap to refresh'),
                      ),
                    ],
                  ),
                )
              : widget.mediaType == MediaType.image
              ? _buildImageGrid()
              : widget.mediaType == MediaType.video
              ? _buildVideoGrid()
              : widget.mediaType == MediaType.audio
              ? _buildAudioGrid()
              : _buildMediaList(),
        ),
      ],
    );
  }

  String _getMediaTypeTitle() {
    switch (widget.mediaType) {
      case MediaType.image:
        return 'Images';
      case MediaType.video:
        return 'Videos';
      case MediaType.audio:
        return 'Audio Files';
      case MediaType.document:
        return 'Documents';
      case MediaType.zip:
        return 'Archives';
    }
  }

  Widget _buildImageGrid() {
    return GridView.builder(
      padding: const EdgeInsets.all(8),
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 3,
        mainAxisSpacing: 8,
        crossAxisSpacing: 8,
      ),
      itemCount: _mediaPaths.length,
      itemBuilder: (context, index) {
        final path = _mediaPaths[index];
        return GestureDetector(
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => MediaPreviewScreen(
                  mediaPath: path,
                  mediaType: widget.mediaType,
                  mediaManager: widget.mediaManager,
                ),
              ),
            );
          },
          child: FutureBuilder<Uint8List?>(
            future: widget.mediaManager.getImagePreview(path),
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {
                return Container(
                  decoration: BoxDecoration(
                    color: Colors.grey[200],
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: const Center(child: CircularProgressIndicator()),
                );
              }

              if (snapshot.hasError) {
                return Container(
                  decoration: BoxDecoration(
                    color: Colors.red[100],
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: const Center(
                    child: Icon(Icons.error, color: Colors.red),
                  ),
                );
              }

              if (snapshot.connectionState == ConnectionState.done &&
                  snapshot.hasData &&
                  snapshot.data != null) {
                return ClipRRect(
                  borderRadius: BorderRadius.circular(8),
                  child: Image.memory(snapshot.data!, fit: BoxFit.cover),
                );
              }

              return Container(
                decoration: BoxDecoration(
                  color: Colors.grey[300],
                  borderRadius: BorderRadius.circular(8),
                ),
                child: const Center(child: Icon(Icons.image, size: 30)),
              );
            },
          ),
        );
      },
    );
  }

  Widget _buildVideoGrid() {
    return GridView.builder(
      padding: const EdgeInsets.all(8),
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 3,
        mainAxisSpacing: 8,
        crossAxisSpacing: 8,
      ),
      itemCount: _mediaPaths.length,
      itemBuilder: (context, index) {
        final path = _mediaPaths[index];
        return GestureDetector(
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => MediaPreviewScreen(
                  mediaPath: path,
                  mediaType: widget.mediaType,
                  mediaManager: widget.mediaManager,
                ),
              ),
            );
          },
          child: FutureBuilder<Uint8List?>(
            future: widget.mediaManager.getVideoThumbnail(path),
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {
                return Container(
                  decoration: BoxDecoration(
                    color: Colors.grey[200],
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: const Center(
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        CircularProgressIndicator(),
                        SizedBox(height: 8),
                        Icon(Icons.video_file, size: 20),
                      ],
                    ),
                  ),
                );
              }

              if (snapshot.hasError) {
                return Container(
                  decoration: BoxDecoration(
                    color: Colors.red[100],
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: const Center(
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Icon(Icons.error, color: Colors.red),
                        SizedBox(height: 4),
                        Icon(Icons.video_file, size: 20),
                      ],
                    ),
                  ),
                );
              }

              if (snapshot.connectionState == ConnectionState.done &&
                  snapshot.hasData &&
                  snapshot.data != null) {
                return Stack(
                  children: [
                    ClipRRect(
                      borderRadius: BorderRadius.circular(8),
                      child: Image.memory(
                        snapshot.data!,
                        fit: BoxFit.cover,
                        width: double.infinity,
                        height: double.infinity,
                      ),
                    ),
                    Positioned(
                      bottom: 4,
                      right: 4,
                      child: Container(
                        padding: const EdgeInsets.all(4),
                        decoration: BoxDecoration(
                          color: Colors.black54,
                          borderRadius: BorderRadius.circular(4),
                        ),
                        child: const Icon(
                          Icons.play_arrow,
                          color: Colors.white,
                          size: 16,
                        ),
                      ),
                    ),
                  ],
                );
              }

              return Container(
                decoration: BoxDecoration(
                  color: Colors.grey[300],
                  borderRadius: BorderRadius.circular(8),
                ),
                child: const Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Icon(Icons.video_file, size: 30),
                      SizedBox(height: 4),
                      Text('Video', style: TextStyle(fontSize: 12)),
                    ],
                  ),
                ),
              );
            },
          ),
        );
      },
    );
  }

  Widget _buildAudioGrid() {
    return GridView.builder(
      padding: const EdgeInsets.all(8),
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 3,
        mainAxisSpacing: 8,
        crossAxisSpacing: 8,
      ),
      itemCount: _mediaPaths.length,
      itemBuilder: (context, index) {
        final path = _mediaPaths[index];
        final fileName = path.split('/').last;
        return GestureDetector(
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => MediaPreviewScreen(
                  mediaPath: path,
                  mediaType: widget.mediaType,
                  mediaManager: widget.mediaManager,
                ),
              ),
            );
          },
          child: FutureBuilder<Uint8List?>(
            future: widget.mediaManager.getAudioThumbnail(path),
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {
                return Container(
                  decoration: BoxDecoration(
                    color: Colors.blue[50],
                    borderRadius: BorderRadius.circular(8),
                    border: Border.all(color: Colors.blue[200]!, width: 1),
                  ),
                  child: const Center(
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        CircularProgressIndicator(),
                        SizedBox(height: 8),
                        Icon(Icons.music_note, size: 20),
                      ],
                    ),
                  ),
                );
              }

              if (snapshot.hasError) {
                return Container(
                  decoration: BoxDecoration(
                    color: Colors.red[100],
                    borderRadius: BorderRadius.circular(8),
                    border: Border.all(color: Colors.red[200]!, width: 1),
                  ),
                  child: const Center(
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Icon(Icons.error, color: Colors.red),
                        SizedBox(height: 4),
                        Icon(Icons.music_note, size: 20),
                      ],
                    ),
                  ),
                );
              }

              if (snapshot.connectionState == ConnectionState.done &&
                  snapshot.hasData &&
                  snapshot.data != null) {
                return Stack(
                  children: [
                    ClipRRect(
                      borderRadius: BorderRadius.circular(8),
                      child: Image.memory(
                        snapshot.data!,
                        fit: BoxFit.cover,
                        width: double.infinity,
                        height: double.infinity,
                      ),
                    ),
                    Positioned(
                      bottom: 4,
                      right: 4,
                      child: Container(
                        padding: const EdgeInsets.all(4),
                        decoration: BoxDecoration(
                          color: Colors.black54,
                          borderRadius: BorderRadius.circular(4),
                        ),
                        child: const Icon(
                          Icons.music_note,
                          color: Colors.white,
                          size: 16,
                        ),
                      ),
                    ),
                  ],
                );
              }

              return Container(
                decoration: BoxDecoration(
                  color: Colors.blue[50],
                  borderRadius: BorderRadius.circular(8),
                  border: Border.all(color: Colors.blue[200]!, width: 1),
                ),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Icon(Icons.music_note, size: 40, color: Colors.blue[600]),
                    const SizedBox(height: 8),
                    Padding(
                      padding: const EdgeInsets.symmetric(horizontal: 4),
                      child: Text(
                        fileName,
                        style: const TextStyle(fontSize: 10),
                        textAlign: TextAlign.center,
                        maxLines: 2,
                        overflow: TextOverflow.ellipsis,
                      ),
                    ),
                  ],
                ),
              );
            },
          ),
        );
      },
    );
  }

  Widget _buildMediaList() {
    return ListView.builder(
      itemCount: _mediaPaths.length,
      itemBuilder: (context, index) {
        final path = _mediaPaths[index];
        final fileName = path.split('/').last;

        return ListTile(
          leading: _getMediaIcon(),
          title: Text(fileName),
          subtitle: Text(path),
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => MediaPreviewScreen(
                  mediaPath: path,
                  mediaType: widget.mediaType,
                  mediaManager: widget.mediaManager,
                ),
              ),
            );
          },
        );
      },
    );
  }

  Widget _getMediaIcon() {
    switch (widget.mediaType) {
      case MediaType.image:
        return const Icon(Icons.image);
      case MediaType.video:
        return const Icon(Icons.video_file);
      case MediaType.audio:
        return const Icon(Icons.audio_file);
      case MediaType.document:
        return const Icon(Icons.insert_drive_file);
      case MediaType.zip:
        return const Icon(Icons.archive);
    }
  }

  Widget _getMediaIconLarge() {
    switch (widget.mediaType) {
      case MediaType.image:
        return const Icon(Icons.image, size: 64, color: Colors.grey);
      case MediaType.video:
        return const Icon(Icons.video_file, size: 64, color: Colors.grey);
      case MediaType.audio:
        return const Icon(Icons.audio_file, size: 64, color: Colors.grey);
      case MediaType.document:
        return const Icon(
          Icons.insert_drive_file,
          size: 64,
          color: Colors.grey,
        );
      case MediaType.zip:
        return const Icon(Icons.archive, size: 64, color: Colors.grey);
    }
  }

  @override
  bool get wantKeepAlive => true;
}

// Screen for previewing media
class MediaPreviewScreen extends StatelessWidget {
  final String mediaPath;
  final MediaType mediaType;
  final MediaManager mediaManager;

  const MediaPreviewScreen({
    super.key,
    required this.mediaPath,
    required this.mediaType,
    required this.mediaManager,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(mediaPath.split('/').last)),
      body: Center(child: _buildMediaPreview()),
    );
  }

  Widget _buildMediaPreview() {
    switch (mediaType) {
      case MediaType.image:
        return FutureBuilder<Uint8List?>(
          future: mediaManager.getImagePreview(mediaPath),
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const CircularProgressIndicator();
            } else if (snapshot.hasError) {
              return Text('Error: ${snapshot.error}');
            } else if (snapshot.hasData && snapshot.data != null) {
              return InteractiveViewer(
                panEnabled: true,
                boundaryMargin: const EdgeInsets.all(20),
                minScale: 0.5,
                maxScale: 4,
                child: Image.memory(snapshot.data!, fit: BoxFit.contain),
              );
            } else {
              return const Text('Image not available');
            }
          },
        );
      case MediaType.video:
        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.video_file, size: 100),
            const SizedBox(height: 20),
            Text(
              'Video: ${mediaPath.split('/').last}',
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                // Here you would typically implement video playback
                // For example, using the video_player package
              },
              child: const Text('Play Video'),
            ),
          ],
        );
      case MediaType.audio:
        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.audio_file, size: 100),
            const SizedBox(height: 20),
            Text(
              'Audio: ${mediaPath.split('/').last}',
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                // Here you would typically implement audio playback
                // For example, using the audioplayers package
              },
              child: const Text('Play Audio'),
            ),
          ],
        );
      case MediaType.document:
        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.insert_drive_file, size: 100),
            const SizedBox(height: 20),
            Text(
              'Document: ${mediaPath.split('/').last}',
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                // Here you would typically implement document viewing
                // For example, using the flutter_pdfview package for PDFs
              },
              child: const Text('Open Document'),
            ),
          ],
        );
      case MediaType.zip:
        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.archive, size: 100),
            const SizedBox(height: 20),
            Text(
              'Archive: ${mediaPath.split('/').last}',
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                // Here you would typically implement archive extraction
                // or listing of contents
              },
              child: const Text('Extract Archive'),
            ),
          ],
        );
    }
  }
}
3
likes
160
points
227
downloads

Publisher

verified publisherswanflutterdev.com

Weekly Downloads

A Flutter plugin for managing media files on Android, iOS, and macOS with enhanced performance and latest OS compatibility.

Repository (GitHub)

Documentation

API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on media_manager

Packages that implement media_manager