image_marker_viewer 1.0.3 copy "image_marker_viewer: ^1.0.3" to clipboard
image_marker_viewer: ^1.0.3 copied to clipboard

A Flutter package for adding interactive markers on images with custom colors, titles, and notes. Supports web, mobile, and desktop platforms.

example/lib/main.dart

import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:file_picker/file_picker.dart';
import 'package:image_marker_viewer/widgets/image_marker_viewer.dart';
import 'package:image_marker_viewer/controllers/image_marker_controller.dart';
import 'package:image_marker_viewer/models/image_marker.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Image Marker Viewer Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MarkerExample(),
    );
  }
}

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

  @override
  State<MarkerExample> createState() => _MarkerExampleState();
}

class _MarkerExampleState extends State<MarkerExample>
    with SingleTickerProviderStateMixin {
  // Her görsel için ayrı controller tag'leri
  final List<String> _imageTags = [];
  final Map<String, Uint8List> _images = {}; // tag -> image bytes
  final Map<String, String> _imageNames = {}; // tag -> image name
  int _selectedImageIndex = 0;
  ImageMarker? selectedMarker;
  String? _currentImageTag;
  TabController? _tabController;

  @override
  void initState() {
    super.initState();
    _updateTabController();
  }

  @override
  void dispose() {
    _tabController?.dispose();
    super.dispose();
  }

  void _updateTabController() {
    _tabController?.dispose();
    if (_imageTags.isNotEmpty) {
      _tabController = TabController(
        length: _imageTags.length,
        initialIndex: _selectedImageIndex.clamp(0, _imageTags.length - 1),
        vsync: this,
      );
      _tabController!.addListener(() {
        if (!_tabController!.indexIsChanging) {
          setState(() {
            _selectedImageIndex = _tabController!.index;
            _currentImageTag = _imageTags[_selectedImageIndex];
            selectedMarker = null;
          });
        }
      });
    }
  }

  Future<void> pickImage() async {
    final result = await FilePicker.platform.pickFiles(
      type: FileType.image,
      allowMultiple: true,
    );
    if (result != null && result.files.isNotEmpty) {
      setState(() {
        for (var file in result.files) {
          if (file.bytes != null) {
            // Her görsel için benzersiz tag oluştur
            final tag =
                'image_${DateTime.now().millisecondsSinceEpoch}_${_imageTags.length}';
            _imageTags.add(tag);
            _images[tag] = file.bytes!;
            _imageNames[tag] = file.name;

            // Controller'ı tag ile oluştur
            if (!Get.isRegistered<ImageMarkerController>(tag: tag)) {
              Get.put(ImageMarkerController(), tag: tag);
            }
          }
        }
        // Yeni eklenen ilk görseli seç
        if (_imageTags.isNotEmpty) {
          _selectedImageIndex = _imageTags.length - 1;
          _currentImageTag = _imageTags[_selectedImageIndex];
        }
        selectedMarker = null;
        _updateTabController();
      });
    }
  }

  void _onMarkerAdded(ImageMarker marker) {
    setState(() {
      // Marker eklendiğinde UI güncellenir (Obx ile zaten reaktif)
    });
  }

  void _onMarkerSelected(ImageMarker marker) {
    setState(() {
      selectedMarker = marker;
    });
  }

  ImageMarkerController _getCurrentController() {
    if (_currentImageTag == null) {
      return Get.put(ImageMarkerController());
    }
    if (Get.isRegistered<ImageMarkerController>(tag: _currentImageTag)) {
      return Get.find<ImageMarkerController>(tag: _currentImageTag);
    }
    return Get.put(ImageMarkerController(), tag: _currentImageTag);
  }

  @override
  Widget build(BuildContext context) {
    // Mevcut görsel tag'ini güncelle
    if (_imageTags.isNotEmpty && _selectedImageIndex < _imageTags.length) {
      _currentImageTag = _imageTags[_selectedImageIndex];
    } else {
      _currentImageTag = null;
    }

    return Scaffold(
      appBar: AppBar(
        title: Text(_currentImageTag != null
            ? 'Image Marker Viewer - ${_imageNames[_currentImageTag] ?? "Görsel ${_selectedImageIndex + 1}"}'
            : 'Image Marker Viewer'),
        actions: [
          if (_currentImageTag != null)
            IconButton(
              icon: const Icon(Icons.download),
              onPressed: () {
                final controller = _getCurrentController();
                final json = controller.exportToJson();
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(
                    content: Text('JSON export:\n$json'),
                    duration: const Duration(seconds: 5),
                  ),
                );
              },
              tooltip: 'Markerları JSON olarak export et',
            ),
        ],
        bottom: _imageTags.length > 1 && _tabController != null
            ? PreferredSize(
                preferredSize: const Size.fromHeight(48),
                child: Obx(() => TabBar(
                      controller: _tabController,
                      isScrollable: true,
                      tabs: _imageTags.asMap().entries.map((entry) {
                        final index = entry.key;
                        final tag = entry.value;
                        if (Get.isRegistered<ImageMarkerController>(tag: tag)) {
                          final controller =
                              Get.find<ImageMarkerController>(tag: tag);
                          return Tab(
                            text:
                                '${_imageNames[tag] ?? 'Görsel ${index + 1}'} (${controller.markers.length})',
                            icon: const Icon(Icons.image),
                          );
                        } else {
                          return Tab(
                            text: _imageNames[tag] ?? 'Görsel ${index + 1}',
                            icon: const Icon(Icons.image),
                          );
                        }
                      }).toList(),
                    )),
              )
            : null,
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: pickImage,
        child: const Icon(Icons.add_photo_alternate),
        tooltip: 'Görsel Ekle',
      ),
      body: _imageTags.isEmpty
          ? const Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(Icons.image, size: 64, color: Colors.grey),
                  SizedBox(height: 16),
                  Text(
                    'Henüz bir görsel seçilmedi.',
                    style: TextStyle(fontSize: 16, color: Colors.grey),
                  ),
                  SizedBox(height: 8),
                  Text(
                    'Yukarıdaki butona tıklayarak görsel ekleyin',
                    style: TextStyle(fontSize: 14, color: Colors.grey),
                  ),
                ],
              ),
            )
          : Row(
              children: [
                // Sol taraf - Görsel
                Expanded(
                  flex: 2,
                  child: Container(
                    margin: const EdgeInsets.all(16),
                    child: ClipRRect(
                      borderRadius: BorderRadius.circular(12),
                      child: _currentImageTag != null
                          ? ImageMarkerViewer(
                              key: ValueKey(
                                  _currentImageTag), // Her görsel için farklı key
                              image: MemoryImage(_images[_currentImageTag]!),
                              tag: _currentImageTag, // Her görsel için ayrı tag
                              onMarkerAdded: _onMarkerAdded,
                              onMarkerSelected: _onMarkerSelected,
                            )
                          : const Center(child: Text('Görsel yükleniyor...')),
                    ),
                  ),
                ),
                // Sağ taraf - Marker Bilgileri
                Expanded(
                  flex: 1,
                  child: Container(
                    margin: const EdgeInsets.all(16),
                    padding: const EdgeInsets.all(20),
                    decoration: BoxDecoration(
                      color: Colors.grey[50],
                      borderRadius: BorderRadius.circular(12),
                      border: Border.all(color: Colors.grey[300]!),
                    ),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Row(
                          children: [
                            Icon(Icons.info_outline, color: Colors.grey[700]),
                            const SizedBox(width: 8),
                            Text(
                              'Marker Bilgileri',
                              style: Theme.of(context)
                                  .textTheme
                                  .titleLarge
                                  ?.copyWith(
                                    fontWeight: FontWeight.bold,
                                  ),
                            ),
                          ],
                        ),
                        const SizedBox(height: 20),
                        // Seçili Marker Bilgisi
                        if (selectedMarker != null)
                          Expanded(
                            child: SingleChildScrollView(
                              child: Column(
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                  Container(
                                    padding: const EdgeInsets.all(16),
                                    decoration: BoxDecoration(
                                      color: selectedMarker!.color
                                          .withOpacity(0.1),
                                      borderRadius: BorderRadius.circular(8),
                                      border: Border.all(
                                        color: selectedMarker!.color,
                                        width: 2,
                                      ),
                                    ),
                                    child: Column(
                                      crossAxisAlignment:
                                          CrossAxisAlignment.start,
                                      children: [
                                        Row(
                                          children: [
                                            Container(
                                              width: 24,
                                              height: 24,
                                              decoration: BoxDecoration(
                                                color: selectedMarker!.color,
                                                shape: BoxShape.circle,
                                                border: Border.all(
                                                  color: Colors.white,
                                                  width: 2,
                                                ),
                                              ),
                                            ),
                                            const SizedBox(width: 12),
                                            Expanded(
                                              child: Text(
                                                selectedMarker!.markerTitle ??
                                                    'Başlıksız Marker',
                                                style: Theme.of(context)
                                                    .textTheme
                                                    .titleMedium
                                                    ?.copyWith(
                                                      fontWeight:
                                                          FontWeight.bold,
                                                    ),
                                              ),
                                            ),
                                          ],
                                        ),
                                        if (selectedMarker!.markerTitle !=
                                                null &&
                                            selectedMarker!
                                                .markerTitle!.isNotEmpty) ...[
                                          const SizedBox(height: 12),
                                          Row(
                                            crossAxisAlignment:
                                                CrossAxisAlignment.start,
                                            children: [
                                              Icon(
                                                Icons.title,
                                                size: 18,
                                                color: Colors.grey[600],
                                              ),
                                              const SizedBox(width: 8),
                                              Expanded(
                                                child: Text(
                                                  selectedMarker!.markerTitle!,
                                                  style: TextStyle(
                                                    fontSize: 14,
                                                    color: Colors.grey[800],
                                                  ),
                                                ),
                                              ),
                                            ],
                                          ),
                                        ],
                                        if (selectedMarker!.note != null &&
                                            selectedMarker!
                                                .note!.isNotEmpty) ...[
                                          const SizedBox(height: 12),
                                          Row(
                                            crossAxisAlignment:
                                                CrossAxisAlignment.start,
                                            children: [
                                              Icon(
                                                Icons.note,
                                                size: 18,
                                                color: Colors.grey[600],
                                              ),
                                              const SizedBox(width: 8),
                                              Expanded(
                                                child: Text(
                                                  selectedMarker!.note!,
                                                  style: TextStyle(
                                                    fontSize: 14,
                                                    color: Colors.grey[800],
                                                  ),
                                                ),
                                              ),
                                            ],
                                          ),
                                        ],
                                        const SizedBox(height: 12),
                                        Row(
                                          children: [
                                            Icon(
                                              Icons.palette,
                                              size: 18,
                                              color: Colors.grey[600],
                                            ),
                                            const SizedBox(width: 8),
                                            Text(
                                              'Renk: ',
                                              style: TextStyle(
                                                fontSize: 14,
                                                color: Colors.grey[600],
                                              ),
                                            ),
                                            Container(
                                              width: 20,
                                              height: 20,
                                              decoration: BoxDecoration(
                                                color: selectedMarker!.color,
                                                shape: BoxShape.circle,
                                                border: Border.all(
                                                  color: Colors.grey[300]!,
                                                ),
                                              ),
                                            ),
                                            const SizedBox(width: 8),
                                            Text(
                                              '#${selectedMarker!.color.value.toRadixString(16).substring(2).toUpperCase()}',
                                              style: TextStyle(
                                                fontSize: 12,
                                                color: Colors.grey[600],
                                                fontFamily: 'monospace',
                                              ),
                                            ),
                                          ],
                                        ),
                                        const SizedBox(height: 12),
                                        Row(
                                          children: [
                                            Icon(
                                              Icons.location_on,
                                              size: 18,
                                              color: Colors.grey[600],
                                            ),
                                            const SizedBox(width: 8),
                                            Text(
                                              'Pozisyon: ',
                                              style: TextStyle(
                                                fontSize: 14,
                                                color: Colors.grey[600],
                                              ),
                                            ),
                                            Text(
                                              '(${selectedMarker!.x.toStringAsFixed(2)}, ${selectedMarker!.y.toStringAsFixed(2)})',
                                              style: TextStyle(
                                                fontSize: 12,
                                                color: Colors.grey[800],
                                                fontFamily: 'monospace',
                                              ),
                                            ),
                                          ],
                                        ),
                                      ],
                                    ),
                                  ),
                                ],
                              ),
                            ),
                          )
                        else
                          Expanded(
                            child: Center(
                              child: Column(
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: [
                                  Icon(
                                    Icons.touch_app,
                                    size: 64,
                                    color: Colors.grey[400],
                                  ),
                                  const SizedBox(height: 16),
                                  Text(
                                    'Marker\'a tıklayın',
                                    style: TextStyle(
                                      fontSize: 16,
                                      color: Colors.grey[600],
                                    ),
                                  ),
                                  const SizedBox(height: 8),
                                  Text(
                                    'Bilgileri burada görüntüleyin',
                                    style: TextStyle(
                                      fontSize: 14,
                                      color: Colors.grey[500],
                                    ),
                                  ),
                                ],
                              ),
                            ),
                          ),
                        const Divider(),
                        // Tüm Markerlar Listesi
                        Obx(() {
                          final controller = _getCurrentController();
                          final markers = controller.markers;
                          return Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Text(
                                'Tüm Markerlar (${markers.length})',
                                style: Theme.of(context)
                                    .textTheme
                                    .titleSmall
                                    ?.copyWith(
                                      fontWeight: FontWeight.bold,
                                    ),
                              ),
                              const SizedBox(height: 12),
                              if (markers.isEmpty)
                                Padding(
                                  padding: const EdgeInsets.all(8.0),
                                  child: Text(
                                    'Henüz marker eklenmedi',
                                    style: TextStyle(
                                      color: Colors.grey[600],
                                      fontSize: 12,
                                    ),
                                  ),
                                )
                              else
                                SizedBox(
                                  height: 100,
                                  child: ListView.builder(
                                    scrollDirection: Axis.horizontal,
                                    itemCount: markers.length,
                                    itemBuilder: (context, index) {
                                      final marker = markers[index];
                                      return GestureDetector(
                                        onTap: () {
                                          setState(() {
                                            selectedMarker = marker;
                                          });
                                        },
                                        child: Container(
                                          width: 80,
                                          margin:
                                              const EdgeInsets.only(right: 8),
                                          padding: const EdgeInsets.all(12),
                                          decoration: BoxDecoration(
                                            color: marker == selectedMarker
                                                ? marker.color.withOpacity(0.2)
                                                : Colors.white,
                                            borderRadius:
                                                BorderRadius.circular(8),
                                            border: Border.all(
                                              color: marker == selectedMarker
                                                  ? marker.color
                                                  : Colors.grey[300]!,
                                              width: marker == selectedMarker
                                                  ? 2
                                                  : 1,
                                            ),
                                          ),
                                          child: Column(
                                            mainAxisAlignment:
                                                MainAxisAlignment.center,
                                            children: [
                                              Container(
                                                width: 24,
                                                height: 24,
                                                decoration: BoxDecoration(
                                                  color: marker.color,
                                                  shape: BoxShape.circle,
                                                  border: Border.all(
                                                    color: Colors.white,
                                                    width: 2,
                                                  ),
                                                ),
                                              ),
                                              const SizedBox(height: 8),
                                              Text(
                                                marker.markerTitle ??
                                                    'Marker ${index + 1}',
                                                style: TextStyle(
                                                  fontSize: 10,
                                                  fontWeight: FontWeight.bold,
                                                  color: Colors.grey[800],
                                                ),
                                                maxLines: 1,
                                                overflow: TextOverflow.ellipsis,
                                              ),
                                            ],
                                          ),
                                        ),
                                      );
                                    },
                                  ),
                                ),
                            ],
                          );
                        }),
                      ],
                    ),
                  ),
                ),
              ],
            ),
    );
  }
}
5
likes
140
points
171
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter package for adding interactive markers on images with custom colors, titles, and notes. Supports web, mobile, and desktop platforms.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

file_picker, flutter, flutter_colorpicker, get, image_picker

More

Packages that depend on image_marker_viewer