Files
receipity/lib/models/item.dart
T
2026-08-28 18:23:56 +02:00

131 lines
3.7 KiB
Dart

/// 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<String, dynamic> 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<String, dynamic> 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);
}
}