fix(refactor): description in topic list screen handling, and add various loading handling widget
This commit is contained in:
parent
e500ed0fa0
commit
b50ee898fe
|
|
@ -1 +1 @@
|
||||||
const String baseUrl = 'https://7333-114-6-25-184.ngrok-free.app/';
|
const String baseUrl = 'http://54.173.167.62/';
|
||||||
|
|
|
||||||
37
lib/core/widgets/loading/shimmer_loading_widget.dart
Normal file
37
lib/core/widgets/loading/shimmer_loading_widget.dart
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:shimmer/shimmer.dart';
|
||||||
|
|
||||||
|
class ShimmerLoadingWidget extends StatelessWidget {
|
||||||
|
final double width;
|
||||||
|
final double height;
|
||||||
|
final BorderRadius? borderRadius;
|
||||||
|
final EdgeInsets? padding;
|
||||||
|
|
||||||
|
const ShimmerLoadingWidget({
|
||||||
|
super.key,
|
||||||
|
required this.width,
|
||||||
|
required this.height,
|
||||||
|
this.borderRadius,
|
||||||
|
this.padding,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: padding ?? EdgeInsets.zero,
|
||||||
|
child: Shimmer.fromColors(
|
||||||
|
baseColor: Colors.grey[300]!,
|
||||||
|
highlightColor: Colors.grey[100]!,
|
||||||
|
period: const Duration(milliseconds: 1500),
|
||||||
|
child: Container(
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: borderRadius ?? BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ import 'package:english_learning/features/history/screens/history_screen.dart';
|
||||||
import 'package:english_learning/features/home/data/card_data.dart';
|
import 'package:english_learning/features/home/data/card_data.dart';
|
||||||
import 'package:english_learning/features/home/provider/completed_topics_provider.dart';
|
import 'package:english_learning/features/home/provider/completed_topics_provider.dart';
|
||||||
import 'package:english_learning/features/home/widgets/progress_card.dart';
|
import 'package:english_learning/features/home/widgets/progress_card.dart';
|
||||||
|
import 'package:english_learning/features/home/widgets/progress_card_loading.dart';
|
||||||
import 'package:english_learning/features/home/widgets/welcome_card.dart';
|
import 'package:english_learning/features/home/widgets/welcome_card.dart';
|
||||||
import 'package:english_learning/features/learning/screens/learning_screen.dart';
|
import 'package:english_learning/features/learning/screens/learning_screen.dart';
|
||||||
import 'package:english_learning/features/settings/modules/edit_profile/screens/edit_profile_screen.dart';
|
import 'package:english_learning/features/settings/modules/edit_profile/screens/edit_profile_screen.dart';
|
||||||
|
|
@ -17,7 +18,6 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:google_nav_bar/google_nav_bar.dart';
|
import 'package:google_nav_bar/google_nav_bar.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:shimmer/shimmer.dart';
|
|
||||||
|
|
||||||
class HomeScreen extends StatefulWidget {
|
class HomeScreen extends StatefulWidget {
|
||||||
const HomeScreen({super.key});
|
const HomeScreen({super.key});
|
||||||
|
|
@ -203,31 +203,6 @@ class _HomeContentState extends State<HomeContent> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildShimmerEffect() {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
|
||||||
child: Shimmer.fromColors(
|
|
||||||
baseColor: Colors.grey[300]!,
|
|
||||||
highlightColor: Colors.grey[100]!,
|
|
||||||
child: Column(
|
|
||||||
children: List.generate(
|
|
||||||
2,
|
|
||||||
(index) => Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 16.0),
|
|
||||||
child: Container(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 150,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildCompletedTopicsContent(CompletedTopicsProvider provider) {
|
Widget _buildCompletedTopicsContent(CompletedTopicsProvider provider) {
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
|
|
@ -438,7 +413,7 @@ class _HomeContentState extends State<HomeContent> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
completedTopicsProvider.isLoading
|
completedTopicsProvider.isLoading
|
||||||
? _buildShimmerEffect()
|
? const ProgressCardLoading()
|
||||||
: completedTopicsProvider.completedTopics.isEmpty
|
: completedTopicsProvider.completedTopics.isEmpty
|
||||||
? _buildNoDataWidget()
|
? _buildNoDataWidget()
|
||||||
: _buildCompletedTopicsContent(
|
: _buildCompletedTopicsContent(
|
||||||
|
|
|
||||||
26
lib/features/home/widgets/progress_card_loading.dart
Normal file
26
lib/features/home/widgets/progress_card_loading.dart
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import 'package:english_learning/core/widgets/loading/shimmer_loading_widget.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ProgressCardLoading extends StatelessWidget {
|
||||||
|
const ProgressCardLoading({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
color: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
elevation: 0,
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: ShimmerLoadingWidget(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 160,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import 'package:english_learning/features/learning/modules/level/screens/level_l
|
||||||
import 'package:english_learning/features/learning/modules/topics/providers/topic_provider.dart';
|
import 'package:english_learning/features/learning/modules/topics/providers/topic_provider.dart';
|
||||||
import 'package:english_learning/features/learning/modules/topics/widgets/topic_card.dart';
|
import 'package:english_learning/features/learning/modules/topics/widgets/topic_card.dart';
|
||||||
import 'package:english_learning/core/utils/styles/theme.dart';
|
import 'package:english_learning/core/utils/styles/theme.dart';
|
||||||
|
import 'package:english_learning/features/learning/modules/topics/widgets/topic_card_loading.dart';
|
||||||
import 'package:english_learning/features/learning/provider/section_provider.dart';
|
import 'package:english_learning/features/learning/provider/section_provider.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
@ -53,6 +54,102 @@ class _TopicsListScreenState extends State<TopicsListScreen> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tambahkan method baru di dalam _TopicsListScreenState
|
||||||
|
void _showSectionDescriptionDialog(BuildContext context, String description) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return Dialog(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
elevation: 0,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
color: AppColors.whiteColor,
|
||||||
|
shape: BoxShape.rectangle,
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black26,
|
||||||
|
blurRadius: 10.0,
|
||||||
|
offset: Offset(0.0, 10.0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 16, horizontal: 16),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: AppColors.blueColor,
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(16),
|
||||||
|
topRight: Radius.circular(16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'Section Description',
|
||||||
|
style: AppTextStyles.whiteTextStyle.copyWith(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Content
|
||||||
|
Flexible(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Text(
|
||||||
|
description,
|
||||||
|
style: AppTextStyles.greyTextStyle.copyWith(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w300,
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.justify,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppColors.primaryColor,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 12, horizontal: 20),
|
||||||
|
),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
icon: const Icon(Icons.close, color: Colors.white),
|
||||||
|
label: const Text(
|
||||||
|
'Close',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final sectionProvider = Provider.of<SectionProvider>(context);
|
final sectionProvider = Provider.of<SectionProvider>(context);
|
||||||
|
|
@ -71,6 +168,7 @@ class _TopicsListScreenState extends State<TopicsListScreen> {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
),
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
flexibleSpace: Container(
|
flexibleSpace: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|
@ -87,14 +185,13 @@ class _TopicsListScreenState extends State<TopicsListScreen> {
|
||||||
_getFullImageUrl(selectedSection.thumbnail ?? ''),
|
_getFullImageUrl(selectedSection.thumbnail ?? ''),
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 115,
|
height: 140,
|
||||||
errorBuilder: (context, error, stackTrace) {
|
errorBuilder: (context, error, stackTrace) {
|
||||||
print('Error loading image: $error');
|
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 115,
|
height: 140,
|
||||||
color: Colors.grey[300],
|
color: Colors.grey[300],
|
||||||
child: Icon(
|
child: const Icon(
|
||||||
Icons.image_not_supported,
|
Icons.image_not_supported,
|
||||||
color: Colors.grey,
|
color: Colors.grey,
|
||||||
),
|
),
|
||||||
|
|
@ -112,12 +209,18 @@ class _TopicsListScreenState extends State<TopicsListScreen> {
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 115,
|
height: 140,
|
||||||
color: AppColors.blackColor.withOpacity(0.5),
|
color: AppColors.blackColor.withOpacity(0.7),
|
||||||
),
|
),
|
||||||
Center(
|
Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 42.0),
|
padding: const EdgeInsets.symmetric(vertical: 32.0),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
selectedSection.description,
|
selectedSection.description,
|
||||||
style: AppTextStyles.whiteTextStyle.copyWith(
|
style: AppTextStyles.whiteTextStyle.copyWith(
|
||||||
|
|
@ -125,22 +228,82 @@ class _TopicsListScreenState extends State<TopicsListScreen> {
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
maxLines: 3,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => _showSectionDescriptionDialog(
|
||||||
|
context, selectedSection.description),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8, horizontal: 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Read More',
|
||||||
|
style: AppTextStyles.whiteTextStyle.copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white.withOpacity(0.8),
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
decorationColor: Colors.white.withOpacity(0.8),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
// const SizedBox(height: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
child: Consumer<TopicProvider>(
|
child: Consumer<TopicProvider>(
|
||||||
builder: (context, topicProvider, _) {
|
builder: (context, topicProvider, _) {
|
||||||
|
if (topicProvider.isLoading) {
|
||||||
|
return ListView.builder(
|
||||||
|
padding: const EdgeInsets.only(top: 8),
|
||||||
|
itemCount: 6,
|
||||||
|
itemBuilder: (context, index) => const TopicCardLoading(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topicProvider.error != null) {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Failed to load topics',
|
||||||
|
style: AppTextStyles.greyTextStyle,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _fetchTopics,
|
||||||
|
child: const Text('Retry'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (topicProvider.topics.isEmpty) {
|
if (topicProvider.topics.isEmpty) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return Center(
|
||||||
} else if (topicProvider.error != null) {
|
child: Text(
|
||||||
return const Center(child: Text('No topics available'));
|
'No topics available',
|
||||||
} else {
|
style: AppTextStyles.greyTextStyle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
itemCount: topicProvider.topics.length,
|
itemCount: topicProvider.topics.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
|
|
@ -163,7 +326,6 @@ class _TopicsListScreenState extends State<TopicsListScreen> {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,14 @@ class TopicCard extends StatelessWidget {
|
||||||
final String description;
|
final String description;
|
||||||
final bool isCompleted;
|
final bool isCompleted;
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
|
final bool isLoading;
|
||||||
const TopicCard({
|
const TopicCard({
|
||||||
super.key,
|
super.key,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.description,
|
required this.description,
|
||||||
required this.isCompleted,
|
required this.isCompleted,
|
||||||
|
this.isLoading = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -52,7 +54,7 @@ class TopicCard extends StatelessWidget {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
maxLines: 4,
|
maxLines: 3,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -63,8 +65,7 @@ class TopicCard extends StatelessWidget {
|
||||||
isCompleted
|
isCompleted
|
||||||
? Icons.check_circle
|
? Icons.check_circle
|
||||||
: Icons.radio_button_unchecked,
|
: Icons.radio_button_unchecked,
|
||||||
color:
|
color: AppColors.blueColor,
|
||||||
isCompleted ? AppColors.blueColor : AppColors.greyColor,
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
import 'package:english_learning/core/widgets/loading/shimmer_loading_widget.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class TopicCardLoading extends StatelessWidget {
|
||||||
|
const TopicCardLoading({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
color: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
elevation: 0,
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Title shimmer
|
||||||
|
ShimmerLoadingWidget(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 16,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
// First line of description
|
||||||
|
ShimmerLoadingWidget(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 12,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
|
),
|
||||||
|
// Second line of description (shorter)
|
||||||
|
ShimmerLoadingWidget(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.6,
|
||||||
|
height: 12,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
// Completion status icon shimmer
|
||||||
|
ShimmerLoadingWidget(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
borderRadius: BorderRadius.circular(12), // Make it circular
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ import 'package:english_learning/features/auth/provider/user_provider.dart';
|
||||||
import 'package:english_learning/features/learning/provider/section_provider.dart';
|
import 'package:english_learning/features/learning/provider/section_provider.dart';
|
||||||
import 'package:english_learning/features/learning/widgets/section_card.dart';
|
import 'package:english_learning/features/learning/widgets/section_card.dart';
|
||||||
import 'package:english_learning/features/learning/modules/topics/screens/topics_list_screen.dart';
|
import 'package:english_learning/features/learning/modules/topics/screens/topics_list_screen.dart';
|
||||||
import 'package:english_learning/features/learning/widgets/section_card_shimmer.dart';
|
import 'package:english_learning/features/learning/widgets/section_card_loading.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:english_learning/core/utils/styles/theme.dart';
|
import 'package:english_learning/core/utils/styles/theme.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
@ -67,7 +67,12 @@ class _LearningScreenState extends State<LearningScreen> {
|
||||||
child: Consumer<SectionProvider>(
|
child: Consumer<SectionProvider>(
|
||||||
builder: (context, sectionProvider, _) {
|
builder: (context, sectionProvider, _) {
|
||||||
if (sectionProvider.isLoading) {
|
if (sectionProvider.isLoading) {
|
||||||
return _buildShimmerLoading();
|
return ListView.builder(
|
||||||
|
padding: const EdgeInsets.only(top: 8),
|
||||||
|
itemCount: 6,
|
||||||
|
itemBuilder: (context, index) =>
|
||||||
|
const SectionCardLoading(),
|
||||||
|
);
|
||||||
} else if (sectionProvider.error != null) {
|
} else if (sectionProvider.error != null) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -97,7 +102,7 @@ class _LearningScreenState extends State<LearningScreen> {
|
||||||
itemCount: sectionProvider.sections.length,
|
itemCount: sectionProvider.sections.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final section = sectionProvider.sections[index];
|
final section = sectionProvider.sections[index];
|
||||||
return LearningCard(
|
return SectionCard(
|
||||||
section: section,
|
section: section,
|
||||||
onTap: () => Navigator.push(
|
onTap: () => Navigator.push(
|
||||||
context,
|
context,
|
||||||
|
|
@ -120,52 +125,4 @@ class _LearningScreenState extends State<LearningScreen> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildShimmerLoading() {
|
|
||||||
return ListView.builder(
|
|
||||||
itemCount: 5, // Misalnya, kita menampilkan 5 shimmer items
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return ShimmerWidget(
|
|
||||||
child: Container(
|
|
||||||
margin: const EdgeInsets.only(bottom: 16),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 80,
|
|
||||||
height: 80,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.grey[300],
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
height: 20,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.grey[300],
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Container(
|
|
||||||
height: 20,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.grey[300],
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,24 @@
|
||||||
import 'package:english_learning/core/services/constants.dart';
|
import 'package:english_learning/core/services/constants.dart';
|
||||||
|
import 'package:english_learning/core/widgets/loading/shimmer_loading_widget.dart';
|
||||||
import 'package:english_learning/features/learning/modules/model/section_model.dart';
|
import 'package:english_learning/features/learning/modules/model/section_model.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:english_learning/core/utils/styles/theme.dart';
|
import 'package:english_learning/core/utils/styles/theme.dart';
|
||||||
|
|
||||||
class LearningCard extends StatefulWidget {
|
class SectionCard extends StatefulWidget {
|
||||||
final Section section;
|
final Section section;
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
const LearningCard({
|
const SectionCard({
|
||||||
super.key,
|
super.key,
|
||||||
required this.section,
|
required this.section,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<LearningCard> createState() => _LearningCardState();
|
State<SectionCard> createState() => _SectionCardState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LearningCardState extends State<LearningCard>
|
class _SectionCardState extends State<SectionCard>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
String _getFullImageUrl(String thumbnail) {
|
String _getFullImageUrl(String thumbnail) {
|
||||||
if (thumbnail.startsWith('http')) {
|
if (thumbnail.startsWith('http')) {
|
||||||
|
|
@ -64,11 +65,10 @@ class _LearningCardState extends State<LearningCard>
|
||||||
},
|
},
|
||||||
loadingBuilder: (context, child, loadingProgress) {
|
loadingBuilder: (context, child, loadingProgress) {
|
||||||
if (loadingProgress == null) return child;
|
if (loadingProgress == null) return child;
|
||||||
return Container(
|
return ShimmerLoadingWidget(
|
||||||
width: 90,
|
width: 90,
|
||||||
height: 104,
|
height: 104,
|
||||||
color: Colors.grey[300],
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: Center(child: CircularProgressIndicator()),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
|
|
|
||||||
59
lib/features/learning/widgets/section_card_loading.dart
Normal file
59
lib/features/learning/widgets/section_card_loading.dart
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
import 'package:english_learning/core/widgets/loading/shimmer_loading_widget.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SectionCardLoading extends StatelessWidget {
|
||||||
|
const SectionCardLoading({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
color: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
elevation: 1,
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12.0),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ShimmerLoadingWidget(
|
||||||
|
width: 90,
|
||||||
|
height: 104,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Title shimmer
|
||||||
|
ShimmerLoadingWidget(
|
||||||
|
width: 200,
|
||||||
|
height: 16,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
//description
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
for (int i = 0; i < 3; i++) ...[
|
||||||
|
const ShimmerLoadingWidget(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 12,
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(4)),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:shimmer/shimmer.dart';
|
|
||||||
|
|
||||||
class ShimmerWidget extends StatelessWidget {
|
|
||||||
final Widget child;
|
|
||||||
|
|
||||||
const ShimmerWidget({
|
|
||||||
super.key,
|
|
||||||
required this.child,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Shimmer.fromColors(
|
|
||||||
baseColor: Colors.grey[300]!,
|
|
||||||
highlightColor: Colors.grey[100]!,
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user