easy_permissions_manager 0.0.4 copy "easy_permissions_manager: ^0.0.4" to clipboard
easy_permissions_manager: ^0.0.4 copied to clipboard

A robust, JSON-based Flutter permission manager for Android, iOS, and Web. Handles configurations, UI prompts, and native setup automatically.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart'
    show kIsWeb, defaultTargetPlatform, TargetPlatform;
import 'package:easy_permissions_manager/easy_permissions_manager.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await EasyPermissions.initFromAsset('assets/easy_permissions.json');

  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        fontFamily: 'GoogleSans',
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF00C6FF),
          brightness: Brightness.light,
          primary: const Color(0xFF00C6FF),
          secondary: const Color(0xFF0072FF),
        ),
        appBarTheme: const AppBarTheme(
          centerTitle: true,
          elevation: 12,
          backgroundColor: Colors.white,
          titleTextStyle: TextStyle(
            color: Colors.black87,
            fontFamily: 'GoogleSans',
            fontWeight: FontWeight.bold,
            fontSize: 24,
          ),
        ),
        scaffoldBackgroundColor: const Color(0xFFF5F7FA),
      ),
      home: const PermissionDashboard(),
    );
  }
}

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

  @override
  State<PermissionDashboard> createState() => _PermissionDashboardState();
}

class _PermissionDashboardState extends State<PermissionDashboard> {
  Map<String, PermissionResult> _statuses = {};
  final Set<String> _loadingKeys = {};
  bool _loading = true;

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

  Future<void> _refreshStatuses() async {
    final results = await EasyPermissions.checkStatusDetailed();
    if (!mounted) return;
    setState(() {
      _statuses = results;
      _loading = false;
    });
  }

  Future<void> _askPermission(String key) async {
    setState(() {
      _loadingKeys.add(key);
    });
    await EasyPermissions.askPermissionDetailed(key);
    await _refreshStatuses();
    if (!mounted) return;
    setState(() {
      _loadingKeys.remove(key);
    });
  }

  Future<void> _askAll() async {
    await EasyPermissions.askDetailed();
    _refreshStatuses();
  }

