1. Anuncie Aqui ! Entre em contato fdantas@4each.com.br

[Flutter] Photo library permission is denied

Discussão em 'Mobile' iniciado por Stack, Outubro 17, 2024 às 09:43.

  1. Stack

    Stack Membro Participativo

    I am trying to access the photo library with image_picker for my app , for the user to add cover photo but I can't find any request when I access the Settings>Privacy>Photos and also the app when I scroll the settings down it's not appearing here is the code

    class WorkoutContentAdded extends StatefulWidget {
    @override
    _WorkoutContentAddedState createState() => _WorkoutContentAddedState();
    }

    class _WorkoutContentAddedState extends State<WorkoutContentAdded> {
    String? coverPhotoUrl;
    File? _image;

    @override
    Widget build(BuildContext context) {
    return Scaffold(
    backgroundColor: Colors.white,
    body: StreamBuilder<QuerySnapshot>(
    stream: FirebaseFirestore.instance.collection('programs').snapshots(),
    builder: (context, snapshot) {
    if (snapshot.hasError) {
    return Center(child: Text('Error: ${snapshot.error}'));
    }

    if (snapshot.connectionState == ConnectionState.waiting) {
    return Center(child: CircularProgressIndicator());
    }

    if (snapshot.data!.docs.isEmpty) {
    return Center(child: Text('No programs available'));
    }

    DocumentSnapshot firstDocument = snapshot.data!.docs.first;
    Map<String, dynamic> firstData = firstDocument.data() as Map<String, dynamic>;

    return CustomScrollView(
    slivers: [
    SliverAppBar(
    expandedHeight: 300,
    floating: false,
    pinned: true,
    backgroundColor: Colors.transparent,
    flexibleSpace: FlexibleSpaceBar(
    background: Stack(
    fit: StackFit.expand,
    children: [
    coverPhotoUrl != null
    ? Image.file(
    File(coverPhotoUrl!),
    fit: BoxFit.cover,
    )
    : Image.asset(
    'assets/cover_photo.jpg',
    fit: BoxFit.cover,
    ),
    Positioned(
    left: 16,
    right: 16,
    bottom: 16,
    child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
    Text(
    firstData['title'] ?? 'No title',
    style: TextStyle(
    fontSize: 24,
    fontWeight: FontWeight.bold,
    color: Colors.white,
    ),
    ),
    SizedBox(height: 8),
    Row(
    children: [
    Container(
    padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
    decoration: BoxDecoration(
    color: Colors.grey.withOpacity(0.5),
    borderRadius: BorderRadius.circular(20),
    ),
    child: Text(
    firstData['category'] ?? 'N/A',
    style: TextStyle(color: Colors.white),
    ),
    ),
    SizedBox(width: 8),
    Container(
    padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
    decoration: BoxDecoration(
    color: Colors.grey.withOpacity(0.5),
    borderRadius: BorderRadius.circular(20),
    ),
    child: Text(
    (firstData['days'] as List<dynamic>?)?.map((day) => day.toString()[0].toUpperCase()).join(' ') ?? 'N/A',
    style: TextStyle(color: Colors.white),
    ),
    ),
    ],
    ),
    ],
    ),
    ),
    ],
    ),
    ),
    leading: Padding(
    padding: const EdgeInsets.all(8.0),
    child: CircleAvatar(
    backgroundColor: Colors.grey.withOpacity(0.5),
    child: IconButton(
    icon: Icon(Icons.arrow_back, color: Colors.white),
    onPressed: () => Navigator.pop(context),
    ),
    ),
    ),
    actions: [
    Padding(
    padding: const EdgeInsets.all(8.0),
    child: ElevatedButton.icon(
    onPressed: _showBottomSheet,
    icon: Icon(Icons.edit, color: Colors.white),
    label: Text('Edit', style: TextStyle(color: Colors.white)),
    style: ElevatedButton.styleFrom(
    backgroundColor: Colors.black.withOpacity(0.5),
    shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(20),
    ),
    padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
    ),
    ),
    ),
    ],
    ),
    ],
    );
    },
    ),
    );
    }

    void _showBottomSheet() {
    showModalBottomSheet(
    context: context,
    isScrollControlled: true,
    backgroundColor: Colors.white,
    shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
    ),
    builder: (BuildContext context) {
    return Padding(
    padding: EdgeInsets.only(
    bottom: MediaQuery.of(context).viewInsets.bottom,
    ),
    child: Container(
    padding: EdgeInsets.all(24),
    child: Column(
    mainAxisSize: MainAxisSize.min,
    children: [
    ElevatedButton(
    onPressed: () {
    print("Add cover photo button pressed");
    _pickImage();
    },
    child: Text(
    'Add cover photo',
    style: TextStyle(
    fontWeight: FontWeight.bold,
    fontSize: 16,
    ),
    ),
    style: ElevatedButton.styleFrom(
    backgroundColor: Colors.white,
    foregroundColor: Colors.black,
    padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
    shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(30),
    ),
    elevation: 5,
    ),
    ),
    SizedBox(height: 24),
    Text(
    'Add more details about the workout',
    textAlign: TextAlign.center,
    style: TextStyle(
    fontSize: 20,
    fontWeight: FontWeight.bold,
    color: Colors.black,
    ),
    ),
    SizedBox(height: 16),
    Text(
    'Do you use any equipment for the workouts?',
    textAlign: TextAlign.center,
    style: TextStyle(
    fontSize: 16,
    color: Colors.black,
    ),
    ),
    SizedBox(height: 16),
    TextField(
    decoration: InputDecoration(
    hintText: 'eg: 2kg dumbbells, 3kg ball',
    border: OutlineInputBorder(),
    ),
    ),
    SizedBox(height: 24),
    Center(
    child: ElevatedButton(
    onPressed: () {
    // TODO: Implement save functionality
    Navigator.pop(context);
    },
    child: Text(
    'Save',
    style: TextStyle(
    fontWeight: FontWeight.bold,
    fontSize: 16,
    ),
    ),
    style: ElevatedButton.styleFrom(
    backgroundColor: Colors.black,
    foregroundColor: Colors.white,
    padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16),
    shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(30),
    ),
    ),
    ),
    ),
    ],
    ),
    ),
    );
    },
    );
    }

    Future<void> _pickImage() async {
    print("_pickImage method started");
    try {
    // Check current permission status
    var status = await Permission.photos.status;
    print("Current photo library permission status: $status");

    if (status.isGranted) {
    await _proceedWithImagePicking();
    } else if (status.isPermanentlyDenied) {
    print("Photo library permission is permanently denied");
    // Show a dialog explaining why the permission is needed and how to enable it
    showDialog(
    context: context,
    builder: (BuildContext context) => AlertDialog(
    title: Text('Permission Required'),
    content: Text('Photo library access is required to select a cover photo. Please enable it in your device settings.'),
    actions: <Widget>[
    TextButton(
    child: Text('Cancel'),
    onPressed: () => Navigator.of(context).pop(),
    ),
    TextButton(
    child: Text('Open Settings'),
    onPressed: () async {
    Navigator.of(context).pop();
    await openAppSettings();
    },
    ),
    ],
    ),
    );
    } else {
    // Permission is denied but not permanently. We can request it.
    status = await Permission.photos.request();
    if (status.isGranted) {
    await _proceedWithImagePicking();
    } else {
    print("Photo library permission is denied");
    ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('Permission not granted. Please try again.')),
    );
    }
    }
    } catch (e) {
    print('Error in _pickImage: $e');
    ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('Error selecting image: $e')),
    );
    }
    }

    Future<void> _proceedWithImagePicking() async {
    final ImagePicker _picker = ImagePicker();
    print("Attempting to pick an image");
    final XFile? image = await _picker.pickImage(source: ImageSource.gallery);

    if (image != null) {
    print("Image picked: ${image.path}");
    setState(() {
    _image = File(image.path);
    coverPhotoUrl = image.path;
    });
    print("Cover photo URL set to: $coverPhotoUrl");

    // Show a success message
    ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('Cover photo updated successfully')),
    );
    } else {
    print('No image selected');
    }
    }

    Future<void> _uploadImage() async {
    // Implement image upload functionality here
    }
    }


    and here is the info.plist

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>$(DEVELOPMENT_LANGUAGE)</string>
    <key>CFBundleDisplayName</key>
    <string>Influfit</string>
    <key>CFBundleExecutable</key>
    <string>$(EXECUTABLE_NAME)</string>
    <key>CFBundleIdentifier</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>influfit</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>$(FLUTTER_BUILD_NAME)</string>
    <key>CFBundleSignature</key>
    <string>????</string>
    <key>CFBundleVersion</key>
    <string>$(FLUTTER_BUILD_NUMBER)</string>
    <key>LSRequiresIPhoneOS</key>
    <true/>
    <key>UILaunchStoryboardName</key>
    <string>LaunchScreen</string>
    <key>UIMainStoryboardFile</key>
    <string>Main</string>
    <key>UISupportedInterfaceOrientations</key>
    <array>
    <string>UIInterfaceOrientationPortrait</string>
    <string>UIInterfaceOrientationLandscapeLeft</string>
    <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    <key>UISupportedInterfaceOrientations~ipad</key>
    <array>
    <string>UIInterfaceOrientationPortrait</string>
    <string>UIInterfaceOrientationPortraitUpsideDown</string>
    <string>UIInterfaceOrientationLandscapeLeft</string>
    <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    <key>CADisableMinimumFrameDurationOnPhone</key>
    <true/>
    <key>UIApplicationSupportsIndirectInputEvents</key>
    <true/>
    <key>NSCameraUsageDescription</key>
    <string>Access to your camera is required to capture photos required for loan processing or your profile.</string>
    <key>NSLocationUsageDescription</key>
    <string>To show your location on the map.</string>
    <key>NSPhotoLibraryUsageDescription</key>
    <string>Access to your photo library is required to select photos for your profile or loan processing.</string>
    </dict>
    </plist>


    Any suggestion what I am doing wrong? I leave here a video of the issue https://imgur.com/a/qkL8HKZ

    Continue reading...

Compartilhe esta Página