pic_translator 1.0.3+4 copy "pic_translator: ^1.0.3+4" to clipboard
pic_translator: ^1.0.3+4 copied to clipboard

A Flutter plugin for translating text in images from one language to another. It extracts text from images using ML Kit and overlays the translated text on the original image

example/lib/main.dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image_picker/image_picker.dart';
import 'package:pic_translator/pic_translator.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'PicTranslator Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('PicTranslator Demo'),
        centerTitle: true,
      ),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(20.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              // Header with logo or icon
              const Icon(Icons.translate, size: 80, color: Colors.blue),
              const SizedBox(height: 20),

              const Text(
                'Choose a Feature',
                style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 40),

              // Text Extraction Button
              ElevatedButton.icon(
                onPressed: () {
                  Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) => const TextExtractionPage(),
                    ),
                  );
                },
                icon: const Icon(Icons.text_fields),
                label: const Text('Extract Text from Image'),
                style: ElevatedButton.styleFrom(
                  minimumSize: const Size.fromHeight(50),
                  padding:
                      const EdgeInsets.symmetric(vertical: 12, horizontal: 24),
                ),
              ),

              const SizedBox(height: 16),

              // Text Translation Button
              ElevatedButton.icon(
                onPressed: () {
                  Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) => const ImageTranslationPage(),
                    ),
                  );
                },
                icon: const Icon(Icons.translate),
                label: const Text('Translate Text in Image'),
                style: ElevatedButton.styleFrom(
                  minimumSize: const Size.fromHeight(50),
                  padding:
                      const EdgeInsets.symmetric(vertical: 12, horizontal: 24),
                ),
              ),

              const SizedBox(height: 16),

              // Direct Static Methods Demo Button
              ElevatedButton.icon(
                onPressed: () {
                  Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) => const StaticMethodsPage(),
                    ),
                  );
                },
                icon: const Icon(Icons.speed),
                label: const Text('Quick Functions Demo'),
                style: ElevatedButton.styleFrom(
                  minimumSize: const Size.fromHeight(50),
                  padding:
                      const EdgeInsets.symmetric(vertical: 12, horizontal: 24),
                  backgroundColor: Colors.green.shade700,
                  foregroundColor: Colors.white,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

//---------------------------- TEXT EXTRACTION PAGE ----------------------------//

class TextExtractionPage extends StatefulWidget {
  const TextExtractionPage({Key? key}) : super(key: key);

  @override
  State<TextExtractionPage> createState() => _TextExtractionPageState();
}

class _TextExtractionPageState extends State<TextExtractionPage> {
  final PicTranslator _translator = PicTranslator();
  final ImagePicker _picker = ImagePicker();

  File? _selectedImage;
  String _extractedText = '';
  bool _isProcessing = false;

  @override
  void dispose() {
    _translator.dispose();
    super.dispose();
  }

  Future<void> _pickImage(ImageSource source) async {
    try {
      final XFile? pickedFile = await _picker.pickImage(source: source);

      if (pickedFile == null) return;

      setState(() {
        _selectedImage = File(pickedFile.path);
        _extractedText = '';
        _isProcessing = true;
      });

      await _extractText();
    } catch (e) {
      _showSnackBar('Error picking image: $e');
      setState(() {
        _isProcessing = false;
      });
    }
  }

  Future<void> _extractText() async {
    if (_selectedImage == null) return;

    try {
      // Extract text from the selected image
      final recognizedText =
          await _translator.extractTextFromImage(_selectedImage!);

      setState(() {
        _extractedText = recognizedText.text.isEmpty
            ? 'No text detected in the image'
            : recognizedText.text;
        _isProcessing = false;
      });
    } catch (e) {
      _showSnackBar('Error extracting text: $e');
      setState(() {
        _extractedText = 'Error: $e';
        _isProcessing = false;
      });
    }
  }

  void _copyToClipboard() {
    if (_extractedText.isEmpty ||
        _extractedText == 'No text detected in the image') {
      _showSnackBar('No text to copy');
      return;
    }

    Clipboard.setData(ClipboardData(text: _extractedText));
    _showSnackBar('Text copied to clipboard');
  }

  void _showSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Extract Text from Image'),
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Image selection buttons
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                Expanded(
                  child: ElevatedButton.icon(
                    onPressed: _isProcessing
                        ? null
                        : () => _pickImage(ImageSource.camera),
                    icon: const Icon(Icons.camera_alt),
                    label: const Text('Camera'),
                    style: ElevatedButton.styleFrom(
                      padding: const EdgeInsets.symmetric(vertical: 12),
                    ),
                  ),
                ),
                const SizedBox(width: 16),
                Expanded(
                  child: ElevatedButton.icon(
                    onPressed: _isProcessing
                        ? null
                        : () => _pickImage(ImageSource.gallery),
                    icon: const Icon(Icons.photo_library),
                    label: const Text('Gallery'),
                    style: ElevatedButton.styleFrom(
                      padding: const EdgeInsets.symmetric(vertical: 12),
                    ),
                  ),
                ),
              ],
            ),

            const SizedBox(height: 24),

            // Image preview
            Container(
              height: 250,
              decoration: BoxDecoration(
                border: Border.all(color: Colors.grey),
                borderRadius: BorderRadius.circular(8),
              ),
              child: _isProcessing
                  ? const Center(
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          CircularProgressIndicator(),
                          SizedBox(height: 16),
                          Text('Processing image...'),
                        ],
                      ),
                    )
                  : _selectedImage != null
                      ? ClipRRect(
                          borderRadius: BorderRadius.circular(8),
                          child: Image.file(
                            _selectedImage!,
                            fit: BoxFit.contain,
                          ),
                        )
                      : const Center(
                          child: Text('No image selected'),
                        ),
            ),

            const SizedBox(height: 24),

            // Extracted text section
            Card(
              elevation: 2,
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        const Text(
                          'Extracted Text:',
                          style: TextStyle(
                            fontWeight: FontWeight.bold,
                            fontSize: 16,
                          ),
                        ),
                        if (_extractedText.isNotEmpty &&
                            _extractedText != 'No text detected in the image')
                          IconButton(
                            icon: const Icon(Icons.copy),
                            tooltip: 'Copy to clipboard',
                            onPressed: _copyToClipboard,
                          ),
                      ],
                    ),
                    const Divider(),
                    Container(
                      width: double.infinity,
                      padding: const EdgeInsets.all(12),
                      decoration: BoxDecoration(
                        color: Colors.grey.shade100,
                        borderRadius: BorderRadius.circular(8),
                      ),
                      child: Text(
                        _extractedText.isEmpty
                            ? 'Extract text from an image by taking a photo or selecting from gallery'
                            : _extractedText,
                        style: const TextStyle(fontSize: 16),
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

//---------------------------- IMAGE TRANSLATION PAGE ----------------------------//

class ImageTranslationPage extends StatefulWidget {
  const ImageTranslationPage({Key? key}) : super(key: key);

  @override
  State<ImageTranslationPage> createState() => _ImageTranslationPageState();
}

class _ImageTranslationPageState extends State<ImageTranslationPage> {
  PicTranslator _translator = PicTranslator();
  final ImagePicker _picker = ImagePicker();

  String _targetLanguage = 'en';
  File? _selectedImage;
  File? _translatedImage;
  String _extractedText = '';
  String _translatedText = '';
  bool _isProcessing = false;

  @override
  void dispose() {
    _translator.dispose();
    _cleanupFiles();
    super.dispose();
  }

  void _cleanupFiles() {
    try {
      if (_translatedImage != null && _translatedImage!.existsSync()) {
        _translatedImage!.deleteSync();
      }
    } catch (e) {
      print('Error cleaning up files: $e');
    }
  }

  void _showZoomableImage(File imageFile) {
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) => ZoomableImageView(imageFile: imageFile),
      ),
    );
  }

  Future<void> _getImageFromSource(ImageSource source) async {
    _cleanupFiles();

    try {
      final XFile? pickedImage = await _picker.pickImage(source: source);
      if (pickedImage == null) return;

      setState(() {
        _selectedImage = File(pickedImage.path);
        _translatedImage = null;
        _extractedText = '';
        _translatedText = '';
        _isProcessing = true;
      });

      await _processImage();
    } catch (e) {
      _showSnackBar('Error selecting image: $e');
      setState(() {
        _isProcessing = false;
      });
    }
  }

  Future<void> _changeLanguage(String language) async {
    if (_targetLanguage == language) return;

    if (_translatedImage != null && _translatedImage!.existsSync()) {
      try {
        _translatedImage!.deleteSync();
      } catch (e) {
        print('Error deleting previous translation: $e');
      }
    }

    setState(() {
      _targetLanguage = language;
      if (_selectedImage != null) {
        _isProcessing = true;
        _translatedImage = null;
        _translatedText = '';
      }
    });

    if (_selectedImage != null) {
      _translator.dispose();
      _translator = PicTranslator();
      await _processImage();
    }
  }

  Future<void> _processImage() async {
    if (_selectedImage == null) {
      setState(() {
        _isProcessing = false;
      });
      return;
    }

    try {
      // Use the processImage method to handle the full workflow
      final result = await _translator.processImage(
        imageFile: _selectedImage!,
        targetLanguage: _targetLanguage,
      );

      setState(() {
        _extractedText = result.originalText;
        _translatedText = result.translatedText;
        _translatedImage = result.translatedImageFile;
        _isProcessing = false;
      });
    } catch (e) {
      _showSnackBar('Error processing image: $e');
      setState(() {
        _isProcessing = false;
      });
    }
  }

  Future<void> _shareTranslatedImage() async {
    if (_translatedImage == null || !_translatedImage!.existsSync()) {
      _showSnackBar('No translated image to share');
      return;
    }

    try {
      await Share.shareXFiles(
        [XFile(_translatedImage!.path)],
        text: 'Translated Image',
      );
    } catch (e) {
      _showSnackBar('Error sharing image: $e');
    }
  }

  Future<void> _saveTranslatedImage() async {
    if (_translatedImage == null || !_translatedImage!.existsSync()) {
      _showSnackBar('No translated image to save');
      return;
    }

    try {
      final directory = await getApplicationDocumentsDirectory();
      final savedPath =
          '${directory.path}/translated_image_${DateTime.now().millisecondsSinceEpoch}.jpg';

      await _translatedImage!.copy(savedPath);

      _showSnackBar('Image saved to: $savedPath');
    } catch (e) {
      _showSnackBar('Error saving image: $e');
    }
  }

  void _showSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Translate Text in Image'),
      ),
      body: SingleChildScrollView(
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              // Language selector
              Row(
                children: [
                  const Text(
                    'Target Language:',
                    style: TextStyle(fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(width: 8),
                  Expanded(
                    child: DropdownButton<String>(
                      isExpanded: true,
                      value: _targetLanguage,
                      onChanged: (value) {
                        if (value != null) {
                          _changeLanguage(value);
                        }
                      },
                      items:
                          PicTranslator.availableLanguages.entries.map((entry) {
                        return DropdownMenuItem<String>(
                          value: entry.key,
                          child: Text(entry.value),
                        );
                      }).toList(),
                    ),
                  ),
                ],
              ),

              const SizedBox(height: 24),

              // Image selection buttons
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  Expanded(
                    child: ElevatedButton.icon(
                      onPressed: _isProcessing
                          ? null
                          : () => _getImageFromSource(ImageSource.camera),
                      icon: const Icon(Icons.camera_alt),
                      label: const Text('Camera'),
                      style: ElevatedButton.styleFrom(
                        padding: const EdgeInsets.symmetric(vertical: 12),
                      ),
                    ),
                  ),
                  const SizedBox(width: 16),
                  Expanded(
                    child: ElevatedButton.icon(
                      onPressed: _isProcessing
                          ? null
                          : () => _getImageFromSource(ImageSource.gallery),
                      icon: const Icon(Icons.photo_library),
                      label: const Text('Gallery'),
                      style: ElevatedButton.styleFrom(
                        padding: const EdgeInsets.symmetric(vertical: 12),
                      ),
                    ),
                  ),
                ],
              ),

              const SizedBox(height: 24),

              // Processing indicator
              if (_isProcessing)
                const Center(
                  child: Column(
                    children: [
                      CircularProgressIndicator(),
                      SizedBox(height: 8),
                      Text('Processing image...'),
                    ],
                  ),
                ),

              // Original image
              Container(
                height: 200,
                margin: const EdgeInsets.only(bottom: 16),
                decoration: BoxDecoration(
                  border: Border.all(color: Colors.grey),
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Padding(
                      padding: EdgeInsets.all(8.0),
                      child: Text(
                        'Original Image:',
                        style: TextStyle(fontWeight: FontWeight.bold),
                      ),
                    ),
                    Expanded(
                      child: _selectedImage != null
                          ? ClipRRect(
                              borderRadius: const BorderRadius.only(
                                bottomLeft: Radius.circular(8),
                                bottomRight: Radius.circular(8),
                              ),
                              child: Image.file(
                                _selectedImage!,
                                width: double.infinity,
                                fit: BoxFit.contain,
                              ),
                            )
                          : const Center(
                              child: Text('No image selected'),
                            ),
                    ),
                  ],
                ),
              ),

              // Translated image
              Container(
                height: 200,
                margin: const EdgeInsets.only(bottom: 16),
                decoration: BoxDecoration(
                  border: Border.all(color: Colors.blue),
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: Row(
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: [
                          const Text(
                            'Translated Image:',
                            style: TextStyle(fontWeight: FontWeight.bold),
                          ),
                          if (_translatedImage != null)
                            Row(
                              children: [
                                IconButton(
                                  icon: const Icon(Icons.zoom_in, size: 20),
                                  tooltip: 'Zoom',
                                  onPressed: () {
                                    _showZoomableImage(_translatedImage!);
                                  },
                                ),
                                IconButton(
                                  icon: const Icon(Icons.share, size: 20),
                                  tooltip: 'Share',
                                  onPressed: _shareTranslatedImage,
                                ),
                                IconButton(
                                  icon: const Icon(Icons.save_alt, size: 20),
                                  tooltip: 'Save',
                                  onPressed: _saveTranslatedImage,
                                ),
                              ],
                            ),
                        ],
                      ),
                    ),
                    Expanded(
                      child: _isProcessing
                          ? const Center(
                              child: CircularProgressIndicator(),
                            )
                          : _translatedImage != null
                              ? GestureDetector(
                                  onTap: () {
                                    _showZoomableImage(_translatedImage!);
                                  },
                                  child: ClipRRect(
                                    borderRadius: const BorderRadius.only(
                                      bottomLeft: Radius.circular(8),
                                      bottomRight: Radius.circular(8),
                                    ),
                                    child: Image.file(
                                      _translatedImage!,
                                      width: double.infinity,
                                      fit: BoxFit.contain,
                                    ),
                                  ),
                                )
                              : const Center(
                                  child: Text('No translation available'),
                                ),
                    ),
                  ],
                ),
              ),

              // Text details expander
              if (_extractedText.isNotEmpty || _translatedText.isNotEmpty)
                Card(
                  elevation: 2,
                  child: ExpansionTile(
                    title: const Text(
                      'Text Details',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                    children: [
                      Padding(
                        padding: const EdgeInsets.all(16.0),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Row(
                              mainAxisAlignment: MainAxisAlignment.spaceBetween,
                              children: [
                                const Text(
                                  'Extracted Text:',
                                  style: TextStyle(
                                    fontWeight: FontWeight.bold,
                                    fontSize: 16,
                                  ),
                                ),
                                if (_extractedText.isNotEmpty &&
                                    _extractedText != 'No text detected')
                                  IconButton(
                                    icon: const Icon(Icons.copy, size: 20),
                                    tooltip: 'Copy',
                                    onPressed: () {
                                      Clipboard.setData(
                                          ClipboardData(text: _extractedText));
                                      _showSnackBar(
                                          'Original text copied to clipboard');
                                    },
                                  ),
                              ],
                            ),
                            const SizedBox(height: 8),
                            Container(
                              width: double.infinity,
                              padding: const EdgeInsets.all(12),
                              decoration: BoxDecoration(
                                color: Colors.grey.shade100,
                                borderRadius: BorderRadius.circular(8),
                              ),
                              child: Text(_extractedText.isEmpty
                                  ? 'No text detected'
                                  : _extractedText),
                            ),
                            const SizedBox(height: 16),
                            Row(
                              mainAxisAlignment: MainAxisAlignment.spaceBetween,
                              children: [
                                const Text(
                                  'Translated Text:',
                                  style: TextStyle(
                                    fontWeight: FontWeight.bold,
                                    fontSize: 16,
                                  ),
                                ),
                                if (_translatedText.isNotEmpty &&
                                    _translatedText !=
                                        'No translation available')
                                  IconButton(
                                    icon: const Icon(Icons.copy, size: 20),
                                    tooltip: 'Copy',
                                    onPressed: () {
                                      Clipboard.setData(
                                          ClipboardData(text: _translatedText));
                                      _showSnackBar(
                                          'Translated text copied to clipboard');
                                    },
                                  ),
                              ],
                            ),
                            const SizedBox(height: 8),
                            Container(
                              width: double.infinity,
                              padding: const EdgeInsets.all(12),
                              decoration: BoxDecoration(
                                color: Colors.blue.shade50,
                                borderRadius: BorderRadius.circular(8),
                              ),
                              child: Text(_translatedText.isEmpty
                                  ? 'No translation available'
                                  : _translatedText),
                            ),
                          ],
                        ),
                      ),
                    ],
                  ),
                ),
            ],
          ),
        ),
      ),
    );
  }
}

