2025-07-11

작업 시작전 저장
This commit is contained in:
girinb
2025-07-11 13:49:46 +09:00
parent 2ad2af76e0
commit 42453abe41
8 changed files with 198 additions and 95 deletions

62
lib/career_page.dart Normal file
View File

@@ -0,0 +1,62 @@
import 'package:flutter/material.dart';
import 'package:csp2/common/widgets/custom_bottom_nav_bar.dart';
class JobsPage extends StatefulWidget {
const JobsPage({super.key});
@override
State<JobsPage> createState() => _JobsPageState();
}
class _JobsPageState extends State<JobsPage> {
int _selectedIndex = 3; // Assuming Jobs is the 4th item (index 3)
String? _selectedDropdownValue; // 드롭다운 선택 값 저장 변수
@override
void initState() {
super.initState();
_selectedDropdownValue = 'Indonesia'; // 초기값 설정
}
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
// TODO: Add navigation logic here based on index
// For now, we'll just print the index
print('Tapped index: $index');
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(16.0), // 상단에 패딩 추가
child: Column(
mainAxisAlignment: MainAxisAlignment.start, // 상단 정렬
crossAxisAlignment: CrossAxisAlignment.start, // 왼쪽 정렬
children: [
DropdownButton<String>(
value: _selectedDropdownValue,
onChanged: (String? newValue) {
setState(() {
_selectedDropdownValue = newValue;
});
},
items: <String>['Indonesia', 'Hongkong', 'Singapore', 'South Korea',' Japan']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
const SizedBox(height: 20),
Text('Selected: $_selectedDropdownValue'),
],
),
),
);
}
}

View File

@@ -35,7 +35,7 @@ class CustomBottomNavBar extends StatelessWidget {
), ),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.work_outline), icon: Icon(Icons.work_outline),
label: 'Jobs', label: 'Career',
), ),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.more_horiz_outlined), icon: Icon(Icons.more_horiz_outlined),

View File

@@ -22,10 +22,10 @@ class CaseStudyPlan {
factory CaseStudyPlan.fromJson(Map<String, dynamic> json) { factory CaseStudyPlan.fromJson(Map<String, dynamic> json) {
return CaseStudyPlan( return CaseStudyPlan(
planId: json['planId'] ?? '아이디 없음', planId: json['casestudy lesson id'] ?? '아이디 없음',
planTitle: json['planTitle'] ?? '제목 없음', planTitle: json['course_name'] ?? '제목 없음',
planTeacher: json['planTeacher'] ?? '선생님 정보 없음', planTeacher: json['planTeacher'] ?? '',
thumbnail: json['thumbnail'] ?? '', thumbnail: json['course_thumbnail'] ?? '',
); );
} }
} }
@@ -460,7 +460,8 @@ class _HomePageState extends State<HomePage> {
), ),
const SizedBox(width: 8.0), const SizedBox(width: 8.0),
Text( Text(
plan.planTeacher, // plan.planTeacher,
"",
style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Colors.grey[600]), style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Colors.grey[600]),
), ),
], ],
@@ -508,7 +509,7 @@ class _HomePageState extends State<HomePage> {
), ),
// --- ▼▼▼ 추천 클래스 섹션 호출 ▼▼▼ --- // --- ▼▼▼ 추천 클래스 섹션 호출 ▼▼▼ ---
_buildRecommendSection(), // _buildRecommendSection(),
// --- ▲▲▲ 추천 클래스 섹션 호출 끝 ▲▲▲ --- // --- ▲▲▲ 추천 클래스 섹션 호출 끝 ▲▲▲ ---
], ],
); );

View File

@@ -1,32 +0,0 @@
import 'package:flutter/material.dart';
import 'package:csp2/common/widgets/custom_bottom_nav_bar.dart';
class JobsPage extends StatefulWidget {
const JobsPage({super.key});
@override
State<JobsPage> createState() => _JobsPageState();
}
class _JobsPageState extends State<JobsPage> {
int _selectedIndex = 3; // Assuming Jobs is the 4th item (index 3)
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
// TODO: Add navigation logic here based on index
// For now, we'll just print the index
print('Tapped index: $index');
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: const Center(
child: Text('Jobs Page'),
),
);
}
}

View File

@@ -6,7 +6,7 @@ import 'package:flutter/material.dart';
import 'home_page.dart'; // HomePage에 콜백을 전달해야 하므로 import 경로 확인 import 'home_page.dart'; // HomePage에 콜백을 전달해야 하므로 import 경로 확인
import 'plan_page.dart'; import 'plan_page.dart';
import 'statistics_page.dart'; import 'statistics_page.dart';
import 'jobs_page.dart'; import 'career_page.dart';
import 'more_page.dart'; import 'more_page.dart';
import 'common/widgets/custom_bottom_nav_bar.dart'; import 'common/widgets/custom_bottom_nav_bar.dart';
@@ -31,14 +31,15 @@ class MyApp extends StatelessWidget {
} }
class MyHomePage extends StatefulWidget { class MyHomePage extends StatefulWidget {
const MyHomePage({super.key}); final int initialIndex;
const MyHomePage({super.key, this.initialIndex = 0});
@override @override
State<MyHomePage> createState() => _MyHomePageState(); State<MyHomePage> createState() => _MyHomePageState();
} }
class _MyHomePageState extends State<MyHomePage> { class _MyHomePageState extends State<MyHomePage> {
int _selectedIndex = 0; late int _selectedIndex;
// 각 탭에 연결될 페이지 위젯 리스트 // 각 탭에 연결될 페이지 위젯 리스트
// HomePage는 StatefulWidget이므로 const를 붙이지 않습니다. // HomePage는 StatefulWidget이므로 const를 붙이지 않습니다.
@@ -48,6 +49,7 @@ class _MyHomePageState extends State<MyHomePage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_selectedIndex = widget.initialIndex;
// *** 수정: HomePage 생성 시 onNavigateToPlanTab 콜백 전달 *** // *** 수정: HomePage 생성 시 onNavigateToPlanTab 콜백 전달 ***
_widgetOptions = <Widget>[ _widgetOptions = <Widget>[
HomePage(onNavigateToPlanTab: _onItemTapped), // 콜백 함수 전달 HomePage(onNavigateToPlanTab: _onItemTapped), // 콜백 함수 전달
@@ -87,7 +89,7 @@ class _MyHomePageState extends State<MyHomePage> {
appBarTitle = 'Statistics'; appBarTitle = 'Statistics';
} }
else if (_selectedIndex == 3) { else if (_selectedIndex == 3) {
appBarTitle = 'Jobs'; appBarTitle = 'Career';
} }
else if (_selectedIndex == 4) { else if (_selectedIndex == 4) {
appBarTitle = 'More'; appBarTitle = 'More';

View File

@@ -21,10 +21,10 @@ class CaseStudyPlan {
factory CaseStudyPlan.fromJson(Map<String, dynamic> json) { factory CaseStudyPlan.fromJson(Map<String, dynamic> json) {
return CaseStudyPlan( return CaseStudyPlan(
planId: json['planId'] ?? 'ID 없음', planId: json['casestudy lesson id'] ?? '아이디 없음',
planTitle: json['planTitle'] ?? '제목 없음', planTitle: json['course_name'] ?? '제목 없음',
planTeacher: json['planTeacher'] ?? '선생님 정보 없음', planTeacher: json['planTeacher'] ?? '',
thumbnail: json['thumbnail'] ?? '', thumbnail: json['course_thumbnail'] ?? '',
); );
} }
} }
@@ -147,14 +147,16 @@ class _PlanPageState extends State<PlanPage> {
], ],
), ),
const SizedBox(height: 8.0), const SizedBox(height: 8.0),
if (plan.thumbnail.isNotEmpty) if (plan.thumbnail.isNotEmpty)
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(8.0), borderRadius: BorderRadius.circular(8.0),
child: Image.network( child: Image.network(
plan.thumbnail, plan.thumbnail,
height: 140, height: 120,
width: double.infinity, width: double.infinity,
fit: BoxFit.cover, fit: BoxFit.contain,
alignment: Alignment.centerLeft,
loadingBuilder: (BuildContext context, Widget child, loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) { ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return child; if (loadingProgress == null) return child;

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'dart:convert'; import 'dart:convert';
import 'main.dart';
import 'youtube_player_page.dart'; // YoutubePlayerPage import import 'youtube_player_page.dart'; // YoutubePlayerPage import
import 'common/widgets/custom_bottom_nav_bar.dart'; import 'common/widgets/custom_bottom_nav_bar.dart';
@@ -10,12 +11,16 @@ class PlanDetailItem {
final String lessonTag; final String lessonTag;
final String lessonUrl; final String lessonUrl;
final String thumbnail; final String thumbnail;
final String lessonName;
final String lessonDescription;
PlanDetailItem({ PlanDetailItem({
required this.lessonId, required this.lessonId,
required this.lessonTag, required this.lessonTag,
required this.lessonUrl, required this.lessonUrl,
required this.thumbnail, required this.thumbnail,
required this.lessonName,
required this.lessonDescription,
}); });
factory PlanDetailItem.fromJson(Map<String, dynamic> json) { factory PlanDetailItem.fromJson(Map<String, dynamic> json) {
@@ -24,6 +29,8 @@ class PlanDetailItem {
lessonTag: json['lesson tag'] ?? '태그 없음', lessonTag: json['lesson tag'] ?? '태그 없음',
lessonUrl: json['lesson url'] ?? 'URL 없음', lessonUrl: json['lesson url'] ?? 'URL 없음',
thumbnail: json['thumbnail'] ?? '', thumbnail: json['thumbnail'] ?? '',
lessonName: json['lesson_name'] ?? '이름 없음',
lessonDescription: json['lesson_description'] ?? '설명 없음',
); );
} }
} }
@@ -43,6 +50,7 @@ class _PlanPageDetailState extends State<PlanPageDetail> {
Future<List<PlanDetailItem>>? _planDetails; Future<List<PlanDetailItem>>? _planDetails;
late int _currentBottomNavIndex; late int _currentBottomNavIndex;
String? _selectedYoutubeUrl; String? _selectedYoutubeUrl;
PlanDetailItem? _selectedItem; // <<< 선택된 아이템을 저장할 변수 추가
@override @override
void initState() { void initState() {
@@ -80,7 +88,6 @@ class _PlanPageDetailState extends State<PlanPageDetail> {
}); });
_planDetails = _planDetails =
Future.error(Exception("전달된 인자가 올바르지 않습니다. (Map<String, String> 형태여야 함)")); Future.error(Exception("전달된 인자가 올바르지 않습니다. (Map<String, String> 형태여야 함)"));
} }
} }
} }
@@ -111,29 +118,11 @@ class _PlanPageDetailState extends State<PlanPageDetail> {
setState(() { setState(() {
_currentBottomNavIndex = index; _currentBottomNavIndex = index;
}); });
if (index == 0) { Navigator.of(context).pushAndRemoveUntil(
Navigator.of(context).popUntil((route) => route.isFirst); MaterialPageRoute(builder: (context) => MyHomePage(initialIndex: index)),
} else { (Route<dynamic> route) => false,
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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -171,6 +160,9 @@ class _PlanPageDetailState extends State<PlanPageDetail> {
return const Center(child: Text('세부 계획 데이터가 없습니다.')); return const Center(child: Text('세부 계획 데이터가 없습니다.'));
} else { } else {
final details = snapshot.data!; final details = snapshot.data!;
if (_selectedItem == null && details.isNotEmpty) {
_selectedItem = details.first;
}
// 첫 번째 비디오의 URL을 가져와 _selectedYoutubeUrl을 초기화합니다. // 첫 번째 비디오의 URL을 가져와 _selectedYoutubeUrl을 초기화합니다.
if (_selectedYoutubeUrl == null && details.isNotEmpty) { if (_selectedYoutubeUrl == null && details.isNotEmpty) {
@@ -178,12 +170,12 @@ class _PlanPageDetailState extends State<PlanPageDetail> {
(item) => item.lessonUrl.isNotEmpty && (item) => item.lessonUrl.isNotEmpty &&
(item.lessonUrl.contains('youtube.com') || (item.lessonUrl.contains('youtube.com') ||
item.lessonUrl.contains('youtu.be')), item.lessonUrl.contains('youtu.be')),
orElse: () => PlanDetailItem(lessonId: '', lessonTag: '', lessonUrl: '', thumbnail: ''), orElse: () => PlanDetailItem(lessonId: '', lessonTag: '', lessonUrl: '', thumbnail: '',lessonName: '', lessonDescription: ''),
).lessonUrl.isNotEmpty ? details.firstWhere( ).lessonUrl.isNotEmpty ? details.firstWhere(
(item) => item.lessonUrl.isNotEmpty && (item) => item.lessonUrl.isNotEmpty &&
(item.lessonUrl.contains('youtube.com') || (item.lessonUrl.contains('youtube.com') ||
item.lessonUrl.contains('youtu.be')), item.lessonUrl.contains('youtu.be')),
orElse: () => PlanDetailItem(lessonId: '', lessonTag: '', lessonUrl: '', thumbnail: ''), orElse: () => PlanDetailItem(lessonId: '', lessonTag: '', lessonUrl: '', thumbnail: '',lessonName: '', lessonDescription: ''),
).lessonUrl : null; ).lessonUrl : null;
} }
@@ -199,12 +191,12 @@ class _PlanPageDetailState extends State<PlanPageDetail> {
final item = details[index]; final item = details[index];
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
setState(() {
_selectedItem = item;
if (item.lessonUrl.isNotEmpty && if (item.lessonUrl.isNotEmpty &&
(item.lessonUrl.contains('youtube.com') || (item.lessonUrl.contains('youtube.com') ||
item.lessonUrl.contains('youtu.be'))) { item.lessonUrl.contains('youtu.be'))) {
setState(() {
_selectedYoutubeUrl = item.lessonUrl; _selectedYoutubeUrl = item.lessonUrl;
});
} else { } else {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
@@ -212,6 +204,7 @@ class _PlanPageDetailState extends State<PlanPageDetail> {
'유효한 YouTube URL이 아닙니다: ${item.lessonUrl}')), '유효한 YouTube URL이 아닙니다: ${item.lessonUrl}')),
); );
} }
});
}, },
child: Container( child: Container(
width: 110, width: 110,
@@ -289,7 +282,82 @@ class _PlanPageDetailState extends State<PlanPageDetail> {
); );
}, },
), ),
)], ),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_selectedItem!.thumbnail.isNotEmpty
? ClipRRect(
borderRadius: BorderRadius.circular(12.0),
child: Image.network(
_selectedItem!.thumbnail,
height: 200,
width: double.infinity,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container(
height: 200,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(12.0),
),
child: const Icon(Icons.broken_image, size: 60, color: Colors.grey),
);
},
),
)
: Container(
height: 200,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(12.0),
),
child: const Icon(Icons.image_not_supported, size: 60, color: Colors.grey),
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
_selectedItem!.lessonName,
style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold),
),
),
IconButton(
icon: const Icon(Icons.play_circle_fill, size: 40, color: Colors.red),
onPressed: () {
if (_selectedYoutubeUrl != null && _selectedYoutubeUrl!.isNotEmpty) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => YoutubePlayerPage(lessonUrl: _selectedYoutubeUrl!),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('재생할 수 있는 영상이 없습니다.')),
);
}
},
),
],
),
const SizedBox(height: 12),
Text(
_selectedItem!.lessonDescription,
style: Theme.of(context).textTheme.bodyLarge,
),
],
),
),
),
],
); );
} }
}, },

View File

@@ -54,7 +54,7 @@ class _YoutubePlayerPageState extends State<YoutubePlayerPage> {
_controller = YoutubePlayerController( _controller = YoutubePlayerController(
initialVideoId: _videoId!, initialVideoId: _videoId!,
flags: const YoutubePlayerFlags( flags: const YoutubePlayerFlags(
autoPlay: false, autoPlay: true,
mute: false, mute: false,
), ),
)..addListener(_playerListener); )..addListener(_playerListener);
@@ -150,7 +150,7 @@ class _YoutubePlayerPageState extends State<YoutubePlayerPage> {
tabName = 'Statistics'; tabName = 'Statistics';
break; break;
case 3: case 3:
tabName = 'Jobs'; // New case for Jobs tabName = 'Career'; // New case for Jobs
break; break;
case 4: case 4:
tabName = 'More'; // Updated case for More tabName = 'More'; // Updated case for More
@@ -247,12 +247,12 @@ class _YoutubePlayerPageState extends State<YoutubePlayerPage> {
), ),
], ],
), ),
// bottomNavigationBar: isFullScreen bottomNavigationBar: isFullScreen
// ? null ? null
// : CustomBottomNavBar( : CustomBottomNavBar(
// currentIndex: _currentBottomNavIndex, currentIndex: _currentBottomNavIndex,
// onTap: _onBottomNavItemTapped, onTap: _onBottomNavItemTapped,
// ), ),
), ),
); );
} }