diff --git a/userfront/lib/features/dashboard/presentation/dashboard_screen.dart b/userfront/lib/features/dashboard/presentation/dashboard_screen.dart index 6d87dff4..6b8c42fd 100644 --- a/userfront/lib/features/dashboard/presentation/dashboard_screen.dart +++ b/userfront/lib/features/dashboard/presentation/dashboard_screen.dart @@ -835,136 +835,77 @@ class _DashboardScreenState extends ConsumerState { Widget _buildActivityGrid(List<_ActivityItem> activities, bool isMobile) { if (activities.isEmpty) return const SizedBox.shrink(); - final shouldShowToggle = activities.length > 4; + return LayoutBuilder( + builder: (context, constraints) { + final maxWidth = constraints.maxWidth; + + // 화면 너비에 따른 컬럼 수 및 초기 표시 개수 결정 + int crossAxisCount; + if (maxWidth > 1200) { + crossAxisCount = 4; + } else if (maxWidth > 800) { + crossAxisCount = 3; + } else { + crossAxisCount = 2; + } - // 더보기를 누르지 않은 경우: 최대 4개 노출 (Grid/Wrap) - if (!_showAllActivities) { - final visibleActivities = activities.take(4).toList(); - Widget grid; - if (isMobile) { - grid = GridView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - mainAxisSpacing: 12, - crossAxisSpacing: 12, - childAspectRatio: 1.05, - ), - itemCount: visibleActivities.length, - itemBuilder: (context, index) => _buildActivityCard(visibleActivities[index]), - ); - } else { - grid = Wrap( - spacing: 12, - runSpacing: 12, - children: visibleActivities.map(_buildActivityCard).toList(), - ); - } + // 초기 표시 개수는 한 줄에 표시되는 개수와 동일하게 설정 (요청에 따라 유동적 조절 가능) + final int initialVisibleCount = crossAxisCount; + final shouldShowToggle = activities.length > initialVisibleCount; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - grid, - if (shouldShowToggle) - Padding( - padding: const EdgeInsets.only(top: 12), - child: Align( - alignment: Alignment.centerRight, - child: TextButton.icon( - onPressed: () => setState(() => _showAllActivities = true), - icon: const Icon(Icons.add, size: 18, color: Colors.blueAccent), - label: const Text('더보기', style: TextStyle(color: Colors.blueAccent, fontWeight: FontWeight.bold)), - ), - ), - ), - ], - ); - } + List<_ActivityItem> visibleActivities; + if (_showAllActivities) { + visibleActivities = activities; + } else { + visibleActivities = activities.take(initialVisibleCount).toList(); + } - // 더보기를 누른 경우: 가로 슬라이더/캐러셀 전환 - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Stack( - alignment: Alignment.center, + // 카드의 너비를 화면 너비에 맞춰 계산 (여백 고려) + final double spacing = 12.0; + final double cardWidth = (maxWidth - (spacing * (crossAxisCount - 1))) / crossAxisCount; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( - height: 220, - child: ListView.separated( - controller: _rpScrollController, - scrollDirection: Axis.horizontal, - itemCount: activities.length, - separatorBuilder: (context, index) => const SizedBox(width: 12), - itemBuilder: (context, index) => UnconstrainedBox( - alignment: Alignment.topCenter, - child: _buildActivityCard(activities[index]), + Wrap( + spacing: spacing, + runSpacing: spacing, + children: visibleActivities.map((item) { + return SizedBox( + width: cardWidth, + child: _buildActivityCard(item, cardWidth: cardWidth), + ); + }).toList(), + ), + if (shouldShowToggle) + Padding( + padding: const EdgeInsets.only(top: 16), + child: Align( + alignment: Alignment.centerRight, + child: TextButton.icon( + onPressed: () => setState(() => _showAllActivities = !_showAllActivities), + icon: Icon( + _showAllActivities ? Icons.keyboard_arrow_up : Icons.add, + size: 18, + color: _showAllActivities ? Colors.grey : Colors.blueAccent, + ), + label: Text( + _showAllActivities ? '접기' : '+ 더보기', + style: TextStyle( + color: _showAllActivities ? Colors.grey : Colors.blueAccent, + fontWeight: FontWeight.bold, + ), + ), + ), ), ), - ), - // 왼쪽 이동 버튼 - Positioned( - left: 0, - child: _buildScrollButton( - icon: Icons.chevron_left, - onPressed: () => _rpScrollController.animateTo( - (_rpScrollController.offset - 300).clamp(0, _rpScrollController.position.maxScrollExtent), - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - ), - ), - ), - // 오른쪽 이동 버튼 - Positioned( - right: 0, - child: _buildScrollButton( - icon: Icons.chevron_right, - onPressed: () => _rpScrollController.animateTo( - (_rpScrollController.offset + 300).clamp(0, _rpScrollController.position.maxScrollExtent), - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - ), - ), - ), ], - ), - const SizedBox(height: 12), - Align( - alignment: Alignment.centerRight, - child: TextButton.icon( - onPressed: () => setState(() { - _showAllActivities = false; - _rpScrollController.jumpTo(0); // 접을 때 위치 초기화 - }), - icon: const Icon(Icons.close, size: 18, color: Colors.grey), - label: const Text('접기', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold)), - ), - ), - ], + ); + }, ); } - Widget _buildScrollButton({required IconData icon, required VoidCallback onPressed}) { - return Container( - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.8), - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 4, - offset: const Offset(0, 2), - ), - ], - ), - child: IconButton( - icon: Icon(icon, color: _ink), - onPressed: onPressed, - ), - ); - } - - Widget _buildActivityCard(_ActivityItem item) { + Widget _buildActivityCard(_ActivityItem item, {double? cardWidth}) { final isActive = item.status == '활성'; final statusColor = isActive ? Colors.green : Colors.grey; final borderColor = isActive ? Colors.green.withOpacity(0.5) : _border; @@ -975,7 +916,7 @@ class _DashboardScreenState extends ConsumerState { // 카드 컨텐츠 final cardContent = Container( - width: 260, + width: cardWidth ?? 260, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: _surface,