대학연결
This commit is contained in:
56
lib/common/widgets/course_card.dart
Normal file
56
lib/common/widgets/course_card.dart
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:csp2/course.dart';
|
||||||
|
class CourseCard extends StatelessWidget {
|
||||||
|
final Course course;
|
||||||
|
|
||||||
|
const CourseCard({super.key, required this.course});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: 150, // 각 항목의 너비
|
||||||
|
margin: const EdgeInsets.only(right: 12.0),
|
||||||
|
padding: const EdgeInsets.all(8.0), // 내부 패딩 추가
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).cardColor, // 카드 배경색
|
||||||
|
borderRadius: BorderRadius.circular(12.0), // 둥근 모서리
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withOpacity(0.2), // 그림자 색상
|
||||||
|
spreadRadius: 1,
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: const Offset(0, 2), // 그림자 위치
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
|
|
||||||
|
child: Image.network(
|
||||||
|
course.affiliateOrgIcon,
|
||||||
|
width: double.infinity, // 가로 사이즈에 비례하여 채우도록 변경
|
||||||
|
height: 100,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8.0),
|
||||||
|
Text(
|
||||||
|
course.affiliateName,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
course.affiliateDescription,
|
||||||
|
style: const TextStyle(color: Colors.grey),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,39 +14,18 @@ class UpcomingClassCard extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: 300, // 각 항목의 너비를 지정합니다.
|
width: MediaQuery.of(context).size.width / 2, // 화면 너비의 절반으로 설정
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: Card(
|
child: Card(
|
||||||
margin: const EdgeInsets.only(right: 12.0), // 항목 간의 간격 조정
|
margin: const EdgeInsets.only(right: 12.0), // 항목 간의 간격 조정
|
||||||
elevation: 2.0,
|
elevation: 2.0,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5.0)),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(12.0),
|
padding: const EdgeInsets.all(5.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
plan.planTitle,
|
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: 2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8.0),
|
|
||||||
Text(
|
|
||||||
// plan.planTeacher,
|
|
||||||
"",
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Colors.grey[600]),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8.0),
|
|
||||||
if (plan.thumbnail.isNotEmpty)
|
if (plan.thumbnail.isNotEmpty)
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
@@ -54,7 +33,7 @@ class UpcomingClassCard extends StatelessWidget {
|
|||||||
child: Image.network(
|
child: Image.network(
|
||||||
plan.thumbnail,
|
plan.thumbnail,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.contain,
|
||||||
loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
|
loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
|
||||||
if (loadingProgress == null) return child;
|
if (loadingProgress == null) return child;
|
||||||
return Center(
|
return Center(
|
||||||
@@ -74,10 +53,34 @@ class UpcomingClassCard extends StatelessWidget {
|
|||||||
else
|
else
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.grey[200],
|
color: Colors.grey[200],
|
||||||
child: const Center(child: Text('No Image', style: TextStyle(color: Colors.grey)))
|
child: const Center(child: Text('No Image', style: TextStyle(color: Colors.grey)))
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// const SizedBox(height: 5.0),
|
||||||
|
SizedBox(
|
||||||
|
height: 70, // 텍스트 영역의 고정 높이 설정 (두 줄 텍스트를 위한 충분한 공간)
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 48.0, // plan.planTitle이 항상 두 줄 공간을 차지하도록 고정
|
||||||
|
child: Text(
|
||||||
|
plan.planTitle,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4.0),
|
||||||
|
Text(
|
||||||
|
plan.planTeacher,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Colors.grey[600]),
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
32
lib/course.dart
Normal file
32
lib/course.dart
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
class Course {
|
||||||
|
final String affiliateNumber;
|
||||||
|
final String affiliateName;
|
||||||
|
final String affiliateType;
|
||||||
|
final String affiliateResult;
|
||||||
|
final String affiliateDescription;
|
||||||
|
final String affiliateOrg;
|
||||||
|
final String affiliateOrgIcon;
|
||||||
|
|
||||||
|
Course({
|
||||||
|
required this.affiliateNumber,
|
||||||
|
required this.affiliateName,
|
||||||
|
required this.affiliateType,
|
||||||
|
required this.affiliateResult,
|
||||||
|
required this.affiliateDescription,
|
||||||
|
required this.affiliateOrg,
|
||||||
|
required this.affiliateOrgIcon,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Course.fromJson(Map<String, dynamic> json) {
|
||||||
|
return Course(
|
||||||
|
affiliateNumber: json['affliate_number'] as String,
|
||||||
|
affiliateName: json['affliate_name'] as String,
|
||||||
|
affiliateType: json['affliate_type'] as String,
|
||||||
|
affiliateResult: json['affliate_result'] as String,
|
||||||
|
affiliateDescription: json['affliate_discription'] as String,
|
||||||
|
affiliateOrg: json['affliate_org'] as String,
|
||||||
|
affiliateOrgIcon: json['affliate_org_icon'] as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -4,6 +4,8 @@ import 'dart:convert';
|
|||||||
import 'dart:async'; // Timer를 사용하기 위해 추가
|
import 'dart:async'; // Timer를 사용하기 위해 추가
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:csp2/common/widgets/upcoming_class_card.dart';
|
import 'package:csp2/common/widgets/upcoming_class_card.dart';
|
||||||
|
import 'package:csp2/common/widgets/course_card.dart';
|
||||||
|
import 'package:csp2/course.dart'; // Course 클래스 import
|
||||||
// import 'package:flutter_native_timezone/flutter_native_timezone.dart'; // 주석 처리된 상태 유지
|
// import 'package:flutter_native_timezone/flutter_native_timezone.dart'; // 주석 처리된 상태 유지
|
||||||
import '../plan_page.dart'; // PlanPage import
|
import '../plan_page.dart'; // PlanPage import
|
||||||
|
|
||||||
@@ -25,7 +27,7 @@ class CaseStudyPlan {
|
|||||||
return CaseStudyPlan(
|
return CaseStudyPlan(
|
||||||
planId: json['casestudy lesson id'] ?? '아이디 없음',
|
planId: json['casestudy lesson id'] ?? '아이디 없음',
|
||||||
planTitle: json['course_name'] ?? '제목 없음',
|
planTitle: json['course_name'] ?? '제목 없음',
|
||||||
planTeacher: json['planTeacher'] ?? '선생님',
|
planTeacher: json['course_description'] ?? '선생님',
|
||||||
thumbnail: json['course_thumbnail'] ?? '',
|
thumbnail: json['course_thumbnail'] ?? '',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -69,6 +71,7 @@ class HomePage extends StatefulWidget {
|
|||||||
|
|
||||||
class _HomePageState extends State<HomePage> {
|
class _HomePageState extends State<HomePage> {
|
||||||
late Future<List<CaseStudyPlan>> _caseStudyPlans;
|
late Future<List<CaseStudyPlan>> _caseStudyPlans;
|
||||||
|
late Future<List<Course>> _newCoursesFuture; // New: Future for new courses
|
||||||
DateTime _currentTime = DateTime.now();
|
DateTime _currentTime = DateTime.now();
|
||||||
String _currentTimeZone = 'Loading timezone...';
|
String _currentTimeZone = 'Loading timezone...';
|
||||||
late Stream<DateTime> _clockStream;
|
late Stream<DateTime> _clockStream;
|
||||||
@@ -93,6 +96,7 @@ class _HomePageState extends State<HomePage> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_caseStudyPlans = _fetchCaseStudyPlans();
|
_caseStudyPlans = _fetchCaseStudyPlans();
|
||||||
|
_newCoursesFuture = _fetchNewCourses(); // Initialize new courses future
|
||||||
_fetchTimezone();
|
_fetchTimezone();
|
||||||
_clockStream = Stream.periodic(const Duration(seconds: 1), (_) {
|
_clockStream = Stream.periodic(const Duration(seconds: 1), (_) {
|
||||||
return DateTime.now();
|
return DateTime.now();
|
||||||
@@ -131,6 +135,24 @@ class _HomePageState extends State<HomePage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New: Fetch new courses from API
|
||||||
|
Future<List<Course>> _fetchNewCourses() async {
|
||||||
|
final response = await http.get(Uri.parse('https://helloworld5-ad2uqhckxq-uc.a.run.app/'));
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final Map<String, dynamic> decodedJson = json.decode(response.body);
|
||||||
|
if (decodedJson.containsKey('data') && decodedJson['data'] is List) {
|
||||||
|
final List<dynamic> coursesJson = decodedJson['data'];
|
||||||
|
return coursesJson
|
||||||
|
.map((jsonItem) => Course.fromJson(jsonItem as Map<String, dynamic>))
|
||||||
|
.toList();
|
||||||
|
} else {
|
||||||
|
throw Exception('Invalid data format for new courses: "data" field is missing or not a list.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load new courses. Status Code: ${response.statusCode}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _fetchTimezone() async {
|
Future<void> _fetchTimezone() async {
|
||||||
try {
|
try {
|
||||||
final String timeZone = DateTime.now().timeZoneName;
|
final String timeZone = DateTime.now().timeZoneName;
|
||||||
@@ -288,6 +310,7 @@ class _HomePageState extends State<HomePage> {
|
|||||||
|
|
||||||
Widget _buildHomeContent() {
|
Widget _buildHomeContent() {
|
||||||
return Column(
|
return Column(
|
||||||
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Padding(
|
Padding(
|
||||||
@@ -311,10 +334,10 @@ class _HomePageState extends State<HomePage> {
|
|||||||
),
|
),
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 8.0), // vertical을 위아래 다르게 조정
|
padding: EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 8.0), // vertical을 위아래 다르게 조정
|
||||||
child: Text('Upcoming Classes', style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold)),
|
child: Text('Upcoming Study', style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold)),
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 250, // 가로 리스트의 높이를 지정합니다.
|
height: 200, // 가로 리스트의 높이를 줄입니다.
|
||||||
child: FutureBuilder<List<CaseStudyPlan>>(
|
child: FutureBuilder<List<CaseStudyPlan>>(
|
||||||
future: _caseStudyPlans,
|
future: _caseStudyPlans,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
@@ -335,11 +358,14 @@ class _HomePageState extends State<HomePage> {
|
|||||||
itemCount: plans.length,
|
itemCount: plans.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final plan = plans[index];
|
final plan = plans[index];
|
||||||
return UpcomingClassCard(
|
return Align(
|
||||||
plan: plan,
|
alignment: Alignment.topCenter, // 항목을 상단 중앙에 정렬
|
||||||
onTap: () {
|
child: UpcomingClassCard(
|
||||||
widget.onNavigateToPlanTab(1);
|
plan: plan,
|
||||||
},
|
onTap: () {
|
||||||
|
widget.onNavigateToPlanTab(1);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -347,10 +373,45 @@ class _HomePageState extends State<HomePage> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// --- ▼▼▼ Find Your New Course 섹션 ▼▼▼ ---
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 8.0),
|
||||||
|
child: Text('Find Your New Course', style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold)),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 175, // 가로 리스트의 높이
|
||||||
|
child: FutureBuilder<List<Course>>(
|
||||||
|
future: _newCoursesFuture,
|
||||||
|
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 new courses: ${snapshot.error}', textAlign: TextAlign.center),
|
||||||
|
));
|
||||||
|
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||||
|
return const Center(child: Text('No new courses available.'));
|
||||||
|
} else {
|
||||||
|
final courses = snapshot.data!;
|
||||||
|
return ListView.builder(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
padding: const EdgeInsets.only(left: 16.0, right: 16.0, top: 8.0),
|
||||||
|
itemCount: courses.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final course = courses[index];
|
||||||
|
return CourseCard(course: course,);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// --- ▲▲▲ Find Your New Course 섹션 끝 ▲▲▲ ---
|
||||||
// --- ▼▼▼ 추천 클래스 섹션 호출 ▼▼▼ ---
|
// --- ▼▼▼ 추천 클래스 섹션 호출 ▼▼▼ ---
|
||||||
// _buildRecommendSection(),
|
// _buildRecommendSection(),
|
||||||
// --- ▲▲▲ 추천 클래스 섹션 호출 끝 ▲▲▲ ---
|
// --- ▲▲▲ 추천 클래스 섹션 호출 끝 ▲▲▲ ---
|
||||||
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -369,4 +430,118 @@ class _HomePageState extends State<HomePage> {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Widget _buildRecommendSection() {
|
||||||
|
if (_isLoadingRecommends) {
|
||||||
|
return const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 20.0),
|
||||||
|
child: Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_hasRecommendError) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 16.0),
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.error_outline, color: Colors.red, size: 40),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Failed to load recommendations.\n$_recommendErrorText',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(color: Colors.redAccent),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
icon: const Icon(Icons.refresh),
|
||||||
|
label: const Text('Retry'),
|
||||||
|
onPressed: _fetchRecommendPlans,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_recommendPlans.isEmpty) {
|
||||||
|
return const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 20.0),
|
||||||
|
child: Center(child: Text('No recommendations available at the moment.')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final currentPlan = _recommendPlans[_currentRecommendIndex];
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 16.0), // 하단 마진 추가
|
||||||
|
padding: const EdgeInsets.all(12.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).cardColor, // 카드 색상 사용
|
||||||
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withOpacity(0.2),
|
||||||
|
spreadRadius: 1,
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'✨ Recommend Classes', // 이모지 추가
|
||||||
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 12.0),
|
||||||
|
|
||||||
|
Text(
|
||||||
|
currentPlan.planName,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
// Test: Plan ID 표시 (디버깅용)
|
||||||
|
// Text('ID: ${currentPlan.planId}', style: TextStyle(fontSize: 10, color: Colors.grey)),
|
||||||
|
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: 16 / 9,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
|
child: currentPlan.thumbnail.isNotEmpty
|
||||||
|
? Image.network(
|
||||||
|
currentPlan.thumbnail,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
|
||||||
|
if (loadingProgress == null) return child;
|
||||||
|
return Container( // 로딩 중 배경색 및 인디케이터 중앙 정렬 개선
|
||||||
|
color: Colors.grey[200],
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
value: loadingProgress.expectedTotalBytes != null
|
||||||
|
? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) {
|
||||||
|
return Container(
|
||||||
|
color: Colors.grey[200],
|
||||||
|
child: const Center(child: Icon(Icons.broken_image, color: Colors.grey, size: 50)),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: Container(
|
||||||
|
color: Colors.grey[200],
|
||||||
|
child: const Center(child: Text('No Image Available', style: TextStyle(color: Colors.grey))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user