//---------------------------- STATIC METHODS DEMO PAGE ----------------------------//

class StaticMethodsPage extends StatefulWidget {
  const StaticMethodsPage({Key? key}) : super(key: key);

  @override
  State<StaticMethodsPage> createState() => _StaticMethodsPageState();
}

class _StaticMethodsPageState extends State<StaticMethodsPage> {
  final ImagePicker _picker = ImagePicker();

  String? _imagePath;
  String? _translatedImagePath;
  String _extractedText = '';
  String _translatedText = '';
  bool _isProcessing = false;
  String _targetLanguage = 'fr'; // Default to French

  void _showSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }

  Future<void> _pickImageAndExtractText() async {
    setState(() => _isProcessing = true);

    try {
      // Pick an image
      final pickedFile = await _picker.pickImage(source: ImageSource.gallery);
      if (pickedFile == null) {
        setState(() => _isProcessing = false);
        return;
      }

      _imagePath = pickedFile.path;

      // Extract text using static method
      final text = await PicTranslator.getTextFromImage(_imagePath!);

      setState(() {
        _extractedText = text.isEmpty ? 'No text detected' : text;
        _isProcessing = false;
      });
    } catch (e) {
      setState(() {
        _isProcessing = false;
      });
      _showSnackBar('Error: $e');
    }
  }

  Future<void> _pickImageAndTranslateText() async {
    setState(() => _isProcessing = true);

    try {
      // Pick an image
      final pickedFile = await _picker.pickImage(source: ImageSource.gallery);
      if (pickedFile == null) {
        setState(() => _isProcessing = false);
        return;
      }

      _imagePath = pickedFile.path;

      // Translate text using static method
      final text = await PicTranslator.translateTextFromImage(
        _imagePath!,
        _targetLanguage,
      );

      setState(() {
        _translatedText = text.isEmpty ? 'No text to translate' : text;
        _isProcessing = false;
      });
    } catch (e) {
      setState(() {
        _isProcessing = false;
      });
      _showSnackBar('Error: $e');
    }
  }

  Future<void> _pickImageAndCreateTranslatedImage() async {
    setState(() => _isProcessing = true);

    try {
      // Pick an image
      final pickedFile = await _picker.pickImage(source: ImageSource.gallery);
      if (pickedFile == null) {
        setState(() => _isProcessing = false);
        return;
      }

      _imagePath = pickedFile.path;

      // Create translated image using static method
      final translatedImagePath =
          await PicTranslator.createTranslatedImageFromPath(
        _imagePath!,
        _targetLanguage,
      );

      setState(() {
        _translatedImagePath = translatedImagePath;
        _isProcessing = false;
      });
    } catch (e) {
      setState(() {
        _isProcessing = false;
      });
      _showSnackBar('Error: $e');
    }
  }

  Future<void> _pickImageAndGetCompleteTranslation() async {
    setState(() => _isProcessing = true);

    try {
      // Pick an image
      final pickedFile = await _picker.pickImage(source: ImageSource.gallery);
      if (pickedFile == null) {
        setState(() => _isProcessing = false);
        return;
      }

      _imagePath = pickedFile.path;

      // Get complete translation results with a single function call
      final result = await PicTranslator.translateImage(
        _imagePath!,
        _targetLanguage,
      );

      setState(() {
        _extractedText = result.originalText;
        _translatedText = result.translatedText;
        _translatedImagePath = result.translatedImagePath;
        _isProcessing = false;
      });
    } catch (e) {
      setState(() {
        _isProcessing = false;
      });
      _showSnackBar('Error: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Quick Functions Demo'),
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Language selector
            Row(
              children: [
                const Text(
                  'Target Language:',
                  style: TextStyle(fontWeight: FontWeight.bold),
                ),
                const SizedBox(width: 8),
                Expanded(
                  child: DropdownButton<String>(
                    isExpanded: true,
                    value: _targetLanguage,
                    onChanged: (value) {
                      if (value != null) {
                        setState(() {
                          _targetLanguage = value;
                        });
                      }
                    },
                    items:
                        PicTranslator.availableLanguages.entries.map((entry) {
                      return DropdownMenuItem<String>(
                        value: entry.key,
                        child: Text(entry.value),
                      );
                    }).toList(),
                  ),
                ),
              ],
            ),

            const SizedBox(height: 24),

            // Individual functions
            const Text(
              'Static Methods:',
              style: TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
              ),
            ),

            const SizedBox(height: 16),

            // Function buttons
            Wrap(
              spacing: 16,
              runSpacing: 16,
              children: [
                ElevatedButton.icon(
                  onPressed: _isProcessing ? null : _pickImageAndExtractText,
                  icon: const Icon(Icons.text_fields),
                  label: const Text('Extract Text\ngetTextFromImage'),
                  style: ElevatedButton.styleFrom(
                    padding: const EdgeInsets.all(12),
                  ),
                ),
                ElevatedButton.icon(
                  onPressed: _isProcessing ? null : _pickImageAndTranslateText,
                  icon: const Icon(Icons.translate),
                  label: const Text('Translate Text\ntranslateTextFromImage'),
                  style: ElevatedButton.styleFrom(
                    padding: const EdgeInsets.all(12),
                  ),
                ),
                ElevatedButton.icon(
                  onPressed:
                      _isProcessing ? null : _pickImageAndCreateTranslatedImage,
                  icon: const Icon(Icons.image),
                  label:
                      const Text('Create Image\ncreateTranslatedImageFromPath'),
                  style: ElevatedButton.styleFrom(
                    padding: const EdgeInsets.all(12),
                  ),
                ),
                ElevatedButton.icon(
                  onPressed: _isProcessing
                      ? null
                      : _pickImageAndGetCompleteTranslation,
                  icon: const Icon(Icons.all_inclusive),
                  label: const Text('Complete Process\ntranslateImage'),
                  style: ElevatedButton.styleFrom(
                    padding: const EdgeInsets.all(12),
                    backgroundColor: Colors.green.shade700,
                    foregroundColor: Colors.white,
                  ),
                ),
              ],
            ),

            const SizedBox(height: 24),

            // Processing indicator
            if (_isProcessing)
              const Center(
                child: Column(
                  children: [
                    CircularProgressIndicator(),
                    SizedBox(height: 8),
                    Text('Processing...'),
                  ],
                ),
              ),

            // Results section
            if (_imagePath != null)
              Card(
                elevation: 2,
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Text(
                        'Results:',
                        style: TextStyle(
                          fontWeight: FontWeight.bold,
                          fontSize: 18,
                        ),
                      ),
                      const Divider(),

                      // Original image
                      if (_imagePath != null)
                        Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            const Text(
                              'Original Image:',
                              style: TextStyle(
                                fontWeight: FontWeight.bold,
                                fontSize: 16,
                              ),
                            ),
                            const SizedBox(height: 8),
                            Container(
                              height: 150,
                              width: double.infinity,
                              decoration: BoxDecoration(
                                border: Border.all(color: Colors.grey),
                                borderRadius: BorderRadius.circular(8),
                              ),
                              child: ClipRRect(
                                borderRadius: BorderRadius.circular(8),
                                child: Image.file(
                                  File(_imagePath!),
                                  fit: BoxFit.contain,
                                ),
                              ),
                            ),
                          ],
                        ),

                      const SizedBox(height: 16),

                      // Extracted text
                      if (_extractedText.isNotEmpty)
                        Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Row(
                              mainAxisAlignment: MainAxisAlignment.spaceBetween,
                              children: [
                                const Text(
                                  'Extracted Text:',
                                  style: TextStyle(
                                    fontWeight: FontWeight.bold,
                                    fontSize: 16,
                                  ),
                                ),
                                if (_extractedText != 'No text detected')
                                  IconButton(
                                    icon: const Icon(Icons.copy, size: 20),
                                    tooltip: 'Copy',
                                    onPressed: () {
                                      Clipboard.setData(
                                          ClipboardData(text: _extractedText));
                                      _showSnackBar('Text copied to clipboard');
                                    },
                                  ),
                              ],
                            ),
                            const SizedBox(height: 4),
                            Container(
                              width: double.infinity,
                              padding: const EdgeInsets.all(12),
                              decoration: BoxDecoration(
                                color: Colors.grey.shade100,
                                borderRadius: BorderRadius.circular(8),
                              ),
                              child: Text(_extractedText),
                            ),
                          ],
                        ),

                      const SizedBox(height: 16),

                      // Translated text
                      if (_translatedText.isNotEmpty)
                        Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Row(
                              mainAxisAlignment: MainAxisAlignment.spaceBetween,
                              children: [
                                const Text(
                                  'Translated Text:',
                                  style: TextStyle(
                                    fontWeight: FontWeight.bold,
                                    fontSize: 16,
                                  ),
                                ),
                                if (_translatedText != 'No text to translate')
                                  IconButton(
                                    icon: const Icon(Icons.copy, size: 20),
                                    tooltip: 'Copy',
                                    onPressed: () {
                                      Clipboard.setData(
                                          ClipboardData(text: _translatedText));
                                      _showSnackBar(
                                          'Translated text copied to clipboard');
                                    },
                                  ),
                              ],
                            ),
                            const SizedBox(height: 4),
                            Container(
                              width: double.infinity,
                              padding: const EdgeInsets.all(12),
                              decoration: BoxDecoration(
                                color: Colors.blue.shade50,
                                borderRadius: BorderRadius.circular(8),
                              ),
                              child: Text(_translatedText),
                            ),
                          ],
                        ),

                      const SizedBox(height: 16),

                      // Translated image
                      if (_translatedImagePath != null)
                        Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Row(
                              mainAxisAlignment: MainAxisAlignment.spaceBetween,
                              children: [
                                const Text(
                                  'Translated Image:',
                                  style: TextStyle(
                                    fontWeight: FontWeight.bold,
                                    fontSize: 16,
                                  ),
                                ),
                                IconButton(
                                  icon: const Icon(Icons.zoom_in, size: 20),
                                  tooltip: 'Zoom',
                                  onPressed: () {
                                    Navigator.of(context).push(
                                      MaterialPageRoute(
                                        builder: (context) => ZoomableImageView(
                                          imageFile:
                                              File(_translatedImagePath!),
                                        ),
                                      ),
                                    );
                                  },
                                ),
                              ],
                            ),
                            const SizedBox(height: 4),
                            Container(
                              height: 150,
                              width: double.infinity,
                              decoration: BoxDecoration(
                                border: Border.all(color: Colors.blue),
                                borderRadius: BorderRadius.circular(8),
                              ),
                              child: GestureDetector(
                                onTap: () {
                                  Navigator.of(context).push(
                                    MaterialPageRoute(
                                      builder: (context) => ZoomableImageView(
                                        imageFile: File(_translatedImagePath!),
                                      ),
                                    ),
                                  );
                                },
                                child: ClipRRect(
                                  borderRadius: BorderRadius.circular(8),
                                  child: Image.file(
                                    File(_translatedImagePath!),
                                    fit: BoxFit.contain,
                                  ),
                                ),
                              ),
                            ),
                          ],
                        ),
                    ],
                  ),
                ),
              ),
          ],
        ),
      ),
    );
  }
}

