From afb93df48fdcca30f176c58c12f14205ed3f12a2 Mon Sep 17 00:00:00 2001 From: AmanRajSinghMourya Date: Mon, 25 Aug 2025 11:31:32 +0530 Subject: [PATCH] Add CollectionFlexGridViewWidget and SectionTitle components --- .../collection_flex_grid_view.dart | 115 ++++++++++++++++++ .../lib/ui/collections/section_title.dart | 67 ++++++++++ 2 files changed, 182 insertions(+) create mode 100644 mobile/apps/locker/lib/ui/collections/collection_flex_grid_view.dart create mode 100644 mobile/apps/locker/lib/ui/collections/section_title.dart diff --git a/mobile/apps/locker/lib/ui/collections/collection_flex_grid_view.dart b/mobile/apps/locker/lib/ui/collections/collection_flex_grid_view.dart new file mode 100644 index 0000000000..823f502ef1 --- /dev/null +++ b/mobile/apps/locker/lib/ui/collections/collection_flex_grid_view.dart @@ -0,0 +1,115 @@ +import "dart:math"; + +import "package:ente_ui/theme/ente_theme.dart"; +import "package:flutter/material.dart"; +import "package:locker/l10n/l10n.dart"; +import "package:locker/services/collections/models/collection.dart"; +import "package:locker/ui/pages/collection_page.dart"; + +class CollectionFlexGridViewWidget extends StatefulWidget { + final List collections; + final Map collectionFileCounts; + const CollectionFlexGridViewWidget({ + super.key, + required this.collections, + required this.collectionFileCounts, + }); + + @override + State createState() => + _CollectionFlexGridViewWidgetState(); +} + +class _CollectionFlexGridViewWidgetState + extends State { + late List _displayedCollections; + late Map _collectionFileCounts; + + @override + void initState() { + super.initState(); + _displayedCollections = widget.collections; + _collectionFileCounts = widget.collectionFileCounts; + } + + @override + Widget build(BuildContext context) { + return MediaQuery.removePadding( + context: context, + removeBottom: true, + removeTop: true, + child: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + childAspectRatio: 2.2, + ), + itemCount: min(_displayedCollections.length, 4), + itemBuilder: (context, index) { + final collection = _displayedCollections[index]; + final collectionName = collection.name ?? 'Unnamed Collection'; + + return GestureDetector( + onTap: () => _navigateToCollection(collection), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: getEnteColorScheme(context).fillFaint, + ), + padding: const EdgeInsets.all(12), + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + collectionName, + style: getEnteTextTheme(context).body.copyWith( + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + const SizedBox(height: 4), + Text( + context.l10n + .items(_collectionFileCounts[collection.id] ?? 0), + style: getEnteTextTheme(context).small.copyWith( + color: Colors.grey[600], + ), + textAlign: TextAlign.left, + ), + ], + ), + if (collection.type == CollectionType.favorites) + Positioned( + top: 0, + right: 0, + child: Icon( + Icons.star, + color: getEnteColorScheme(context).primary500, + size: 18, + ), + ), + ], + ), + ), + ); + }, + ), + ); + } + + void _navigateToCollection(Collection collection) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => CollectionPage(collection: collection), + ), + ); + } +} diff --git a/mobile/apps/locker/lib/ui/collections/section_title.dart b/mobile/apps/locker/lib/ui/collections/section_title.dart new file mode 100644 index 0000000000..81f9056b24 --- /dev/null +++ b/mobile/apps/locker/lib/ui/collections/section_title.dart @@ -0,0 +1,67 @@ +import "package:ente_ui/theme/ente_theme.dart"; +import 'package:flutter/material.dart'; + +class SectionTitle extends StatelessWidget { + final String? title; + final bool mutedTitle; + final Widget? titleWithBrand; + final EdgeInsetsGeometry? padding; + + const SectionTitle({ + this.title, + this.titleWithBrand, + this.mutedTitle = false, + super.key, + this.padding, + }); + + @override + Widget build(BuildContext context) { + Widget child; + if (titleWithBrand != null) { + child = titleWithBrand!; + } else if (title != null) { + child = Text( + title!, + style: getEnteTextTheme(context).h3Bold, + ); + } else { + child = const SizedBox.shrink(); + } + return child; + } +} + +class SectionOptions extends StatelessWidget { + final Widget title; + final Widget? trailingWidget; + final VoidCallback? onTap; + + const SectionOptions( + this.title, { + this.trailingWidget, + super.key, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + if (trailingWidget != null) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: onTap, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + title, + trailingWidget!, + ], + ), + ); + } else { + return Container( + child: title, + ); + } + } +}