feat: add cameras field to Gallery model and implement CameraPills widget; enhance profile and gallery pages to display cameras

+1
lib/models/gallery.dart
··· 21 21 int? commentCount, 22 22 GalleryViewer? viewer, 23 23 List<Map<String, dynamic>>? facets, 24 + List<String>? cameras, 24 25 }) = _Gallery; 25 26 26 27 factory Gallery.fromJson(Map<String, dynamic> json) => _$GalleryFromJson(json);
+33 -3
lib/models/gallery.freezed.dart
··· 32 32 int? get commentCount => throw _privateConstructorUsedError; 33 33 GalleryViewer? get viewer => throw _privateConstructorUsedError; 34 34 List<Map<String, dynamic>>? get facets => throw _privateConstructorUsedError; 35 + List<String>? get cameras => throw _privateConstructorUsedError; 35 36 36 37 /// Serializes this Gallery to a JSON map. 37 38 Map<String, dynamic> toJson() => throw _privateConstructorUsedError; ··· 59 60 int? commentCount, 60 61 GalleryViewer? viewer, 61 62 List<Map<String, dynamic>>? facets, 63 + List<String>? cameras, 62 64 }); 63 65 64 66 $ProfileCopyWith<$Res>? get creator; ··· 91 93 Object? commentCount = freezed, 92 94 Object? viewer = freezed, 93 95 Object? facets = freezed, 96 + Object? cameras = freezed, 94 97 }) { 95 98 return _then( 96 99 _value.copyWith( ··· 138 141 ? _value.facets 139 142 : facets // ignore: cast_nullable_to_non_nullable 140 143 as List<Map<String, dynamic>>?, 144 + cameras: freezed == cameras 145 + ? _value.cameras 146 + : cameras // ignore: cast_nullable_to_non_nullable 147 + as List<String>?, 141 148 ) 142 149 as $Val, 143 150 ); ··· 192 199 int? commentCount, 193 200 GalleryViewer? viewer, 194 201 List<Map<String, dynamic>>? facets, 202 + List<String>? cameras, 195 203 }); 196 204 197 205 @override ··· 225 233 Object? commentCount = freezed, 226 234 Object? viewer = freezed, 227 235 Object? facets = freezed, 236 + Object? cameras = freezed, 228 237 }) { 229 238 return _then( 230 239 _$GalleryImpl( ··· 272 281 ? _value._facets 273 282 : facets // ignore: cast_nullable_to_non_nullable 274 283 as List<Map<String, dynamic>>?, 284 + cameras: freezed == cameras 285 + ? _value._cameras 286 + : cameras // ignore: cast_nullable_to_non_nullable 287 + as List<String>?, 275 288 ), 276 289 ); 277 290 } ··· 292 305 this.commentCount, 293 306 this.viewer, 294 307 final List<Map<String, dynamic>>? facets, 308 + final List<String>? cameras, 295 309 }) : _items = items, 296 - _facets = facets; 310 + _facets = facets, 311 + _cameras = cameras; 297 312 298 313 factory _$GalleryImpl.fromJson(Map<String, dynamic> json) => 299 314 _$$GalleryImplFromJson(json); ··· 334 349 return EqualUnmodifiableListView(value); 335 350 } 336 351 352 + final List<String>? _cameras; 353 + @override 354 + List<String>? get cameras { 355 + final value = _cameras; 356 + if (value == null) return null; 357 + if (_cameras is EqualUnmodifiableListView) return _cameras; 358 + // ignore: implicit_dynamic_type 359 + return EqualUnmodifiableListView(value); 360 + } 361 + 337 362 @override 338 363 String toString() { 339 - return 'Gallery(uri: $uri, cid: $cid, title: $title, description: $description, items: $items, creator: $creator, createdAt: $createdAt, favCount: $favCount, commentCount: $commentCount, viewer: $viewer, facets: $facets)'; 364 + return 'Gallery(uri: $uri, cid: $cid, title: $title, description: $description, items: $items, creator: $creator, createdAt: $createdAt, favCount: $favCount, commentCount: $commentCount, viewer: $viewer, facets: $facets, cameras: $cameras)'; 340 365 } 341 366 342 367 @override ··· 358 383 (identical(other.commentCount, commentCount) || 359 384 other.commentCount == commentCount) && 360 385 (identical(other.viewer, viewer) || other.viewer == viewer) && 361 - const DeepCollectionEquality().equals(other._facets, _facets)); 386 + const DeepCollectionEquality().equals(other._facets, _facets) && 387 + const DeepCollectionEquality().equals(other._cameras, _cameras)); 362 388 } 363 389 364 390 @JsonKey(includeFromJson: false, includeToJson: false) ··· 376 402 commentCount, 377 403 viewer, 378 404 const DeepCollectionEquality().hash(_facets), 405 + const DeepCollectionEquality().hash(_cameras), 379 406 ); 380 407 381 408 /// Create a copy of Gallery ··· 405 432 final int? commentCount, 406 433 final GalleryViewer? viewer, 407 434 final List<Map<String, dynamic>>? facets, 435 + final List<String>? cameras, 408 436 }) = _$GalleryImpl; 409 437 410 438 factory _Gallery.fromJson(Map<String, dynamic> json) = _$GalleryImpl.fromJson; ··· 431 459 GalleryViewer? get viewer; 432 460 @override 433 461 List<Map<String, dynamic>>? get facets; 462 + @override 463 + List<String>? get cameras; 434 464 435 465 /// Create a copy of Gallery 436 466 /// with the given fields replaced by the non-null parameter values.
+4
lib/models/gallery.g.dart
··· 27 27 facets: (json['facets'] as List<dynamic>?) 28 28 ?.map((e) => e as Map<String, dynamic>) 29 29 .toList(), 30 + cameras: (json['cameras'] as List<dynamic>?) 31 + ?.map((e) => e as String) 32 + .toList(), 30 33 ); 31 34 32 35 Map<String, dynamic> _$$GalleryImplToJson(_$GalleryImpl instance) => ··· 42 45 'commentCount': instance.commentCount, 43 46 'viewer': instance.viewer, 44 47 'facets': instance.facets, 48 + 'cameras': instance.cameras, 45 49 };
+6
lib/screens/gallery_page.dart
··· 7 7 import 'package:grain/screens/hashtag_page.dart'; 8 8 import 'package:grain/screens/profile_page.dart'; 9 9 import 'package:grain/widgets/app_image.dart'; 10 + import 'package:grain/widgets/camera_pills.dart'; 10 11 import 'package:grain/widgets/faceted_text.dart'; 11 12 import 'package:grain/widgets/gallery_action_buttons.dart'; 12 13 import 'package:grain/widgets/gallery_photo_view.dart'; ··· 249 250 MaterialPageRoute(builder: (_) => HashtagPage(hashtag: tag)), 250 251 ), 251 252 ), 253 + ), 254 + if ((gallery.cameras?.isNotEmpty ?? false)) 255 + Padding( 256 + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), 257 + child: CameraPills(cameras: gallery.cameras!), 252 258 ), 253 259 if (isLoggedIn) 254 260 Padding(
+2 -26
lib/screens/profile_page.dart
··· 10 10 import 'package:grain/widgets/app_image.dart'; 11 11 import 'package:grain/widgets/edit_profile_sheet.dart'; 12 12 import 'package:grain/widgets/faceted_text.dart'; 13 + import 'package:grain/widgets/camera_pills.dart'; 13 14 import 'package:url_launcher/url_launcher.dart'; 14 15 15 16 import 'gallery_page.dart'; ··· 284 285 ), 285 286 if ((profile.cameras?.isNotEmpty ?? false)) ...[ 286 287 const SizedBox(height: 16), 287 - Wrap( 288 - spacing: 8, 289 - runSpacing: 4, 290 - children: profile.cameras! 291 - .map( 292 - (camera) => Container( 293 - padding: const EdgeInsets.symmetric( 294 - horizontal: 12, 295 - vertical: 4, 296 - ), 297 - decoration: BoxDecoration( 298 - color: theme.colorScheme.surface, 299 - borderRadius: BorderRadius.circular(999), 300 - ), 301 - child: Text( 302 - '📷 $camera', 303 - style: TextStyle( 304 - color: theme.colorScheme.onSurface, 305 - fontWeight: FontWeight.w500, 306 - fontSize: 12, 307 - ), 308 - ), 309 - ), 310 - ) 311 - .toList(), 312 - ), 288 + CameraPills(cameras: profile.cameras!), 313 289 ], 314 290 if ((profile.description ?? '').isNotEmpty) ...[ 315 291 const SizedBox(height: 16),
+39
lib/widgets/camera_pills.dart
··· 1 + import 'package:flutter/material.dart'; 2 + 3 + class CameraPills extends StatelessWidget { 4 + final List<String> cameras; 5 + final EdgeInsetsGeometry? padding; 6 + const CameraPills({super.key, required this.cameras, this.padding}); 7 + 8 + @override 9 + Widget build(BuildContext context) { 10 + final theme = Theme.of(context); 11 + if (cameras.isEmpty) return SizedBox.shrink(); 12 + return Padding( 13 + padding: padding ?? EdgeInsets.zero, 14 + child: Wrap( 15 + spacing: 8, 16 + runSpacing: 4, 17 + children: cameras 18 + .map( 19 + (camera) => Container( 20 + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), 21 + decoration: BoxDecoration( 22 + color: theme.colorScheme.surface, 23 + borderRadius: BorderRadius.circular(999), 24 + ), 25 + child: Text( 26 + '📷 $camera', 27 + style: TextStyle( 28 + color: theme.colorScheme.onSurface, 29 + fontWeight: FontWeight.w500, 30 + fontSize: 12, 31 + ), 32 + ), 33 + ), 34 + ) 35 + .toList(), 36 + ), 37 + ); 38 + } 39 + }