//---------------------------- ZOOMABLE IMAGE VIEW ----------------------------//

class ZoomableImageView extends StatefulWidget {
  final File imageFile;

  const ZoomableImageView({Key? key, required this.imageFile})
      : super(key: key);

  @override
  State<ZoomableImageView> createState() => _ZoomableImageViewState();
}

class _ZoomableImageViewState extends State<ZoomableImageView> {
  final TransformationController _transformationController =
      TransformationController();

  // For double-tap zoom functionality
  TapDownDetails? _doubleTapDetails;

  @override
  void dispose() {
    _transformationController.dispose();
    super.dispose();
  }

  void _handleDoubleTapDown(TapDownDetails details) {
    _doubleTapDetails = details;
  }

  void _handleDoubleTap() {
    if (_transformationController.value != Matrix4.identity()) {
      // If already zoomed in, zoom out
      _transformationController.value = Matrix4.identity();
    } else {
      // Zoom in to the tapped point
      if (_doubleTapDetails != null) {
        final position = _doubleTapDetails!.localPosition;
        // Zoom factor
        const double scale = 3.0;

        // Create transform matrix for zooming
        final Matrix4 matrix = Matrix4.identity()
          ..translate(-position.dx * (scale - 1), -position.dy * (scale - 1))
          ..scale(scale);

        _transformationController.value = matrix;
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Zoom Image'),
        actions: [
          IconButton(
            icon: const Icon(Icons.zoom_in),
            tooltip: 'Zoom In',
            onPressed: () {
              final Matrix4 matrix = _transformationController.value.clone();
              matrix.scale(1.25);
              _transformationController.value = matrix;
            },
          ),
          IconButton(
            icon: const Icon(Icons.zoom_out),
            tooltip: 'Zoom Out',
            onPressed: () {
              final Matrix4 matrix = _transformationController.value.clone();
              matrix.scale(0.8);
              _transformationController.value = matrix;
            },
          ),
          IconButton(
            icon: const Icon(Icons.refresh),
            tooltip: 'Reset Zoom',
            onPressed: () {
              _transformationController.value = Matrix4.identity();
            },
          ),
        ],
      ),
      body: Center(
        child: GestureDetector(
          onDoubleTapDown: _handleDoubleTapDown,
          onDoubleTap: _handleDoubleTap,
          child: InteractiveViewer(
            transformationController: _transformationController,
            minScale: 0.5,
            maxScale: 5.0,
            boundaryMargin: const EdgeInsets.all(double.infinity),
            child: Image.file(
              widget.imageFile,
              fit: BoxFit.contain,
            ),
          ),
        ),
      ),
    );
  }
}
3
likes
120
points
1
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for translating text in images from one language to another. It extracts text from images using ML Kit and overlays the translated text on the original image

Documentation

API reference

License

MIT (license)

Dependencies

cupertino_icons, flutter, google_fonts, google_ml_kit, image, image_picker, path_provider, permission_handler, share_plus, translator

More

Packages that depend on pic_translator