diff --git a/lib/frontend/widgets/attachment/attachment_sheet.dart b/lib/frontend/widgets/attachment/attachment_sheet.dart index 385f26f..35badb0 100644 --- a/lib/frontend/widgets/attachment/attachment_sheet.dart +++ b/lib/frontend/widgets/attachment/attachment_sheet.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:komet/backend/modules/contacts.dart'; import 'package:komet/core/config/app_frost.dart'; @@ -882,6 +883,8 @@ class _KeepAlivePageState extends State<_KeepAlivePage> } } +enum _CameraAccess { unknown, granted, denied } + class _CameraTile extends StatefulWidget { final VoidCallback onTap; final ColorScheme cs; @@ -893,30 +896,59 @@ class _CameraTile extends StatefulWidget { } class _CameraTileState extends State<_CameraTile> with WidgetsBindingObserver { + static const String _askedKey = 'komet_camera_permission_asked'; + CameraController? _controller; bool _starting = false; + _CameraAccess _access = _CameraAccess.unknown; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); - _maybeStartPreview(); + _resolveAccess(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { - _maybeStartPreview(); + _resolveAccess(); } else if (state == AppLifecycleState.inactive || state == AppLifecycleState.paused) { _stopPreview(); } } - Future _maybeStartPreview() async { - if (_starting || _controller != null) return; + Future _resolveAccess() async { if (!(Platform.isAndroid || Platform.isIOS)) return; - if (!await Permission.camera.status.isGranted) return; + + var status = await Permission.camera.status; + if (status.isDenied) { + final prefs = await SharedPreferences.getInstance(); + if (!(prefs.getBool(_askedKey) ?? false)) { + await prefs.setBool(_askedKey, true); + status = await Permission.camera.request(); + } + } + if (!mounted) return; + + final granted = status.isGranted; + setState( + () => _access = granted ? _CameraAccess.granted : _CameraAccess.denied, + ); + if (granted) _startPreview(); + } + + void _onTap() { + if (_access == _CameraAccess.denied) { + openAppSettings(); + return; + } + widget.onTap(); + } + + Future _startPreview() async { + if (_starting || _controller != null) return; _starting = true; try { final cameras = await availableCameras(); @@ -962,9 +994,11 @@ class _CameraTileState extends State<_CameraTile> with WidgetsBindingObserver { final cs = widget.cs; final controller = _controller; final hasPreview = controller != null && controller.value.isInitialized; + final denied = _access == _CameraAccess.denied; + final l10n = AppLocalizations.of(context)!; return GestureDetector( - onTap: widget.onTap, + onTap: _onTap, child: Stack( fit: StackFit.expand, children: [ @@ -997,14 +1031,19 @@ class _CameraTileState extends State<_CameraTile> with WidgetsBindingObserver { mainAxisSize: MainAxisSize.min, children: [ Icon( - Symbols.photo_camera, + denied + ? Symbols.no_photography + : Symbols.photo_camera, size: 34, color: cs.onSurface, weight: 400, ), const SizedBox(height: 6), Text( - AppLocalizations.of(context)!.attachSheetCamera, + denied + ? l10n.attachSheetCameraAllow + : l10n.attachSheetCamera, + textAlign: TextAlign.center, style: TextStyle( color: cs.onSurfaceVariant, fontSize: 12, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 34db243..ad76b1d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -977,6 +977,7 @@ "attachSheetSettings": "Settings", "attachSheetAddCaptionHint": "Add a caption...", "attachSheetCamera": "Camera", + "attachSheetCameraAllow": "Allow camera", "photoEditorApplyFailed": "Couldn't apply", "photoEditorFlipTooltip": "Flip", "photoEditorRotateTooltip": "Rotate", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 59ce38d..356274a 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -4400,6 +4400,12 @@ abstract class AppLocalizations { /// **'Camera'** String get attachSheetCamera; + /// No description provided for @attachSheetCameraAllow. + /// + /// In en, this message translates to: + /// **'Allow camera'** + String get attachSheetCameraAllow; + /// No description provided for @photoEditorApplyFailed. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index d00aad1..b617d4e 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -2287,6 +2287,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get attachSheetCamera => 'Camera'; + @override + String get attachSheetCameraAllow => 'Allow camera'; + @override String get photoEditorApplyFailed => 'Couldn\'t apply'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 1add0db..14827c9 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -2301,6 +2301,9 @@ class AppLocalizationsRu extends AppLocalizations { @override String get attachSheetCamera => 'Камера'; + @override + String get attachSheetCameraAllow => 'Разрешите камеру'; + @override String get photoEditorApplyFailed => 'Не удалось применить'; diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 6aad57e..b8ac6e9 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -742,6 +742,7 @@ "attachSheetSettings": "Настройки", "attachSheetAddCaptionHint": "Добавить подпись...", "attachSheetCamera": "Камера", + "attachSheetCameraAllow": "Разрешите камеру", "photoEditorApplyFailed": "Не удалось применить", "photoEditorFlipTooltip": "Отразить", "photoEditorRotateTooltip": "Повернуть",