  Future<void> _openSettings() async {
    final success = await EasyPermissions.openSettings();
    if (!success && mounted) {
      // Fallback for Web/Desktop
      showDialog(
        context: context,
        builder: (context) => AlertDialog(
          title: const Text("Action Required"),
          content: const Text(
            "Automatic settings navigation is not supported on this platform.\n\n"
            "Please manually grant the permission by clicking the lock icon in your URL bar or checking your system preferences.",
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: const Text("OK"),
            ),
          ],
        ),
      );
    }
    _refreshStatuses();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Easy Permissions")),
      body: _loading
          ? const Center(child: CircularProgressIndicator())
          : RefreshIndicator(
              onRefresh: _refreshStatuses,
              child: ListView(
                padding: const EdgeInsets.symmetric(
                  horizontal: 20,
                  vertical: 12,
                ),
                children: [
                  const GradientHeader(
                    icon: Icons.shield_rounded,
                    title:
                        "Control app permissions easily with EasyPermissions",
                    gradient: LinearGradient(
                      colors: [Color(0xFF00C6FF), Color(0xFF0072FF)],
                      begin: Alignment.topLeft,
                      end: Alignment.bottomRight,
                    ),
                  ),
                  const SizedBox(height: 12),
                  const Text(
                    "Your Permissions",
                    style: TextStyle(
                      fontSize: 15,
                      fontWeight: FontWeight.bold,
                      color: Colors.black87,
                    ),
                  ),
                  const SizedBox(height: 8),
                  ..._statuses.keys.map(
                    (key) => PermissionCard(
                      keyName: key,
                      result: _statuses[key]!,
                      isLoading: _loadingKeys.contains(key),
                      onAsk: () => _askPermission(key),
                      onOpenSettings: _openSettings,
                    ),
                  ),
                  const SizedBox(height: 16),
                  Row(
                    children: [
                      Expanded(
                        child: SizedBox(
                          height: 55,
                          child: FilledButton.icon(
                            onPressed: _askAll,
                            icon: const Icon(Icons.playlist_add_check_rounded),
                            label: const Text(
                              "Request All",
                              style: TextStyle(
                                fontSize: 14,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                            style: FilledButton.styleFrom(
                              shape: RoundedRectangleBorder(
                                borderRadius: BorderRadius.circular(16),
                              ),
                              backgroundColor: const Color(0xFF0072FF),
                            ),
                          ),
                        ),
                      ),
                      const SizedBox(width: 12),
                      Expanded(
                        child: SizedBox(
                          height: 55,
                          child: OutlinedButton.icon(
                            onPressed: _openSettings,
                            icon: const Icon(Icons.settings_rounded),
                            label: const Text(
                              "Open Settings",
                              style: TextStyle(
                                fontSize: 14,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                            style: OutlinedButton.styleFrom(
                              shape: RoundedRectangleBorder(
                                borderRadius: BorderRadius.circular(16),
                              ),
                              side: const BorderSide(
                                color: Color(0xFF0072FF),
                                width: 2,
                              ),
                            ),
                          ),
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
    );
  }
}

class GradientHeader extends StatelessWidget {
  final IconData icon;
  final String title;
  final Gradient gradient;

  const GradientHeader({
    super.key,
    required this.icon,
    required this.title,
    required this.gradient,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(24),
      decoration: BoxDecoration(
        gradient: gradient,
        borderRadius: BorderRadius.circular(20),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withValues(alpha: 0.08),
            blurRadius: 12,
            offset: const Offset(0, 6),
          ),
        ],
      ),
      child: Row(
        children: [
          Icon(icon, size: 40, color: Colors.white),
          const SizedBox(width: 8),
          Expanded(
            child: Text(
              title,
              textAlign: TextAlign.start,
              style: const TextStyle(
                fontSize: 14,
                color: Colors.white,
                height: 1.5,
                fontWeight: FontWeight.w500,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class PermissionCard extends StatelessWidget {
  final String keyName;
  final PermissionResult result;
  final bool isLoading;
  final VoidCallback onAsk;
  final VoidCallback onOpenSettings;

  const PermissionCard({
    super.key,
    required this.keyName,
    required this.result,
    required this.isLoading,
    required this.onAsk,
    required this.onOpenSettings,
  });

  @override
  Widget build(BuildContext context) {
    final isGranted = result.status.isGranted;
    final isPermanentlyDenied = result.isPermanentlyDenied;
    final isSupported = result.isSupported;
    final isEnabledInConfig = EasyPermissions.isEnabled(keyName);

    Color statusColor;
    String statusText;
    IconData statusIcon;

    if (!isEnabledInConfig) {
      statusColor = Colors.grey.shade500;
      statusIcon = Icons.block_rounded;
      statusText = "Disabled in Config";
    } else if (!isSupported) {
      statusColor = Colors.grey.shade500;
      statusIcon = Icons.do_not_disturb_alt_rounded;
      statusText = "Not Supported";
    } else if (isGranted) {
      statusColor = Colors.green.shade600;
      statusIcon = Icons.check_circle_rounded;
      statusText = "Granted";
    } else if (isPermanentlyDenied) {
      statusColor = Colors.red.shade600;
      statusIcon = Icons.cancel_rounded;
      statusText = "Denied Forever";
    } else {
      statusColor = Colors.orange.shade600;
      statusIcon = Icons.info_rounded;
      statusText = "Ask";
    }

    return Card(
      elevation: 3,
      margin: const EdgeInsets.symmetric(vertical: 8),
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
      shadowColor: Colors.black.withValues(alpha: 0.05),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Row(
          children: [
            Container(
              padding: const EdgeInsets.all(12),
              decoration: BoxDecoration(
                color: statusColor.withValues(alpha: 0.15),
                shape: BoxShape.circle,
              ),
              child: Icon(
                _getIconForKey(keyName),
                color: statusColor,
                size: 16,
              ),
            ),
            const SizedBox(width: 16),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    _capitalize(keyName),
                    style: const TextStyle(
                      fontSize: 14,
                      fontWeight: FontWeight.bold,
                      color: Colors.black87,
                    ),
                  ),
                  const SizedBox(height: 4),
                  Row(
                    children: [
                      Icon(statusIcon, size: 12, color: statusColor),
                      const SizedBox(width: 4),
                      Text(
                        statusText,
                        style: TextStyle(
                          fontSize: 12,
                          fontWeight: FontWeight.w500,
                          color: statusColor,
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
            if (isLoading)
              const Padding(
                padding: EdgeInsets.only(right: 12.0),
                child: SizedBox(
                  width: 20,
                  height: 20,
                  child: CircularProgressIndicator(strokeWidth: 2),
                ),
              )
            else if (isEnabledInConfig && isSupported && !isGranted)
              isPermanentlyDenied
                  ? FutureBuilder<bool>(
                      future: _canOpenSettings(),
                      builder: (context, snapshot) {
                        final canOpen = snapshot.data ?? true;
                        return canOpen
                            ? TextButton(
                                onPressed: onOpenSettings,
                                style: TextButton.styleFrom(
                                  foregroundColor: Colors.red.shade600,
                                  textStyle: const TextStyle(
                                    fontWeight: FontWeight.bold,
                                    fontSize: 12,
                                  ),
                                ),
                                child: const Text("Open Settings"),
                              )
                            : const SizedBox.shrink();
                      },
                    )
                  : TextButton(
                      onPressed: onAsk,
                      style: TextButton.styleFrom(
                        foregroundColor: const Color(0xFF0072FF),
                        textStyle: const TextStyle(
                          fontWeight: FontWeight.bold,
                          fontSize: 12,
                        ),
                      ),
                      child: const Text("Ask"),
                    ),
          ],
        ),
      ),
    );
  }

  Future<bool> _canOpenSettings() async {
    if (kIsWeb) return false;
    if (defaultTargetPlatform == TargetPlatform.linux ||
        defaultTargetPlatform == TargetPlatform.windows ||
        defaultTargetPlatform == TargetPlatform.macOS) {
      // On desktop, we might still try but warn the user
      return true;
    }
    return true;
  }

  IconData _getIconForKey(String key) {
    switch (key.toLowerCase()) {
      case 'camera':
        return Icons.camera_alt_rounded;
      case 'location':
        return Icons.location_on_rounded;
      case 'microphone':
        return Icons.mic_rounded;
      case 'photos':
        return Icons.photo_library_rounded;
      case 'contacts':
        return Icons.contacts_rounded;
      case 'notifications':
        return Icons.notifications_active_rounded;
      default:
        return Icons.security_rounded;
    }
  }

  String _capitalize(String s) => s[0].toUpperCase() + s.substring(1);
}
0
likes
160
points
42
downloads

Documentation

API reference

Publisher

verified publishergreelogix.com

Weekly Downloads

A robust, JSON-based Flutter permission manager for Android, iOS, and Web. Handles configurations, UI prompts, and native setup automatically.

Repository (GitHub)
View/report issues

Topics

#permissions #flutter #android #ios #setup

License

MIT (license)

Dependencies

flutter, path, permission_handler, xml

More

Packages that depend on easy_permissions_manager