Created
September 29, 2025 20:55
-
-
Save h0landa/d00388c467efd37b6b55f59efda747c1 to your computer and use it in GitHub Desktop.
Página de Câmera
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'dart:developer'; | |
| import 'package:camera/camera.dart'; | |
| import 'package:flutter/material.dart'; | |
| import 'package:go_router/go_router.dart'; | |
| import 'package:permission_handler/permission_handler.dart'; | |
| class CameraPage extends StatefulWidget { | |
| const CameraPage({super.key}); | |
| @override | |
| State<CameraPage> createState() => _CameraPageState(); | |
| } | |
| IconData getCameraLensIcon(CameraLensDirection direction) { | |
| switch (direction) { | |
| case CameraLensDirection.back: | |
| return Icons.camera_rear; | |
| case CameraLensDirection.front: | |
| return Icons.camera_front; | |
| case CameraLensDirection.external: | |
| return Icons.camera; | |
| } | |
| } | |
| class _CameraPageState extends State<CameraPage> | |
| with WidgetsBindingObserver, TickerProviderStateMixin { | |
| CameraController? _controller; | |
| XFile? arquivoDeImagem; | |
| double _minAvailableZoom = 1.0; | |
| double _maxAvailableZoom = 1.0; | |
| double _baseScale = 1.0; | |
| double _currentScale = 1.0; | |
| Offset? _focusPoint; | |
| late AnimationController _focusController; | |
| int _pointers = 0; | |
| FlashMode _currentFlashMode = FlashMode.off; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| _focusController = AnimationController( | |
| vsync: this, | |
| duration: const Duration(milliseconds: 500), | |
| ); | |
| WidgetsBinding.instance.addObserver(this); | |
| _checkCameraPermission(); | |
| } | |
| void _showPermissionToConfigDialog() { | |
| showDialog( | |
| context: context, | |
| builder: (context) { | |
| return AlertDialog( | |
| title: const Text('Permissão necessária'), | |
| content: const Text( | |
| 'Este aplicativo precisa de acesso à câmera.' | |
| 'Ative a permissão nas configurações do dispositivo.', | |
| ), | |
| actions: [ | |
| TextButton( | |
| onPressed: () { | |
| context.pop(); | |
| context.go('/'); | |
| }, | |
| child: const Text('Cancelar'), | |
| ), | |
| TextButton( | |
| onPressed: () { | |
| openAppSettings(); | |
| context.pop(); | |
| }, | |
| child: const Text('Abrir Configurações'), | |
| ), | |
| ], | |
| ); | |
| }, | |
| ); | |
| } | |
| @override | |
| Future<void> didChangeAppLifecycleState(AppLifecycleState state) async { | |
| final CameraController? cameraController = _controller; | |
| if (cameraController == null || !cameraController.value.isInitialized) { | |
| return; | |
| } | |
| switch (state) { | |
| case AppLifecycleState.resumed: | |
| await cameraController.resumePreview(); | |
| log('RESUME'); | |
| case AppLifecycleState.paused: | |
| case AppLifecycleState.inactive: | |
| case AppLifecycleState.hidden: | |
| await cameraController.pausePreview(); | |
| log('PREVIEW PAUSADO'); | |
| case AppLifecycleState.detached: | |
| log('DETACEHED'); | |
| } | |
| super.didChangeAppLifecycleState(state); | |
| } | |
| Future<void> _checkCameraPermission() async { | |
| final status = await Permission.camera.request(); | |
| if (status.isGranted) { | |
| await _initializeBackCameraController(); | |
| } | |
| if (status.isDenied) { | |
| if (mounted) context.pop(); | |
| } | |
| if (status.isPermanentlyDenied) { | |
| _showPermissionToConfigDialog(); | |
| } | |
| } | |
| @override | |
| void dispose() { | |
| log('CAMERA FECHADA'); | |
| arquivoDeImagem = null; | |
| _focusController.dispose(); | |
| _controller?.dispose(); | |
| _controller = null; | |
| WidgetsBinding.instance.removeObserver(this); | |
| super.dispose(); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return Scaffold( | |
| backgroundColor: Colors.black, | |
| body: Center( | |
| child: Stack( | |
| children: [ | |
| _cameraPreviewWidget(), | |
| if (_focusPoint != null) _buildFocusIndicator(), | |
| Positioned( | |
| top: MediaQuery.of(context).padding.top - 20, | |
| left: 16, | |
| right: 16, | |
| child: Row( | |
| mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
| children: [ | |
| Container( | |
| decoration: BoxDecoration( | |
| color: const Color.fromARGB(241, 0, 0, 0), | |
| shape: BoxShape.circle, | |
| ), | |
| child: IconButton( | |
| onPressed: () => context.go('/'), | |
| icon: const Icon(Icons.close, color: Colors.white), | |
| ), | |
| ), | |
| _buildFlashButton(), | |
| ], | |
| ), | |
| ), | |
| Positioned( | |
| bottom: MediaQuery.of(context).padding.bottom + 16, | |
| left: 0, | |
| right: 0, | |
| child: Column( | |
| children: [ | |
| Row( | |
| mainAxisAlignment: MainAxisAlignment.center, | |
| children: [ | |
| const Spacer(flex: 2), | |
| GestureDetector( | |
| onTap: takePicture, | |
| child: Container( | |
| width: 80, | |
| height: 80, | |
| decoration: BoxDecoration( | |
| shape: BoxShape.circle, | |
| border: Border.all(color: Colors.white, width: 4), | |
| ), | |
| child: Container( | |
| margin: const EdgeInsets.all(8), | |
| decoration: const BoxDecoration( | |
| color: Colors.white, | |
| shape: BoxShape.circle, | |
| ), | |
| ), | |
| ), | |
| ), | |
| const Spacer(flex: 1), | |
| Padding( | |
| padding: const EdgeInsets.only(right: 16), | |
| child: _buildZoomButton( | |
| '$_currentScale', | |
| _currentScale, | |
| _currentScale, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ], | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ); | |
| } | |
| Widget _cameraPreviewWidget() { | |
| final CameraController? cameraController = _controller; | |
| if (cameraController == null || !cameraController.value.isInitialized) { | |
| return const SizedBox(); | |
| } else { | |
| return Listener( | |
| onPointerDown: (_) => _pointers++, | |
| onPointerUp: (_) => _pointers--, | |
| child: CameraPreview( | |
| _controller!, | |
| child: LayoutBuilder( | |
| builder: (BuildContext context, BoxConstraints constraints) { | |
| return GestureDetector( | |
| behavior: HitTestBehavior.opaque, | |
| onScaleStart: _handleScaleStart, | |
| onScaleUpdate: _handleScaleUpdate, | |
| onTapDown: onFocusPoint, | |
| ); | |
| }, | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| Future<void> onFocusPoint(TapDownDetails details) async { | |
| if (_controller == null || !_controller!.value.isInitialized) { | |
| return; | |
| } | |
| final Offset tapPosition = details.localPosition; | |
| setState(() { | |
| _focusPoint = tapPosition; | |
| }); | |
| final Size? previewSize = _controller?.value.previewSize; | |
| if (previewSize == null) return; | |
| final Offset normalizedPoint = Offset( | |
| tapPosition.dx / previewSize.width, | |
| tapPosition.dy / previewSize.height, | |
| ); | |
| try { | |
| await _controller!.setFocusPoint(normalizedPoint); | |
| await _controller!.setFocusMode(FocusMode.auto); | |
| } on CameraException catch (e) { | |
| _showCameraException(e); | |
| } | |
| } | |
| Widget _buildFocusIndicator() { | |
| if (_focusPoint == null) { | |
| return const SizedBox.shrink(); | |
| } | |
| final animation = Tween<double>( | |
| begin: 1.0, | |
| end: 0.0, | |
| ).animate(_focusController); | |
| return AnimatedBuilder( | |
| animation: _focusController, | |
| builder: (context, child) { | |
| if (!_focusController.isAnimating && _focusController.value == 0.0) { | |
| return const SizedBox.shrink(); | |
| } | |
| return Positioned( | |
| left: _focusPoint!.dx - 25, | |
| top: _focusPoint!.dy - 25, | |
| child: FadeTransition( | |
| opacity: animation, | |
| child: Container( | |
| width: 50, | |
| height: 50, | |
| decoration: BoxDecoration( | |
| border: Border.all(color: Colors.yellow, width: 2), | |
| borderRadius: BorderRadius.circular(5), | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| ); | |
| } | |
| void _handleScaleStart(ScaleStartDetails details) { | |
| _baseScale = _currentScale; | |
| } | |
| Future<void> _handleScaleUpdate(ScaleUpdateDetails details) async { | |
| if (_controller == null) { | |
| return; | |
| } | |
| _currentScale = (_baseScale * details.scale).clamp( | |
| _minAvailableZoom, | |
| _maxAvailableZoom, | |
| ); | |
| await _controller!.setZoomLevel(_currentScale); | |
| setState(() { | |
| }); | |
| } | |
| String timestamp() => DateTime.now().millisecondsSinceEpoch.toString(); | |
| void showInSnackBar(String message) { | |
| ScaffoldMessenger.of( | |
| context, | |
| ).showSnackBar(SnackBar(content: Text(message))); | |
| } | |
| Future<void> _initializeBackCameraController() async { | |
| final cameras = await availableCameras(); | |
| final backCamera = cameras.firstWhere( | |
| (c) => c.lensDirection == CameraLensDirection.back, | |
| orElse: () => cameras.first, | |
| ); | |
| log('CAMERA INICIADA'); | |
| final CameraController cameraController = CameraController( | |
| backCamera, | |
| ResolutionPreset.medium, | |
| imageFormatGroup: ImageFormatGroup.jpeg, | |
| enableAudio: false, | |
| ); | |
| _controller?.dispose(); | |
| _controller = cameraController; | |
| cameraController.addListener(() { | |
| if (mounted) { | |
| setState(() {}); | |
| } | |
| if (cameraController.value.hasError) { | |
| showInSnackBar( | |
| 'Erro de câmera: ${cameraController.value.errorDescription}', | |
| ); | |
| } | |
| }); | |
| try { | |
| await cameraController.initialize(); | |
| await Future.wait(<Future<void>>[ | |
| cameraController.getMaxZoomLevel().then( | |
| (double value) => _maxAvailableZoom = value, | |
| ), | |
| cameraController.getMinZoomLevel().then( | |
| (double value) => _minAvailableZoom = value, | |
| ), | |
| ]); | |
| await cameraController.setFlashMode(FlashMode.off); | |
| _currentFlashMode = FlashMode.off; | |
| } on CameraException catch (e) { | |
| switch (e.code) { | |
| case 'CameraAccessDenied': | |
| showInSnackBar('Acesso de câmera negado.'); | |
| break; | |
| case 'CameraAccessDeniedWithoutPrompt': | |
| showInSnackBar('Ative o acesso a câmera nas configurações'); | |
| break; | |
| default: | |
| _showCameraException(e); | |
| } | |
| } | |
| if (mounted) { | |
| setState(() {}); | |
| } | |
| } | |
| void onTakePictureButtonPressed() { | |
| takePicture().then((XFile? file) { | |
| if (mounted) { | |
| setState(() { | |
| arquivoDeImagem = file; | |
| }); | |
| if (file != null) { | |
| showInSnackBar('Foto salva em ${file.path}'); | |
| } | |
| } | |
| }); | |
| } | |
| FlashMode _getNextFlashMode(FlashMode current) { | |
| switch (current) { | |
| case FlashMode.off: | |
| return FlashMode.auto; | |
| case FlashMode.auto: | |
| return FlashMode.always; | |
| case FlashMode.always: | |
| return FlashMode.off; | |
| case FlashMode.torch: | |
| return FlashMode.off; | |
| } | |
| } | |
| void onFlashModeButtonPressed() async { | |
| if (_controller == null || !_controller!.value.isInitialized) { | |
| return; | |
| } | |
| final nextMode = _getNextFlashMode(_currentFlashMode); | |
| try { | |
| await _controller!.setFlashMode(nextMode); | |
| setState(() { | |
| _currentFlashMode = nextMode; | |
| }); | |
| } on CameraException catch (e) { | |
| _showCameraException(e); | |
| } | |
| } | |
| Future<void> setFlashMode(FlashMode mode) async { | |
| if (_controller == null) { | |
| return; | |
| } | |
| try { | |
| await _controller!.setFlashMode(mode); | |
| } on CameraException catch (e) { | |
| _showCameraException(e); | |
| rethrow; | |
| } | |
| } | |
| Widget _buildFlashButton() { | |
| IconData icon; | |
| Color color; | |
| switch (_currentFlashMode) { | |
| case FlashMode.off: | |
| icon = Icons.flash_off; | |
| color = Colors.white; | |
| case FlashMode.auto: | |
| icon = Icons.flash_auto; | |
| color = Colors.lightBlueAccent; | |
| case FlashMode.always: | |
| icon = Icons.flash_on; | |
| color = Colors.yellow; | |
| case FlashMode.torch: | |
| icon = Icons.highlight; | |
| color = Colors.orange; | |
| } | |
| return Container( | |
| decoration: const BoxDecoration( | |
| color: Color.fromARGB(241, 0, 0, 0), | |
| shape: BoxShape.circle, | |
| ), | |
| child: IconButton( | |
| onPressed: onFlashModeButtonPressed, | |
| icon: Icon(icon, color: color), | |
| ), | |
| ); | |
| } | |
| Future<XFile?> takePicture() async { | |
| final CameraController? cameraController = _controller; | |
| if (cameraController == null || !cameraController.value.isInitialized) { | |
| showInSnackBar('Erro: selecione a câmera primeiro.'); | |
| return null; | |
| } | |
| if (cameraController.value.isTakingPicture) { | |
| return null; | |
| } | |
| try { | |
| final XFile file = await cameraController.takePicture(); | |
| log('foto tirada'); | |
| return file; | |
| } on CameraException catch (e) { | |
| _showCameraException(e); | |
| return null; | |
| } | |
| } | |
| void _showCameraException(CameraException e) { | |
| showInSnackBar('Error: ${e.code}\n${e.description}'); | |
| } | |
| Widget _buildZoomButton(String text, double zoomLevel, double currentZoom) { | |
| final bool isActive = (currentZoom - zoomLevel).abs() < 0.1; | |
| return GestureDetector( | |
| onTap: () => _onZoomChanged(zoomLevel), | |
| child: Container( | |
| padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), | |
| decoration: BoxDecoration( | |
| color: isActive ? Colors.yellow : Colors.transparent, | |
| borderRadius: BorderRadius.circular(16), | |
| ), | |
| child: Text( | |
| "${zoomLevel.toStringAsFixed(1)}x", | |
| style: TextStyle( | |
| color: isActive ? Colors.black : Colors.white, | |
| fontWeight: FontWeight.bold, | |
| fontSize: 16, | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| void _onZoomChanged(double value) async { | |
| if (_controller == null) return; | |
| final currentScale = value.clamp(_minAvailableZoom, _maxAvailableZoom); | |
| await _controller!.setZoomLevel(currentScale); | |
| setState(() { | |
| }); | |
| } | |
| } |
carlosdesenvolvedor
commented
Sep 30, 2025
teste os dois, esse mudei somente como te falei lá no discord A correção removeu um vazamento de memória ao transformar o método dispose() de assíncrono para síncrono (@OverRide void dispose()). Essa mudança fundamental permite que o Flutter descarte a tela imediatamente. A liberação do controlador de câmera (_controller?.dispose()) agora ocorre em segundo plano (sem await), garantindo que o widget não fique retido na memória esperando a conclusão dessa tarefa demorada, eliminando o acúmulo de recursos como o CameraXProxy. Além disso, foi adicionada a remoção explícita do listener para evitar referências circulares.
import 'dart:developer';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:permission_handler/permission_handler.dart';
class CameraPage extends StatefulWidget {
const CameraPage({super.key});
@override
State<CameraPage> createState() => _CameraPageState();
}
IconData getCameraLensIcon(CameraLensDirection direction) {
switch (direction) {
case CameraLensDirection.back:
return Icons.camera_rear;
case CameraLensDirection.front:
return Icons.camera_front;
case CameraLensDirection.external:
return Icons.camera;
}
}
class _CameraPageState extends State<CameraPage>
with WidgetsBindingObserver, TickerProviderStateMixin {
CameraController? _controller;
XFile? arquivoDeImagem;
double _minAvailableZoom = 1.0;
double _maxAvailableZoom = 1.0;
double _baseScale = 1.0;
double _currentScale = 1.0;
Offset? _focusPoint;
late AnimationController _focusController;
int _pointers = 0;
FlashMode _currentFlashMode = FlashMode.off;
@override
void initState() {
super.initState();
_focusController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
WidgetsBinding.instance.addObserver(this);
_checkCameraPermission();
}
void _showPermissionToConfigDialog() {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Permissão necessária'),
content: const Text(
'Este aplicativo precisa de acesso à câmera.'
'Ative a permissão nas configurações do dispositivo.',
),
actions: [
TextButton(
onPressed: () {
context.pop();
context.go('/');
},
child: const Text('Cancelar'),
),
TextButton(
onPressed: () {
openAppSettings();
context.pop();
},
child: const Text('Abrir Configurações'),
),
],
);
},
);
}
@override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
final CameraController? cameraController = _controller;
if (cameraController == null || !cameraController.value.isInitialized) {
return;
}
switch (state) {
case AppLifecycleState.resumed:
await cameraController.resumePreview();
log('RESUME');
case AppLifecycleState.paused:
case AppLifecycleState.inactive:
case AppLifecycleState.hidden:
await cameraController.pausePreview();
log('PREVIEW PAUSADO');
case AppLifecycleState.detached:
log('DETACEHED');
}
super.didChangeAppLifecycleState(state);
}
Future<void> _checkCameraPermission() async {
final status = await Permission.camera.request();
if (status.isGranted) {
await _initializeBackCameraController();
}
if (status.isDenied) {
if (mounted) context.pop();
}
if (status.isPermanentlyDenied) {
_showPermissionToConfigDialog();
}
}
@override
void dispose() {
log('CAMERA FECHADA - DISPOSE SYNC');
// 1. Liberação síncrona de recursos
_focusController.dispose();
WidgetsBinding.instance.removeObserver(this);
// 2. Remove o listener e chama dispose do controlador (fire-and-forget)
// O controlador será liberado em segundo plano sem bloquear
_controller?.removeListener(_cameraListener);
_controller?.dispose();
_controller = null; // Zera a referência
arquivoDeImagem = null;
// 3. SEMPRE CHAME super.dispose() NO FINAL
super.dispose();
}
// Listener extraído para poder ser removido no dispose.
void _cameraListener() {
if (mounted && _controller != null && !_controller!.value.hasError) {
setState(() {});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: Stack(
children: [
_cameraPreviewWidget(),
if (_focusPoint != null) _buildFocusIndicator(),
Positioned(
top: MediaQuery.of(context).padding.top - 20,
left: 16,
right: 16,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
decoration: BoxDecoration(
color: const Color.fromARGB(241, 0, 0, 0),
shape: BoxShape.circle,
),
child: IconButton(
onPressed: () => context.go('/'),
icon: const Icon(Icons.close, color: Colors.white),
),
),
_buildFlashButton(),
],
),
),
Positioned(
bottom: MediaQuery.of(context).padding.bottom + 16,
left: 0,
right: 0,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Spacer(flex: 2),
GestureDetector(
onTap: takePicture,
child: Container(
width: 80,
height: 80,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 4),
),
child: Container(
margin: const EdgeInsets.all(8),
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
),
),
),
const Spacer(flex: 1),
Padding(
padding: const EdgeInsets.only(right: 16),
child: _buildZoomButton(
'$_currentScale',
_currentScale,
_currentScale,
),
),
],
),
],
),
),
],
),
),
);
}
Widget _cameraPreviewWidget() {
final CameraController? cameraController = _controller;
if (cameraController == null || !cameraController.value.isInitialized) {
return const SizedBox();
} else {
return Listener(
onPointerDown: (_) => _pointers++,
onPointerUp: (_) => _pointers--,
child: CameraPreview(
_controller!,
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onScaleStart: _handleScaleStart,
onScaleUpdate: _handleScaleUpdate,
onTapDown: onFocusPoint,
);
},
),
),
);
}
}
Future<void> onFocusPoint(TapDownDetails details) async {
if (_controller == null || !_controller!.value.isInitialized) {
return;
}
final Offset tapPosition = details.localPosition;
setState(() {
_focusPoint = tapPosition;
});
final Size? previewSize = _controller?.value.previewSize;
if (previewSize == null) return;
final Offset normalizedPoint = Offset(
tapPosition.dx / previewSize.width,
tapPosition.dy / previewSize.height,
);
try {
await _controller!.setFocusPoint(normalizedPoint);
await _controller!.setFocusMode(FocusMode.auto);
} on CameraException catch (e) {
_showCameraException(e);
}
}
Widget _buildFocusIndicator() {
if (_focusPoint == null) {
return const SizedBox.shrink();
}
final animation = Tween<double>(
begin: 1.0,
end: 0.0,
).animate(_focusController);
return AnimatedBuilder(
animation: _focusController,
builder: (context, child) {
if (!_focusController.isAnimating && _focusController.value == 0.0) {
return const SizedBox.shrink();
}
return Positioned(
left: _focusPoint!.dx - 25,
top: _focusPoint!.dy - 25,
child: FadeTransition(
opacity: animation,
child: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
border: Border.all(color: Colors.yellow, width: 2),
borderRadius: BorderRadius.circular(5),
),
),
),
);
},
);
}
void _handleScaleStart(ScaleStartDetails details) {
_baseScale = _currentScale;
}
Future<void> _handleScaleUpdate(ScaleUpdateDetails details) async {
if (_controller == null) {
return;
}
_currentScale = (_baseScale * details.scale).clamp(
_minAvailableZoom,
_maxAvailableZoom,
);
await _controller!.setZoomLevel(_currentScale);
setState(() {});
}
String timestamp() => DateTime.now().millisecondsSinceEpoch.toString();
void showInSnackBar(String message) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(message)));
}
Future<void> _initializeBackCameraController() async {
final cameras = await availableCameras();
final backCamera = cameras.firstWhere(
(c) => c.lensDirection == CameraLensDirection.back,
orElse: () => cameras.first,
);
log('CAMERA INICIADA');
final CameraController cameraController = CameraController(
backCamera,
ResolutionPreset.medium,
imageFormatGroup: ImageFormatGroup.jpeg,
enableAudio: false,
);
_controller?.dispose();
_controller = cameraController;
cameraController.addListener(_cameraListener);
try {
await cameraController.initialize();
await Future.wait(<Future<void>>[
cameraController.getMaxZoomLevel().then(
(double value) => _maxAvailableZoom = value,
),
cameraController.getMinZoomLevel().then(
(double value) => _minAvailableZoom = value,
),
]);
await cameraController.setFlashMode(FlashMode.off);
_currentFlashMode = FlashMode.off;
} on CameraException catch (e) {
switch (e.code) {
case 'CameraAccessDenied':
showInSnackBar('Acesso de câmera negado.');
break;
case 'CameraAccessDeniedWithoutPrompt':
showInSnackBar('Ative o acesso a câmera nas configurações');
break;
default:
_showCameraException(e);
}
}
if (mounted) {
setState(() {});
}
}
void onTakePictureButtonPressed() {
takePicture().then((XFile? file) {
if (mounted) {
setState(() {
arquivoDeImagem = file;
});
if (file != null) {
showInSnackBar('Foto salva em ${file.path}');
}
}
});
}
FlashMode _getNextFlashMode(FlashMode current) {
switch (current) {
case FlashMode.off:
return FlashMode.auto;
case FlashMode.auto:
return FlashMode.always;
case FlashMode.always:
return FlashMode.off;
case FlashMode.torch:
return FlashMode.off;
}
}
void onFlashModeButtonPressed() async {
if (_controller == null || !_controller!.value.isInitialized) {
return;
}
final nextMode = _getNextFlashMode(_currentFlashMode);
try {
await _controller!.setFlashMode(nextMode);
setState(() {
_currentFlashMode = nextMode;
});
} on CameraException catch (e) {
_showCameraException(e);
}
}
Future<void> setFlashMode(FlashMode mode) async {
if (_controller == null) {
return;
}
try {
await _controller!.setFlashMode(mode);
} on CameraException catch (e) {
_showCameraException(e);
rethrow;
}
}
Widget _buildFlashButton() {
IconData icon;
Color color;
switch (_currentFlashMode) {
case FlashMode.off:
icon = Icons.flash_off;
color = Colors.white;
case FlashMode.auto:
icon = Icons.flash_auto;
color = Colors.lightBlueAccent;
case FlashMode.always:
icon = Icons.flash_on;
color = Colors.yellow;
case FlashMode.torch:
icon = Icons.highlight;
color = Colors.orange;
}
return Container(
decoration: const BoxDecoration(
color: Color.fromARGB(241, 0, 0, 0),
shape: BoxShape.circle,
),
child: IconButton(
onPressed: onFlashModeButtonPressed,
icon: Icon(icon, color: color),
),
);
}
Future<XFile?> takePicture() async {
final CameraController? cameraController = _controller;
if (cameraController == null || !cameraController.value.isInitialized) {
showInSnackBar('Erro: selecione a câmera primeiro.');
return null;
}
if (cameraController.value.isTakingPicture) {
return null;
}
try {
final XFile file = await cameraController.takePicture();
log('foto tirada');
return file;
} on CameraException catch (e) {
_showCameraException(e);
return null;
}
}
void _showCameraException(CameraException e) {
showInSnackBar('Error: ${e.code}\n${e.description}');
}
Widget _buildZoomButton(String text, double zoomLevel, double currentZoom) {
final bool isActive = (currentZoom - zoomLevel).abs() < 0.1;
return GestureDetector(
onTap: () => _onZoomChanged(zoomLevel),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: isActive ? Colors.yellow : Colors.transparent,
borderRadius: BorderRadius.circular(16),
),
child: Text(
"${zoomLevel.toStringAsFixed(1)}x",
style: TextStyle(
color: isActive ? Colors.black : Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
),
);
}
void _onZoomChanged(double value) async {
if (_controller == null) return;
final currentScale = value.clamp(_minAvailableZoom, _maxAvailableZoom);
await _controller!.setZoomLevel(currentScale);
setState(() {});
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment