import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import 'youtube_player_page.dart'; // YoutubePlayerPage import import 'common/widgets/custom_bottom_nav_bar.dart'; // PlanDetailItem 클래스 (이전과 동일) class PlanDetailItem { final String lessonId; final String lessonTag; final String lessonUrl; final String thumbnail; PlanDetailItem({ required this.lessonId, required this.lessonTag, required this.lessonUrl, required this.thumbnail, }); factory PlanDetailItem.fromJson(Map json) { return PlanDetailItem( lessonId: json['casestudy lesson id'] ?? 'ID 없음', lessonTag: json['lesson tag'] ?? '태그 없음', lessonUrl: json['lesson url'] ?? 'URL 없음', thumbnail: json['thumbnail'] ?? '', ); } } class PlanPageDetail extends StatefulWidget { const PlanPageDetail({ super.key, }); @override State createState() => _PlanPageDetailState(); } class _PlanPageDetailState extends State { String? _planId; String? _planTitle; // <<< planTitle을 저장할 상태 변수 추가 >>> Future>? _planDetails; late int _currentBottomNavIndex; String? _selectedYoutubeUrl; @override void initState() { super.initState(); _currentBottomNavIndex = 0; } @override void didChangeDependencies() { super.didChangeDependencies(); // 인자를 한 번만 처리하도록 조건 추가 if (_planId == null && _planTitle == null) { final Object? arguments = ModalRoute.of(context)?.settings.arguments; if (arguments is Map) { // <<< 전달받은 인자가 Map인지 확인 >>> setState(() { // <<< setState로 상태 변수 업데이트 >>> _planId = arguments['planId']; _planTitle = arguments['planTitle']; }); if (_planId != null) { _planDetails = _fetchPlanDetails(_planId!); } else { // Map에는 있지만 planId 키가 없는 경우 (이론상 발생하기 어려움) setState(() { _planTitle = arguments['planTitle'] ?? 'Error'; // planTitle은 있을 수 있음 }); _planDetails = Future.error(Exception("Plan ID가 Map에 포함되지 않았습니다.")); } } else { // 인자가 Map이 아니거나 null인 경우 setState(() { _planTitle = 'Error'; // AppBar에 오류 표시 }); _planDetails = Future.error(Exception("전달된 인자가 올바르지 않습니다. (Map 형태여야 함)")); } } } Future> _fetchPlanDetails(String planId) async { final response = await http.get( Uri.parse('https://helloworld1-ad2uqhckxq-uc.a.run.app/?id=$planId')); if (response.statusCode == 200) { final Map decodedJson = json.decode(response.body); if (decodedJson.containsKey('data') && decodedJson['data'] is List) { final List detailsJson = decodedJson['data']; return detailsJson .map((jsonItem) => PlanDetailItem.fromJson(jsonItem as Map)) .toList(); } else { throw Exception( 'Invalid API response format: "data" field is missing or not a list.'); } } else { throw Exception( 'Failed to load plan details. Status code: ${response.statusCode}'); } } void _onBottomNavItemTapped(int index) { setState(() { _currentBottomNavIndex = index; }); if (index == 0) { Navigator.of(context).popUntil((route) => route.isFirst); } else { String tabName = ''; switch (index) { case 1: tabName = 'Plan'; break; case 2: tabName = 'Statistics'; break; case 3: tabName = 'Jobs'; // New case for Jobs break; case 4: tabName = 'More'; // Updated case for More break; } ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('$tabName 탭으로 이동 (구현 필요)')), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( toolbarHeight: 40, title: Text(_planTitle.toString()), ), body: _planId == null && _planTitle == null && _planDetails == null ? Center( // 초기 로딩 상태 또는 인자 오류 child: _planTitle == 'Error' ? const Text( '플랜 정보를 불러올 수 없습니다.', style: TextStyle(fontSize: 18, color: Colors.red), textAlign: TextAlign.center, maxLines: 3, overflow: TextOverflow.ellipsis, ) : const CircularProgressIndicator(), ) : FutureBuilder>( future: _planDetails, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } else if (snapshot.hasError) { return Center( child: Padding( padding: const EdgeInsets.all(16.0), child: Text('Error loading details: ${snapshot.error}', textAlign: TextAlign.center, maxLines: 3, overflow: TextOverflow.ellipsis, ))); } else if (!snapshot.hasData || snapshot.data!.isEmpty) { return const Center(child: Text('세부 계획 데이터가 없습니다.')); } else { final details = snapshot.data!; // 첫 번째 비디오의 URL을 가져와 _selectedYoutubeUrl을 초기화합니다. if (_selectedYoutubeUrl == null && details.isNotEmpty) { _selectedYoutubeUrl = details.firstWhere( (item) => item.lessonUrl.isNotEmpty && (item.lessonUrl.contains('youtube.com') || item.lessonUrl.contains('youtu.be')), orElse: () => PlanDetailItem(lessonId: '', lessonTag: '', lessonUrl: '', thumbnail: ''), ).lessonUrl.isNotEmpty ? details.firstWhere( (item) => item.lessonUrl.isNotEmpty && (item.lessonUrl.contains('youtube.com') || item.lessonUrl.contains('youtu.be')), orElse: () => PlanDetailItem(lessonId: '', lessonTag: '', lessonUrl: '', thumbnail: ''), ).lessonUrl : null; } return Column( // ListView와 YoutubePlayerPage를 세로로 배치하기 위해 Column 사용 children: [ SizedBox( height: 150, // 가로 스크롤 리스트의 높이 child: ListView.builder( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 8.0), itemCount: details.length, itemBuilder: (context, index) { final item = details[index]; return GestureDetector( onTap: () { if (item.lessonUrl.isNotEmpty && (item.lessonUrl.contains('youtube.com') || item.lessonUrl.contains('youtu.be'))) { setState(() { _selectedYoutubeUrl = item.lessonUrl; }); } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( '유효한 YouTube URL이 아닙니다: ${item.lessonUrl}')), ); } }, child: Container( width: 110, margin: const EdgeInsets.symmetric( horizontal: 8.0, vertical: 8.0), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox( width: 100, height: 100, child: ClipRRect( borderRadius: BorderRadius.circular(8.0), child: item.thumbnail.isNotEmpty ? Image.network( item.thumbnail, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( color: Colors.grey[200], child: const Icon( Icons.broken_image, size: 40, color: Colors.grey), ); }, loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) { if (loadingProgress == null) return child; return Center( child: CircularProgressIndicator( value: loadingProgress .expectedTotalBytes != null ? loadingProgress .cumulativeBytesLoaded / loadingProgress .expectedTotalBytes! : null, strokeWidth: 2.0, ), ); }, ) : Container( color: Colors.grey[200], child: const Icon( Icons.image_not_supported, size: 40, color: Colors.grey), ), ), ), const SizedBox(height: 6), Text( item.lessonTag, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w500), maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, ), ], ), ), ); }, ), )], ); } }, ), bottomNavigationBar: CustomBottomNavBar( currentIndex: _currentBottomNavIndex, onTap: _onBottomNavItemTapped, ), ); } }