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

A Flutter plugin for managing media files and directories across multiple platforms.

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';

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;

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

  @override
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }

  Future<void> _checkPermission() async {
    final hasPermission = await _mediaManager.requestStoragePermission();
    setState(() {
      _hasPermission = hasPermission;
    });
    if (hasPermission) {
      _loadDirectories();
    }
  }

  Future<void> _loadDirectories() async {
    try {
      final directories = await _mediaManager.getDirectories();
      setState(() {
        _directories = directories;
      });
    } catch (e) {
      debugPrint('Error loading directories: $e');
    }
  }

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

  // 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 _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: 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)),
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: [
          _buildDirectoriesTab(),
          MediaTab(mediaManager: _mediaManager, mediaType: MediaType.image),
          MediaTab(mediaManager: _mediaManager, mediaType: MediaType.video),
          MediaTab(mediaManager: _mediaManager, mediaType: MediaType.audio),
          MediaTab(mediaManager: _mediaManager, mediaType: MediaType.document),
          MediaTab(mediaManager: _mediaManager, mediaType: MediaType.zip),
        ],
      ),
    );
  }

  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 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();
    _loadMedia();
  }

  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:
          mediaPaths = await widget.mediaManager.getAllDocuments();
          break;
        case MediaType.zip:
          mediaPaths = await widget.mediaManager.getAllZipFiles();
          break;
      }

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

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

    return Scaffold(
      appBar: AppBar(
        title: Text(_getMediaTypeTitle()),
        automaticallyImplyLeading: false,
        actions: [
          IconButton(icon: const Icon(Icons.refresh), onPressed: _loadMedia),
        ],
      ),
      body:
          _isLoading
              ? const Center(child: CircularProgressIndicator())
              : _mediaPaths.isEmpty
              ? Center(child: Text('No ${_getMediaTypeTitle()} found'))
              : widget.mediaType == MediaType.image
              ? _buildImageGrid()
              : _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.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 _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);
    }
  }

  @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'),
            ),
          ],
        );
    }
  }
}
2
likes
160
points
147
downloads

Publisher

verified publisherswanflutterdev.com

Weekly Downloads

A Flutter plugin for managing media files and directories across multiple platforms.

Repository (GitHub)

Documentation

API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on media_manager