리스트뷰 가로로 수정
This commit is contained in:
@@ -142,12 +142,17 @@ class _PlanPageDetailState extends State<PlanPageDetail> {
|
||||
toolbarHeight: 40,
|
||||
title: Text(_planTitle.toString()),
|
||||
),
|
||||
body: _planId == null
|
||||
? const Center(
|
||||
child: Text(
|
||||
'Plan ID가 전달되지 않았습니다.',
|
||||
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<List<PlanDetailItem>>(
|
||||
future: _planDetails,
|
||||
@@ -155,82 +160,136 @@ class _PlanPageDetailState extends State<PlanPageDetail> {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (snapshot.hasError) {
|
||||
return Center(child: Text('Error: ${snapshot.error}'));
|
||||
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!;
|
||||
return ListView.builder(
|
||||
itemCount: details.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = details[index];
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
|
||||
child: ListTile(
|
||||
leading: item.thumbnail.isNotEmpty
|
||||
? SizedBox(
|
||||
width: 100,
|
||||
height: 100,
|
||||
child: Image.network(
|
||||
item.thumbnail,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return const Icon(Icons.broken_image, size: 40, color: Colors.grey);
|
||||
|
||||
// 첫 번째 비디오의 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}')),
|
||||
);
|
||||
}
|
||||
},
|
||||
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,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
color: Colors.grey[200],
|
||||
child: const Icon(Icons.image_not_supported, size: 40, color: Colors.grey),
|
||||
),
|
||||
title: Text(item.lessonTag, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('ID: ${item.lessonId}', style: TextStyle(fontSize: 12, color: Colors.grey[700])),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
item.lessonUrl,
|
||||
style: const TextStyle(fontSize: 12, color: Colors.blueAccent),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
),
|
||||
],
|
||||
),
|
||||
isThreeLine: true,
|
||||
onTap: () {
|
||||
if (item.lessonUrl.isNotEmpty && (item.lessonUrl.contains('youtube.com') || item.lessonUrl.contains('youtu.be'))) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => YoutubePlayerPage(
|
||||
lessonUrl: item.lessonUrl,
|
||||
// currentBottomNavIndex: _currentBottomNavIndex, // 현재 탭 인덱스 전달
|
||||
),
|
||||
child: Container(
|
||||
width: 110,
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0, vertical: 8.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('유효한 YouTube URL이 아닙니다: ${item.lessonUrl}')),
|
||||
);
|
||||
}
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
)],
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user