/// Domain models for catalog items shown in lists and detail screens. library; import 'package:flutter/material.dart'; /// High-level grouping used for filters, icons, and accent colors. enum ItemCategory { design, development, productivity, lifestyle, } extension ItemCategoryX on ItemCategory { String get label => switch (this) { ItemCategory.design => 'Design', ItemCategory.development => 'Development', ItemCategory.productivity => 'Productivity', ItemCategory.lifestyle => 'Lifestyle', }; IconData get icon => switch (this) { ItemCategory.design => Icons.palette_outlined, ItemCategory.development => Icons.code, ItemCategory.productivity => Icons.bolt_outlined, ItemCategory.lifestyle => Icons.spa_outlined, }; /// Stable accent used by [Hero] tiles so the animation has a colored target. Color get accent => switch (this) { ItemCategory.design => const Color(0xFF7C3AED), ItemCategory.development => const Color(0xFF0F766E), ItemCategory.productivity => const Color(0xFFC2410C), ItemCategory.lifestyle => const Color(0xFF0369A1), }; static ItemCategory fromName(String name) { return ItemCategory.values.firstWhere( (category) => category.name == name, orElse: () => ItemCategory.productivity, ); } static ItemCategory fromIndex(int index) { return ItemCategory.values[index.abs() % ItemCategory.values.length]; } } /// A catalog entry displayed on Home / Search and opened on the detail route. class Item { const Item({ required this.id, required this.title, required this.subtitle, required this.description, required this.category, required this.createdAt, }); final String id; final String title; final String subtitle; final String description; final ItemCategory category; final DateTime createdAt; /// Tag used by the list-to-detail [Hero] animation. String get heroTag => 'item-hero-$id'; Item copyWith({ String? id, String? title, String? subtitle, String? description, ItemCategory? category, DateTime? createdAt, }) { return Item( id: id ?? this.id, title: title ?? this.title, subtitle: subtitle ?? this.subtitle, description: description ?? this.description, category: category ?? this.category, createdAt: createdAt ?? this.createdAt, ); } /// Maps a JSONPlaceholder `/posts` payload (or any similar JSON object). factory Item.fromJson(Map json) { final id = json['id']?.toString() ?? '0'; final title = (json['title'] as String? ?? 'Untitled').trim(); final body = (json['body'] as String? ?? '').trim(); final numericId = int.tryParse(id) ?? 0; return Item( id: 'remote-$id', title: _titleCase(title), subtitle: _firstSentence(body), description: body.replaceAll('\n', ' '), category: ItemCategoryX.fromIndex(numericId), createdAt: DateTime(2026, 1, 1).add(Duration(days: numericId)), ); } Map toJson() { return { 'id': id, 'title': title, 'subtitle': subtitle, 'description': description, 'category': category.name, 'createdAt': createdAt.toIso8601String(), }; } static String _titleCase(String value) { if (value.isEmpty) return value; return value[0].toUpperCase() + value.substring(1); } static String _firstSentence(String value) { if (value.isEmpty) return 'No summary available.'; final cleaned = value.replaceAll('\n', ' '); final period = cleaned.indexOf('.'); if (period <= 0) { return cleaned.length > 80 ? '${cleaned.substring(0, 77)}…' : cleaned; } return cleaned.substring(0, period + 1); } }