creek

The AI Image Editor of 2030

lib/ui/pages/image_details_page.dart (41724B)


   1import 'dart:io';
   2import 'package:flutter/material.dart';
   3import 'package:flutter_svg/flutter_svg.dart';
   4import 'package:creekui/services/image_service.dart';
   5import 'package:creekui/services/note_service.dart';
   6import 'package:creekui/data/models/note_model.dart';
   7import 'package:creekui/data/models/image_model.dart';
   8import 'package:creekui/data/models/canvas_models.dart';
   9import 'package:creekui/utils/image_actions_helper.dart';
  10import 'package:creekui/utils/image_utils.dart';
  11import 'package:creekui/ui/styles/variables.dart';
  12import 'package:creekui/ui/widgets/primary_button.dart';
  13import 'package:creekui/ui/widgets/empty_state.dart';
  14import 'package:creekui/ui/widgets/selection_overlay_painter.dart';
  15import 'package:creekui/ui/widgets/note_input_sheet.dart';
  16import 'package:creekui/ui/widgets/app_bar.dart';
  17import 'package:creekui/ui/widgets/dialog.dart';
  18import 'package:creekui/ui/widgets/tag_chip.dart';
  19
  20class ImageDetailsPage extends StatefulWidget {
  21  final String imagePath;
  22  final String imageId;
  23  final int projectId;
  24
  25  const ImageDetailsPage({
  26    super.key,
  27    required this.imagePath,
  28    required this.imageId,
  29    required this.projectId,
  30  });
  31
  32  @override
  33  State<ImageDetailsPage> createState() => _ImageDetailsPageState();
  34}
  35
  36class _ImageDetailsPageState extends State<ImageDetailsPage> {
  37  final NoteService _noteService = NoteService();
  38  final ImageService _imageService = ImageService();
  39
  40  ImageModel? _imageModel;
  41  List<NoteModel> _notes = [];
  42  List<String> _currentTags = [];
  43  bool _isLoading = true;
  44  int? _activeNoteId;
  45
  46  // Drawing/Resizing
  47  bool _isDrawMode = false;
  48  bool _isResizing = false;
  49  final GlobalKey _imageKey = GlobalKey();
  50
  51  Offset? _startPos;
  52  Offset? _currentPos;
  53  Rect? _finalSelectionRect;
  54  Size? _imageRenderSize;
  55
  56  DragHandle _activeHandle = DragHandle.none;
  57  Offset? _startDragLocalOffset;
  58  final double _handleSize = 25.0;
  59
  60  final List<String> _allAvailableTags = [
  61    'Compositions',
  62    'Subject',
  63    'Fonts',
  64    'Background',
  65    'Texture',
  66    'Colours',
  67    'Material Look',
  68    'Lighting',
  69    'Style',
  70    'Era',
  71    'Emotion',
  72  ];
  73
  74  @override
  75  void initState() {
  76    super.initState();
  77    _loadData();
  78  }
  79
  80  Future<void> _loadData() async {
  81    try {
  82      final image = await _imageService.getImage(widget.imageId);
  83      final notes = await _noteService.getNotesForImage(widget.imageId);
  84      final tags = await _imageService.getTags(widget.imageId);
  85
  86      if (mounted) {
  87        setState(() {
  88          _imageModel = image;
  89          _notes = notes;
  90          _currentTags = tags;
  91          _isLoading = false;
  92        });
  93      }
  94    } catch (e) {
  95      debugPrint("Error loading details: $e");
  96      if (mounted) setState(() => _isLoading = false);
  97    }
  98  }
  99
 100  // Actions
 101  void _resetSelectionMode() {
 102    setState(() {
 103      _isDrawMode = false;
 104      _isResizing = false;
 105      _finalSelectionRect = null;
 106      _startPos = null;
 107      _currentPos = null;
 108      _activeHandle = DragHandle.none;
 109      _startDragLocalOffset = null;
 110    });
 111  }
 112
 113  void _activateDrawMode() {
 114    _resetSelectionMode();
 115    setState(() => _isDrawMode = true);
 116  }
 117
 118  void _confirmSelectionAndShowModal() {
 119    if (_finalSelectionRect != null) {
 120      setState(() {
 121        _isResizing = false;
 122        _activeHandle = DragHandle.none;
 123      });
 124      _showAddNoteInputDialog();
 125    }
 126  }
 127
 128  void _openNotesSheet({int? highlightId}) {
 129    setState(() => _activeNoteId = highlightId);
 130    showModalBottomSheet(
 131      context: context,
 132      isScrollControlled: true,
 133      backgroundColor: Colors.transparent,
 134      builder:
 135          (context) => _NotesListSheet(
 136            notes: _notes,
 137            highlightId: highlightId,
 138            onAddNotePressed: () {
 139              Navigator.pop(context);
 140              _activateDrawMode();
 141            },
 142          ),
 143    ).whenComplete(() => setState(() => _activeNoteId = null));
 144  }
 145
 146  // Gestures
 147  Offset? _getLocalPosition(Offset globalPosition) {
 148    final RenderBox? box =
 149        _imageKey.currentContext?.findRenderObject() as RenderBox?;
 150    if (box == null) return null;
 151
 152    // Store the render size
 153    _imageRenderSize = box.size;
 154
 155    // Convert global to local
 156    final local = box.globalToLocal(globalPosition);
 157
 158    // Clamp coordinates to ensure we don't draw/drag outside the image
 159    final dx = local.dx.clamp(0.0, box.size.width);
 160    final dy = local.dy.clamp(0.0, box.size.height);
 161    return Offset(dx, dy);
 162  }
 163
 164  // Resizing Handlers
 165  DragHandle _getDragHandle(Offset pos) {
 166    if (_finalSelectionRect == null) return DragHandle.none;
 167    final rect = _finalSelectionRect!;
 168
 169    // Check corners first
 170    if (Rect.fromCircle(
 171      center: rect.topLeft,
 172      radius: _handleSize,
 173    ).contains(pos))
 174      return DragHandle.topLeft;
 175    if (Rect.fromCircle(
 176      center: rect.topRight,
 177      radius: _handleSize,
 178    ).contains(pos))
 179      return DragHandle.topRight;
 180    if (Rect.fromCircle(
 181      center: rect.bottomLeft,
 182      radius: _handleSize,
 183    ).contains(pos))
 184      return DragHandle.bottomLeft;
 185    if (Rect.fromCircle(
 186      center: rect.bottomRight,
 187      radius: _handleSize,
 188    ).contains(pos))
 189      return DragHandle.bottomRight;
 190    if (rect.contains(pos)) return DragHandle.center;
 191    return DragHandle.none;
 192  }
 193
 194  void _onPanStart(DragStartDetails details) {
 195    if (_isDrawMode) {
 196      final pos = _getLocalPosition(details.globalPosition);
 197      if (pos == null) return;
 198      setState(() {
 199        _startPos = pos;
 200        _currentPos = pos;
 201      });
 202    } else if (_isResizing && _finalSelectionRect != null) {
 203      final pos = _getLocalPosition(details.globalPosition);
 204      if (pos == null) return;
 205      final handle = _getDragHandle(pos);
 206      if (handle != DragHandle.none) {
 207        setState(() {
 208          _activeHandle = handle;
 209          if (handle == DragHandle.center) {
 210            _startDragLocalOffset = pos - _finalSelectionRect!.topLeft;
 211          }
 212        });
 213      }
 214    }
 215  }
 216
 217  void _onPanUpdate(DragUpdateDetails details) {
 218    if (_isDrawMode) {
 219      final pos = _getLocalPosition(details.globalPosition);
 220      if (pos == null) return;
 221      setState(() => _currentPos = pos);
 222    } else if (_isResizing && _finalSelectionRect != null) {
 223      final pos = _getLocalPosition(details.globalPosition);
 224      if (pos == null || _activeHandle == DragHandle.none) return;
 225
 226      setState(() {
 227        Rect newRect = _finalSelectionRect!;
 228        final newPoint = pos;
 229
 230        switch (_activeHandle) {
 231          case DragHandle.topLeft:
 232            newRect = Rect.fromLTRB(
 233              newPoint.dx,
 234              newPoint.dy,
 235              newRect.right,
 236              newRect.bottom,
 237            );
 238            break;
 239          case DragHandle.topRight:
 240            newRect = Rect.fromLTRB(
 241              newRect.left,
 242              newPoint.dy,
 243              newPoint.dx,
 244              newRect.bottom,
 245            );
 246            break;
 247          case DragHandle.bottomLeft:
 248            newRect = Rect.fromLTRB(
 249              newPoint.dx,
 250              newRect.top,
 251              newRect.right,
 252              newPoint.dy,
 253            );
 254            break;
 255          case DragHandle.bottomRight:
 256            newRect = Rect.fromLTRB(
 257              newRect.left,
 258              newRect.top,
 259              newPoint.dx,
 260              newPoint.dy,
 261            );
 262            break;
 263          case DragHandle.center:
 264            if (_startDragLocalOffset != null) {
 265              final newTopLeft = newPoint - _startDragLocalOffset!;
 266              newRect = Rect.fromLTWH(
 267                newTopLeft.dx,
 268                newTopLeft.dy,
 269                newRect.width,
 270                newRect.height,
 271              );
 272            }
 273            break;
 274          case DragHandle.none:
 275            return;
 276        }
 277
 278        if (_imageRenderSize != null) {
 279          final cl = newRect.left.clamp(0.0, _imageRenderSize!.width);
 280          final ct = newRect.top.clamp(0.0, _imageRenderSize!.height);
 281          final cr = newRect.right.clamp(0.0, _imageRenderSize!.width);
 282          final cb = newRect.bottom.clamp(0.0, _imageRenderSize!.height);
 283          newRect = Rect.fromLTRB(cl, ct, cr, cb).normalize();
 284        } else {
 285          newRect = newRect.normalize();
 286        }
 287
 288        if (newRect.width > 10 && newRect.height > 10)
 289          _finalSelectionRect = newRect;
 290      });
 291    }
 292  }
 293
 294  void _onPanEnd(DragEndDetails details) {
 295    if (_isDrawMode && _startPos != null && _currentPos != null) {
 296      final rect = Rect.fromPoints(_startPos!, _currentPos!).normalize();
 297      if (rect.width < 10 || rect.height < 10) {
 298        _resetSelectionMode();
 299        return;
 300      }
 301      setState(() {
 302        _isDrawMode = false;
 303        _isResizing = true;
 304        _finalSelectionRect = rect;
 305        _startPos = null;
 306        _currentPos = null;
 307      });
 308    } else if (_isResizing) {
 309      setState(() {
 310        _activeHandle = DragHandle.none;
 311        _startDragLocalOffset = null;
 312      });
 313    }
 314  }
 315
 316  // Show modal
 317  void _showAddNoteInputDialog() {
 318    String initialCategory =
 319        _allAvailableTags.contains('Compositions')
 320            ? 'Compositions'
 321            : (_allAvailableTags.firstOrNull ?? 'General');
 322
 323    showModalBottomSheet(
 324      context: context,
 325      isScrollControlled: true,
 326      backgroundColor: Colors.transparent,
 327      builder: (context) {
 328        return NoteModalOverlay(
 329          screenSize: MediaQuery.of(context).size,
 330          modalContent: NoteInputSheet(
 331            categories: _allAvailableTags,
 332            initialCategory: initialCategory,
 333            onSubmit: (content, category) async {
 334              if (_finalSelectionRect != null && _imageRenderSize != null) {
 335                final normRect = _finalSelectionRect!.normalize();
 336                await _noteService.addNote(
 337                  widget.imageId,
 338                  content,
 339                  category,
 340                  normX: normRect.center.dx / _imageRenderSize!.width,
 341                  normY: normRect.center.dy / _imageRenderSize!.height,
 342                  normWidth: normRect.width / _imageRenderSize!.width,
 343                  normHeight: normRect.height / _imageRenderSize!.height,
 344                );
 345                final updatedNotes = await _noteService.getNotesForImage(
 346                  widget.imageId,
 347                );
 348                if (mounted)
 349                  setState(() {
 350                    _notes = updatedNotes;
 351                    _finalSelectionRect = null;
 352                  });
 353                if (context.mounted) Navigator.pop(context);
 354              }
 355            },
 356          ),
 357        );
 358      },
 359    );
 360  }
 361
 362  // Edit Tags Dialog
 363  void _openEditTagsDialog() {
 364    List<String> tempTags = List.from(_currentTags);
 365    showDialog(
 366      context: context,
 367      builder:
 368          (ctx) => StatefulBuilder(
 369            builder: (context, setLocalState) {
 370              return Dialog(
 371                backgroundColor: Colors.white,
 372                shape: RoundedRectangleBorder(
 373                  borderRadius: BorderRadius.circular(Variables.radiusLarge),
 374                ),
 375                child: ShowDialog(
 376                  title: "Edit Tags",
 377                  primaryButtonText: "Save",
 378                  onPrimaryPressed: () async {
 379                    setState(() => _currentTags = tempTags);
 380                    await _imageService.updateTags(widget.imageId, tempTags);
 381                    if (mounted) Navigator.pop(context);
 382                  },
 383                  content: SingleChildScrollView(
 384                    child: Wrap(
 385                      spacing: 8,
 386                      runSpacing: 8,
 387                      children:
 388                          _allAvailableTags.map((tag) {
 389                            final isSelected = tempTags.contains(tag);
 390                            return GestureDetector(
 391                              onTap: () {
 392                                setLocalState(() {
 393                                  isSelected
 394                                      ? tempTags.remove(tag)
 395                                      : tempTags.add(tag);
 396                                });
 397                              },
 398                              child: TagChip(
 399                                label: tag,
 400                                icon:
 401                                    isSelected
 402                                        ? const Icon(
 403                                          Icons.check,
 404                                          size: 14,
 405                                          color: Variables.chipText,
 406                                        )
 407                                        : null,
 408                              ),
 409                            );
 410                          }).toList(),
 411                    ),
 412                  ),
 413                ),
 414              );
 415            },
 416          ),
 417    );
 418  }
 419
 420  @override
 421  Widget build(BuildContext context) {
 422    // Determine if user can pan/zoom the image carousel
 423    final isSelectionModeActive = _isDrawMode || _isResizing;
 424
 425    return Scaffold(
 426      // Prevents the main screen from pushing up when the keyboard opens
 427      resizeToAvoidBottomInset: false,
 428      backgroundColor: Variables.surfaceBackground,
 429      appBar: CommonAppBar(
 430        showBack: true,
 431        leading: IconButton(
 432          icon: const Icon(
 433            Icons.arrow_back_ios_new,
 434            size: 20,
 435            color: Variables.textPrimary,
 436          ),
 437          onPressed: () => Navigator.pop(context),
 438        ),
 439        centerTitle: true,
 440        titleWidget:
 441            (!isSelectionModeActive && _imageModel != null)
 442                ? Container(
 443                  height: 40,
 444                  decoration: BoxDecoration(
 445                    color: Variables.surfaceSubtle,
 446                    borderRadius: BorderRadius.circular(30),
 447                  ),
 448                  child: InkWell(
 449                    borderRadius: BorderRadius.circular(30),
 450                    onTap:
 451                        () => ImageActionsHelper.sendToFiles(
 452                          context,
 453                          _imageModel!.filePath,
 454                        ),
 455                    child: Padding(
 456                      padding: const EdgeInsets.symmetric(horizontal: 16.0),
 457                      child: Row(
 458                        mainAxisSize: MainAxisSize.min,
 459                        children: [
 460                          SvgPicture.asset(
 461                            'assets/icons/files_icon.svg',
 462                            width: 18,
 463                            height: 18,
 464                            colorFilter: const ColorFilter.mode(
 465                              Variables.textPrimary,
 466                              BlendMode.srcIn,
 467                            ),
 468                          ),
 469                          const SizedBox(width: 8),
 470                          Text(
 471                            "Send to File",
 472                            style: Variables.headerStyle.copyWith(
 473                              fontSize: 14,
 474                              fontWeight: FontWeight.w600,
 475                            ),
 476                          ),
 477                        ],
 478                      ),
 479                    ),
 480                  ),
 481                )
 482                : null,
 483        actions: [
 484          // 1. Confirm Selection (Resizing mode)
 485          if (_isResizing && _finalSelectionRect != null)
 486            IconButton(
 487              icon: const Icon(
 488                Icons.check_circle_outline,
 489                color: Color(0xFF7C4DFF),
 490              ),
 491              onPressed: _confirmSelectionAndShowModal,
 492            ),
 493
 494          // 2. Cancel Selection (Any selection mode)
 495          if (isSelectionModeActive)
 496            IconButton(
 497              icon: const Icon(Icons.close, color: Variables.textPrimary),
 498              onPressed: _resetSelectionMode,
 499            ),
 500
 501          // 3. Standard Actions (when NOT selecting)
 502          if (!isSelectionModeActive && _imageModel != null) ...[
 503            // Share Button
 504            IconButton(
 505              onPressed:
 506                  () => ImageActionsHelper.shareImage(
 507                    context,
 508                    _imageModel!.filePath,
 509                  ),
 510              icon: SvgPicture.asset(
 511                'assets/icons/share_icon.svg',
 512                width: 20,
 513                height: 20,
 514                colorFilter: const ColorFilter.mode(
 515                  Variables.textPrimary,
 516                  BlendMode.srcIn,
 517                ),
 518              ),
 519            ),
 520
 521            // Rename Button
 522            IconButton(
 523              onPressed:
 524                  () => ImageActionsHelper.renameImage(
 525                    context,
 526                    _imageModel!,
 527                    () => _loadData(),
 528                  ),
 529              icon: const Icon(
 530                Icons.drive_file_rename_outline,
 531                color: Variables.textPrimary,
 532              ),
 533            ),
 534          ],
 535        ],
 536      ),
 537      body:
 538          _isLoading
 539              ? const Center(
 540                child: CircularProgressIndicator(color: Variables.textPrimary),
 541              )
 542              : Column(
 543                children: [
 544                  Expanded(
 545                    child: SingleChildScrollView(
 546                      child: Column(
 547                        crossAxisAlignment: CrossAxisAlignment.start,
 548                        children: [
 549                          // 1. Image Container
 550                          Container(
 551                            margin: const EdgeInsets.symmetric(horizontal: 16),
 552                            constraints: const BoxConstraints(maxHeight: 500),
 553                            decoration: BoxDecoration(
 554                              color: Colors.black,
 555                              borderRadius: BorderRadius.circular(20),
 556                            ),
 557                            child: ClipRRect(
 558                              borderRadius: BorderRadius.circular(20),
 559                              child: LayoutBuilder(
 560                                builder: (context, constraints) {
 561                                  return Stack(
 562                                    fit: StackFit.passthrough,
 563                                    children: [
 564                                      InteractiveViewer(
 565                                        // Disable pan/scale if selection or resizing is active
 566                                        panEnabled: !isSelectionModeActive,
 567                                        scaleEnabled: !isSelectionModeActive,
 568                                        child: GestureDetector(
 569                                          onPanStart: _onPanStart,
 570                                          onPanUpdate: _onPanUpdate,
 571                                          onPanEnd: _onPanEnd,
 572                                          child: Stack(
 573                                            children: [
 574                                              Image.file(
 575                                                File(widget.imagePath),
 576                                                key: _imageKey,
 577                                                fit: BoxFit.contain,
 578                                                width: double.infinity,
 579                                              ),
 580
 581                                              // Drawing Overlay (if in drawing mode)
 582                                              if (_isDrawMode &&
 583                                                  _startPos != null &&
 584                                                  _currentPos != null)
 585                                                Positioned.fill(
 586                                                  child: CustomPaint(
 587                                                    painter:
 588                                                        SelectionOverlayPainter(
 589                                                          rect:
 590                                                              Rect.fromPoints(
 591                                                                _startPos!,
 592                                                                _currentPos!,
 593                                                              ).normalize(),
 594                                                          isResizing: false,
 595                                                        ),
 596                                                  ),
 597                                                ),
 598
 599                                              // Final Selection Rect (if in resizing mode)
 600                                              if (_isResizing &&
 601                                                  _finalSelectionRect != null)
 602                                                Positioned.fill(
 603                                                  child: CustomPaint(
 604                                                    painter:
 605                                                        SelectionOverlayPainter(
 606                                                          rect:
 607                                                              _finalSelectionRect!,
 608                                                          isResizing: true,
 609                                                          activeHandle:
 610                                                              _activeHandle,
 611                                                        ),
 612                                                  ),
 613                                                ),
 614                                            ],
 615                                          ),
 616                                        ),
 617                                      ),
 618
 619                                      // Existing Note Indicators
 620                                      ..._notes.map((note) {
 621                                        final x =
 622                                            note.normX * constraints.maxWidth;
 623                                        final y =
 624                                            note.normY * constraints.maxHeight;
 625                                        return Positioned(
 626                                          left: x - 10,
 627                                          top: y - 10,
 628                                          child: GestureDetector(
 629                                            onTap:
 630                                                () => _openNotesSheet(
 631                                                  highlightId: note.id,
 632                                                ),
 633                                            child: Container(
 634                                              width: 20,
 635                                              height: 20,
 636                                              decoration: BoxDecoration(
 637                                                color: Colors.white,
 638                                                shape: BoxShape.circle,
 639                                                boxShadow: [
 640                                                  BoxShadow(
 641                                                    color: Colors.black
 642                                                        .withOpacity(0.3),
 643                                                    blurRadius: 4,
 644                                                    offset: const Offset(0, 1),
 645                                                  ),
 646                                                ],
 647                                                border:
 648                                                    note.id == _activeNoteId
 649                                                        ? Border.all(
 650                                                          color: const Color(
 651                                                            0xFF7C4DFF,
 652                                                          ),
 653                                                          width: 3,
 654                                                        )
 655                                                        : null,
 656                                              ),
 657                                            ),
 658                                          ),
 659                                        );
 660                                      }),
 661
 662                                      // Notes Button (Show only when not in selection mode)
 663                                      if (!isSelectionModeActive)
 664                                        Positioned(
 665                                          bottom: 12,
 666                                          right: 12,
 667                                          child: ElevatedButton(
 668                                            onPressed: () => _openNotesSheet(),
 669                                            style: ElevatedButton.styleFrom(
 670                                              backgroundColor: Colors.white,
 671                                              foregroundColor: Colors.black,
 672                                              elevation: 4,
 673                                              padding:
 674                                                  const EdgeInsets.symmetric(
 675                                                    horizontal: 16,
 676                                                    vertical: 12,
 677                                                  ),
 678                                              shape: RoundedRectangleBorder(
 679                                                borderRadius:
 680                                                    BorderRadius.circular(12),
 681                                              ),
 682                                            ),
 683                                            child: Row(
 684                                              children: const [
 685                                                Text(
 686                                                  "Notes",
 687                                                  style: TextStyle(
 688                                                    fontWeight: FontWeight.w600,
 689                                                    fontSize: 14,
 690                                                  ),
 691                                                ),
 692                                                SizedBox(width: 8),
 693                                                Icon(
 694                                                  Icons.assignment_outlined,
 695                                                  size: 18,
 696                                                ),
 697                                              ],
 698                                            ),
 699                                          ),
 700                                        ),
 701
 702                                      // Instruction Overlays
 703                                      if (_isDrawMode && _startPos == null)
 704                                        Positioned(
 705                                          top: 20,
 706                                          left: 0,
 707                                          right: 0,
 708                                          child: Center(
 709                                            child: Container(
 710                                              padding:
 711                                                  const EdgeInsets.symmetric(
 712                                                    horizontal: 16,
 713                                                    vertical: 8,
 714                                                  ),
 715                                              decoration: BoxDecoration(
 716                                                color: Colors.black87,
 717                                                borderRadius:
 718                                                    BorderRadius.circular(20),
 719                                              ),
 720                                              child: const Text(
 721                                                "Drag on image to select area",
 722                                                style: TextStyle(
 723                                                  color: Colors.white,
 724                                                  fontSize: 12,
 725                                                ),
 726                                              ),
 727                                            ),
 728                                          ),
 729                                        ),
 730                                      if (_isResizing &&
 731                                          _finalSelectionRect != null &&
 732                                          _activeHandle == DragHandle.none)
 733                                        Positioned(
 734                                          top: 20,
 735                                          left: 0,
 736                                          right: 0,
 737                                          child: Center(
 738                                            child: Container(
 739                                              padding:
 740                                                  const EdgeInsets.symmetric(
 741                                                    horizontal: 16,
 742                                                    vertical: 8,
 743                                                  ),
 744                                              decoration: BoxDecoration(
 745                                                color: Colors.black87,
 746                                                borderRadius:
 747                                                    BorderRadius.circular(20),
 748                                              ),
 749                                              child: const Text(
 750                                                "Adjust area or tap checkmark to confirm",
 751                                                style: TextStyle(
 752                                                  color: Colors.white,
 753                                                  fontSize: 12,
 754                                                ),
 755                                              ),
 756                                            ),
 757                                          ),
 758                                        ),
 759                                    ],
 760                                  );
 761                                },
 762                              ),
 763                            ),
 764                          ),
 765
 766                          const SizedBox(height: 20),
 767
 768                          // 2. Info Section
 769                          Padding(
 770                            padding: const EdgeInsets.symmetric(horizontal: 20),
 771                            child: Column(
 772                              crossAxisAlignment: CrossAxisAlignment.start,
 773                              children: [
 774                                Text(
 775                                  _imageModel?.name ?? "Untitled Image",
 776                                  style: Variables.bodyStyle.copyWith(
 777                                    fontSize: 16,
 778                                    fontWeight: FontWeight.bold,
 779                                  ),
 780                                ),
 781
 782                                const SizedBox(height: 24),
 783
 784                                // Tags Box
 785                                Container(
 786                                  padding: const EdgeInsets.all(16),
 787                                  decoration: BoxDecoration(
 788                                    color: const Color(0xFFF9F9F9),
 789                                    borderRadius: BorderRadius.circular(16),
 790                                    border: Border.all(
 791                                      color: Variables.borderSubtle,
 792                                    ),
 793                                  ),
 794                                  child: Column(
 795                                    children: [
 796                                      Row(
 797                                        mainAxisAlignment:
 798                                            MainAxisAlignment.spaceBetween,
 799                                        children: [
 800                                          const Text(
 801                                            "Tags",
 802                                            style: TextStyle(
 803                                              fontWeight: FontWeight.bold,
 804                                              fontSize: 15,
 805                                            ),
 806                                          ),
 807                                          GestureDetector(
 808                                            onTap: _openEditTagsDialog,
 809                                            child: const Icon(
 810                                              Icons.edit_outlined,
 811                                              size: 18,
 812                                              color: Variables.textSecondary,
 813                                            ),
 814                                          ),
 815                                        ],
 816                                      ),
 817                                      const SizedBox(height: 12),
 818                                      const Divider(
 819                                        height: 1,
 820                                        color: Variables.borderSubtle,
 821                                      ),
 822                                      const SizedBox(height: 16),
 823                                      SizedBox(
 824                                        width: double.infinity,
 825                                        child:
 826                                            _currentTags.isEmpty
 827                                                ? const Text(
 828                                                  "No tags yet.",
 829                                                  style: TextStyle(
 830                                                    color: Colors.grey,
 831                                                  ),
 832                                                )
 833                                                : Wrap(
 834                                                  spacing: 8,
 835                                                  runSpacing: 8,
 836                                                  children:
 837                                                      _currentTags
 838                                                          .map(
 839                                                            (tag) => TagChip(
 840                                                              label: tag,
 841                                                              icon: null,
 842                                                            ),
 843                                                          )
 844                                                          .toList(),
 845                                                ),
 846                                      ),
 847                                    ],
 848                                  ),
 849                                ),
 850                              ],
 851                            ),
 852                          ),
 853                          const SizedBox(height: 40),
 854                        ],
 855                      ),
 856                    ),
 857                  ),
 858                ],
 859              ),
 860    );
 861  }
 862}
 863
 864// Notes List Sheet
 865class _NotesListSheet extends StatefulWidget {
 866  final List<NoteModel> notes;
 867  final int? highlightId;
 868  final VoidCallback onAddNotePressed;
 869  const _NotesListSheet({
 870    required this.notes,
 871    this.highlightId,
 872    required this.onAddNotePressed,
 873  });
 874  @override
 875  State<_NotesListSheet> createState() => __NotesListSheetState();
 876}
 877
 878class __NotesListSheetState extends State<_NotesListSheet> {
 879  final ScrollController _scrollController = ScrollController();
 880  @override
 881  void initState() {
 882    super.initState();
 883    if (widget.highlightId != null) {
 884      WidgetsBinding.instance.addPostFrameCallback((_) {
 885        final index = widget.notes.indexWhere(
 886          (n) => n.id == widget.highlightId,
 887        );
 888        if (index != -1 && _scrollController.hasClients) {
 889          _scrollController.animateTo(
 890            index * 80.0,
 891            duration: const Duration(milliseconds: 300),
 892            curve: Curves.easeOut,
 893          );
 894        }
 895      });
 896    }
 897  }
 898
 899  @override
 900  Widget build(BuildContext context) {
 901    return DraggableScrollableSheet(
 902      initialChildSize: 0.5,
 903      minChildSize: 0.3,
 904      maxChildSize: 0.85,
 905      builder: (_, controller) {
 906        return Container(
 907          decoration: const BoxDecoration(
 908            color: Colors.white,
 909            borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
 910          ),
 911          padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
 912          child: Column(
 913            children: [
 914              Center(
 915                child: Container(
 916                  width: 40,
 917                  height: 4,
 918                  margin: const EdgeInsets.only(bottom: 20),
 919                  decoration: BoxDecoration(
 920                    color: Colors.grey[300],
 921                    borderRadius: BorderRadius.circular(2),
 922                  ),
 923                ),
 924              ),
 925              Row(
 926                mainAxisAlignment: MainAxisAlignment.spaceBetween,
 927                children: [
 928                  Text(
 929                    "Notes (${widget.notes.length})",
 930                    style: const TextStyle(
 931                      fontWeight: FontWeight.bold,
 932                      fontSize: 18,
 933                    ),
 934                  ),
 935                  IconButton(
 936                    icon: const Icon(Icons.close, color: Colors.grey),
 937                    onPressed: () => Navigator.pop(context),
 938                  ),
 939                ],
 940              ),
 941              const Divider(),
 942              Expanded(
 943                child:
 944                    widget.notes.isEmpty
 945                        ? const EmptyState(
 946                          icon: Icons.comment,
 947                          title: "No notes yet",
 948                          subtitle: "Tap 'Add Note' to start",
 949                        )
 950                        : ListView.builder(
 951                          controller: controller,
 952                          itemCount: widget.notes.length,
 953                          itemBuilder: (context, index) {
 954                            final note = widget.notes[index];
 955                            final isHighlighted = note.id == widget.highlightId;
 956                            return Container(
 957                              margin: const EdgeInsets.only(bottom: 12),
 958                              padding: const EdgeInsets.all(16),
 959                              decoration: BoxDecoration(
 960                                color:
 961                                    isHighlighted
 962                                        ? const Color(0xFFF3F0FF)
 963                                        : Colors.white,
 964                                borderRadius: BorderRadius.circular(12),
 965                                border: Border.all(
 966                                  color:
 967                                      isHighlighted
 968                                          ? const Color(0xFF7C4DFF)
 969                                          : Colors.grey[200]!,
 970                                  width: isHighlighted ? 1.5 : 1,
 971                                ),
 972                              ),
 973                              child: Column(
 974                                crossAxisAlignment: CrossAxisAlignment.start,
 975                                children: [
 976                                  Container(
 977                                    padding: const EdgeInsets.symmetric(
 978                                      horizontal: 10,
 979                                      vertical: 4,
 980                                    ),
 981                                    decoration: BoxDecoration(
 982                                      color: const Color(0xFFEEF0FF),
 983                                      borderRadius: BorderRadius.circular(6),
 984                                    ),
 985                                    child: Text(
 986                                      note.category,
 987                                      style: const TextStyle(
 988                                        fontSize: 11,
 989                                        fontWeight: FontWeight.w700,
 990                                        color: Color(0xFF7C4DFF),
 991                                      ),
 992                                    ),
 993                                  ),
 994                                  const SizedBox(height: 8),
 995                                  Text(
 996                                    note.content,
 997                                    style: const TextStyle(
 998                                      fontSize: 15,
 999                                      color: Colors.black87,
1000                                      height: 1.4,
1001                                    ),
1002                                  ),
1003                                ],
1004                              ),
1005                            );
1006                          },
1007                        ),
1008              ),
1009              // Add Note Button
1010              const SizedBox(height: 10),
1011              PrimaryButton(
1012                text: "Add Note",
1013                onPressed: widget.onAddNotePressed,
1014                iconPath: "assets/icons/add-line.svg",
1015              ),
1016              const SizedBox(height: 10),
1017            ],
1018          ),
1019        );
1020      },
1021    );
1022  }
1023}