Flutter

카메라 다루기 2단계

whs5758 2025. 8. 20. 17:10

0

import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:gal/gal.dart';
import 'package:image_picker/image_picker.dart';
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as path;
// path 패키지는 파일 경로를 다루는 유틸리티 패키지

void main() {
  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  File? _selectedImage;
  String statusMessage = "사진을 선택하거나 촬영하세요";
  bool _isLoding = false;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('카메라 앱'),
          backgroundColor: Colors.blue,
          foregroundColor: Colors.white,
        ),
        body: SafeArea(
          child: Column(
            children: [
              Container(
                width: double.infinity,
                color: Colors.grey[300],
                padding: const EdgeInsets.all(16.0),
                child: Text(
                  statusMessage,
                  style: TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                  ),
                  textAlign: TextAlign.center,
                ),
              ),
              Expanded(
                child: Container(
                  width: double.infinity,
                  margin: const EdgeInsets.all(16.0),
                  decoration: BoxDecoration(
                    border: Border.all(color: Colors.grey),
                    borderRadius: BorderRadius.circular(8.0),
                  ),
                  child: _isLoding
                      ? Center(child: CircularProgressIndicator())
                      : _selectedImage != null
                          ? ClipRRect(
                              borderRadius: BorderRadius.circular(8.0),
                              child: Image.file(_selectedImage!,
                                  fit: BoxFit.cover))
                          : Column(
                              mainAxisAlignment: MainAxisAlignment.center,
                              children: [
                                Icon(
                                  Icons.image_outlined,
                                  size: 80,
                                  color: Colors.grey,
                                ),
                                SizedBox(height: 16),
                                Text(
                                  "이미지가 없습니다",
                                  style: TextStyle(
                                    fontSize: 20,
                                    color: Colors.grey,
                                  ),
                                ),
                              ],
                            ),
                ),
              ),
              Container(
                padding: const EdgeInsets.all(16.0),
                child: Wrap(
                  spacing: 12.0,
                  runSpacing: 8.0,
                  children: [
                    ElevatedButton.icon(
                      onPressed: _isLoding ? null : _takePhoto,
                      icon: Icon(Icons.camera_alt),
                      label: const Text("카메라"),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.blue,
                        foregroundColor: Colors.white,
                        padding: EdgeInsets.symmetric(
                          horizontal: 20,
                          vertical: 12,
                        ),
                      ),
                    ),
                    ElevatedButton.icon(
                      onPressed: _isLoding ? null : _pickImageFromGallery,
                      icon: Icon(Icons.photo_library),
                      label: const Text("갤러리"),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.green,
                        foregroundColor: Colors.white,
                        padding: EdgeInsets.symmetric(
                          horizontal: 20,
                          vertical: 12,
                        ),
                      ),
                    ),
                    ElevatedButton.icon(
                      onPressed: (_selectedImage != null && _isLoding == false)
                          ? _saveImageToGallery
                          : null,
                      icon: Icon(Icons.photo_library),
                      label: const Text("저장"),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.orange,
                        foregroundColor: Colors.white,
                        padding: EdgeInsets.symmetric(
                          horizontal: 20,
                          vertical: 12,
                        ),
                      ),
                    ),
                    ElevatedButton.icon(
                      onPressed: (_selectedImage != null && _isLoding == false)
                          ? _saveImageToGallery
                          : null,
                      icon: Icon(Icons.photo_library),
                      label: const Text("서버로 전송"),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.orange,
                        foregroundColor: Colors.white,
                        padding: EdgeInsets.symmetric(
                          horizontal: 20,
                          vertical: 12,
                        ),
                      ),
                    ),
                  ],
                ),
              )
            ],
          ),
        ),
      ),
    );
  }

  // 카메라 사진 촬영
  void _takePhoto() async {
    setState(() {
      _isLoding = true;
      statusMessage = "카메라를 준비중...";
    });

    try {
      // 갤러리 접근 권한 확인 요청
      if (await Gal.hasAccess() == false) {
        await Gal.requestAccess();
      }
      // 카메라로 사진 촬영
      XFile? image = await ImagePicker().pickImage(
        source: ImageSource.camera,
        imageQuality: 85,
      );
      if (image != null) {
        setState(() {
          statusMessage = "카메라로 사진 촬영을 했습니다.";
          _isLoding = false;
          _selectedImage = new File(image.path);
          print("촬영된 사진 임시 경로 : ${image.path}");
        });
      } else {
        setState(() {
          statusMessage = "사진 촬영이 취소되었습니다";
          _isLoding = false;
        });
      }
    } catch (e) {
      setState(() {
        statusMessage = "카메라 사용 중 오류가 발생했습니다";
        _isLoding = false;
        print("카메라 오류 발생 : ${e}");
      });
    }
  }

  // 갤러리에서 이미지 선택
  void _pickImageFromGallery() async {
    setState(() {
      _isLoding = true;
      statusMessage = "갤러리 여는 중...";
    });
    try {
      // 갤러리 접근 권한 확인 요청
      if (await Gal.hasAccess() == false) {
        await Gal.requestAccess();
      }
      // 갤러리에 접근 요청
      XFile? pickedImage = await ImagePicker().pickImage(
        source: ImageSource.gallery,
        imageQuality: 85,
      );
      if (pickedImage != null) {
        setState(() {
          _selectedImage = File(pickedImage.path);
          statusMessage = "갤러리에서 이미지를 선택 했습니다.";
          _isLoding = false;
        });
      } else {
        setState(() {
          statusMessage = "이미지 선택이 취소 되었습니다.";
          _isLoding = false;
        });
      }
    } catch (e) {
      setState(() {
        statusMessage = "갤러리 접근 중 오류 발생";
        _isLoding = false;
      });
      print("갤러리 열기 오류 확인 : ${e}");
    }
  }

  void _saveImageToGallery() async {
    if (_selectedImage == null) return;
    setState(() {
      _isLoding = true;
      statusMessage = "이미지를 갤러리에 저장중...";
    });
    try {
      // 갤러리 접근 권한 확인 요청
      if (await Gal.hasAccess() == false) {
        await Gal.requestAccess();
      }
      // 갤러리에 저장
      Gal.putImage(_selectedImage!.path);
      setState(() {
        _isLoding = false;
        statusMessage = "갤러리에 이미지 저장 완료";
      });

      // 3초 후에 원래 메세지로 복원
      Future.delayed(const Duration(seconds: 3), () {
        // mounted 비동기 작업의 결과를 위젯에 반영하기 전에
        // 위젯이 여전히 유효한 상태인지 확인
        if (mounted) {
          // 이 속성은 위젯 트리에 여전히 연결이 되어 있는지 확인
          // 이유는 dispose 호출된 이후에
          // 개발자가 setState() 메서드를 호출하면 오류가 발생
          setState(() {
            statusMessage = "사진을 선택하거나 촬영하세요";
          });
        }
      });
    } catch (e) {
      setState(() {
        statusMessage = "이미지 저장 중 오류 발생";
        _isLoding = false;
      });
    }
  }

  // 서버로 이미지 전송
  Future<void> _uploadToServer() async {
    // 방어적 코드 작성
    if (_selectedImage == null) return;
    setState(() {
      _isLoding = true;
      statusMessage = "서버로 업로드 중 ...";
    });
    // 서버측으로 통신하는 코드
  }

  @override
  void dispose() {
    super.dispose();
    // 일반적으로 자원 연결 해제 코드 작성해 준다.
  }
}