Compare commits

..

No commits in common. "7ba660a4ebe38de23e4034db42a9dfebb90e2cc7" and "e6ce79528b605c85f82fca2dc6f9287556773868" have entirely different histories.

17 changed files with 208 additions and 552 deletions

View File

@ -1,3 +1,3 @@
{ {
"dart.flutterSdkPath": "C:\\tools\\flutter" "dart.flutterSdkPath": ".fvm/versions/3.27.0-0.2.pre"
} }

101
README.md
View File

@ -1,97 +1,16 @@
<p align="center"> # english_learning
<img src="assets/logo.png" alt="SEALS Logo" width="200"/>
</p>
# SEALS - Smart English Adaptive Learning System A new Flutter project.
## Overview ## Getting Started
SEALS refers to a personalized approach to language education that adjusts to the individual learner's needs, pace, and skill level. This project is a starting point for a Flutter application.
## System Requirements A few resources to get you started if this is your first Flutter project:
- Flutter version 3.19.0 or higher - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- Dart version 3.3.0 or higher - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
- Android Studio / VS Code
- Android SDK version 23 (Android 7.0) or higher
- Minimum 2GB RAM
- 500MB free storage space
## Installation For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
1. Clone the repository: samples, guidance on mobile development, and a full API reference.
```bash
git clone https://gitlab.com/profile-image/kedaireka/polinema-adapative-learning/mobile-adaptive-learning.git
```
2. Navigate to project directory:
```bash
cd mobile-adaptive-learning
```
3. Install dependencies:
```bash
flutter pub get
```
4. Run the application:
```bash
flutter run
```
## Dependencies
```yaml
dependencies:
audioplayers: ^6.1.0
bootstrap_icons: ^1.11.3
cached_network_image: ^3.4.1
carousel_slider: ^5.0.0
cupertino_icons: ^1.0.8
dio: ^5.7.0
flick_video_player: ^0.9.0
flutter:
sdk: flutter
flutter_inappwebview: ^6.0.0
flutter_secure_storage: ^9.2.2
flutter_svg: ^2.0.10+1
flutter_widget_from_html: ^0.15.2
google_fonts: ^6.2.1
google_nav_bar: ^5.0.6
html: ^0.15.4
image_picker: ^1.1.2
intl: ^0.19.0
jwt_decoder: ^2.0.1
provider: ^6.1.2
shared_preferences: ^2.3.2
shimmer: ^3.0.0
video_player: ^2.9.1
youtube_player_flutter: ^9.1.1
```
### Build Release
#### Android
```bash
# Generate Android App Bundle
flutter build appbundle --release
# Generate APK
flutter build apk --release
```
## Development Team
- Naresh Pratista - Mobile App Developer - [nareshpratista.contact@gmail.com](mailto:nareshpratista.contact@gmail.com)
- Diah Putri Nofianti - UI/UX Designer - [diahputrinofianti@gmail.com](mailto:diahputrinofianti@gmail.com)
- Elang Putra Adam - Backend Developer - [elangptra17@gmail.com](mailto:elangptra17@gmail.com)
## Acknowledgments
- [Flutter](https://flutter.dev/) - UI framework
- [Provider](https://pub.dev/packages/provider) - State management

View File

@ -1,12 +1,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/>
<application <application
android:label="SEALS" android:label="SEALS"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher">
android:usesCleartextTraffic="true">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"

View File

@ -1,3 +1,3 @@
const String baseUrl = 'https://api.seals.id/'; const String baseUrl = 'http://54.173.167.62/';
const String mediaUrl = 'https://api.seals.id/api/uploads/'; const String mediaUrl = 'http://54.173.167.62/api/uploads/';

View File

@ -9,7 +9,7 @@ class DioClient {
DioClient() { DioClient() {
_dio.options.baseUrl = baseUrl; _dio.options.baseUrl = baseUrl;
_dio.options.connectTimeout = const Duration(seconds: 10); _dio.options.connectTimeout = const Duration(seconds: 10);
_dio.options.receiveTimeout = const Duration(seconds: 30); _dio.options.receiveTimeout = const Duration(seconds: 15);
} }
Future<Response> refreshAccessToken(String refreshToken) async { Future<Response> refreshAccessToken(String refreshToken) async {

View File

@ -1,37 +0,0 @@
import 'package:flutter/material.dart';
mixin BackHandlerMixin<T extends StatefulWidget> on State<T> {
DateTime? currentBackPressTime;
Future<bool> onWillPop() async {
DateTime now = DateTime.now();
if (currentBackPressTime == null ||
now.difference(currentBackPressTime!) > const Duration(seconds: 2)) {
currentBackPressTime = now;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Press back again to exit'),
duration: Duration(seconds: 2),
backgroundColor: Colors.black87,
behavior: SnackBarBehavior.floating,
margin: EdgeInsets.all(16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
),
);
return false;
}
return true;
}
Widget wrapWithBackHandler({required Widget child}) {
return WillPopScope(
onWillPop: onWillPop,
child: child,
);
}
}

View File

@ -19,7 +19,7 @@ class CustomSnackBar {
behavior: SnackBarBehavior.floating, behavior: SnackBarBehavior.floating,
margin: const EdgeInsets.all(16), margin: const EdgeInsets.all(16),
duration: duration, duration: duration,
dismissDirection: DismissDirection.vertical, dismissDirection: DismissDirection.horizontal,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
@ -46,7 +46,7 @@ class CustomSnackBar {
behavior: behavior, behavior: behavior,
margin: const EdgeInsets.all(16), margin: const EdgeInsets.all(16),
duration: duration, duration: duration,
dismissDirection: DismissDirection.vertical, dismissDirection: DismissDirection.horizontal,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),

View File

@ -37,11 +37,8 @@ class HistoryModel {
} }
String get formattedDate { String get formattedDate {
if (studentFinish == null) { return studentFinish != null
return 'N/A'; ? DateFormat('yyyy-MM-dd HH:mm').format(studentFinish!)
} : 'N/A';
final wibDateTime = studentFinish!.add(const Duration(hours: 7));
return '${DateFormat('yyyy-MM-dd HH:mm').format(wibDateTime)} WIB';
} }
} }

View File

@ -62,6 +62,7 @@ class _HistoryScreenState extends State<HistoryScreen> {
} }
} }
// Tambahkan method untuk shimmer loading
Widget _buildShimmerLoading() { Widget _buildShimmerLoading() {
return ListView.builder( return ListView.builder(
itemCount: 5, itemCount: 5,
@ -129,20 +130,24 @@ class _HistoryScreenState extends State<HistoryScreen> {
} }
Widget _buildContent(BuildContext context, HistoryProvider historyProvider) { Widget _buildContent(BuildContext context, HistoryProvider historyProvider) {
// Prioritaskan initial loading
if (_isInitialLoading || historyProvider.isLoading) { if (_isInitialLoading || historyProvider.isLoading) {
return _buildShimmerLoading(); return _buildShimmerLoading();
} }
// Tangani error
if (historyProvider.error != null) { if (historyProvider.error != null) {
return isNotFoundError(historyProvider.error!) return isNotFoundError(historyProvider.error!)
? _buildEmptyState(context) ? _buildEmptyState(context)
: _buildErrorState(context, historyProvider.error!); : _buildErrorState(context, historyProvider.error!);
} }
// Tampilkan empty state jika tidak ada history
if (historyProvider.historyModel.isEmpty) { if (historyProvider.historyModel.isEmpty) {
return _buildEmptyState(context); return _buildEmptyState(context);
} }
// Tampilkan daftar history dengan refresh indicator
return RefreshIndicator( return RefreshIndicator(
onRefresh: _refreshHistory, onRefresh: _refreshHistory,
child: AnimatedSwitcher( child: AnimatedSwitcher(
@ -234,54 +239,46 @@ class _HistoryScreenState extends State<HistoryScreen> {
} }
Widget _buildEmptyState(BuildContext context) { Widget _buildEmptyState(BuildContext context) {
return RefreshIndicator( return Container(
onRefresh: _refreshHistory, decoration: BoxDecoration(
child: ListView( borderRadius: BorderRadius.circular(12),
physics: const AlwaysScrollableScrollPhysics(), color: AppColors.whiteColor,
children: [ ),
Container( child: Padding(
decoration: BoxDecoration( padding: const EdgeInsets.all(16.0),
borderRadius: BorderRadius.circular(12), child: Column(
color: AppColors.whiteColor, mainAxisSize: MainAxisSize.min,
), children: [
child: Padding( Text(
padding: const EdgeInsets.all(16.0), 'Still New?',
child: Column( style: AppTextStyles.blackTextStyle.copyWith(
mainAxisSize: MainAxisSize.min, fontSize: 18,
children: [ fontWeight: FontWeight.bold,
Text(
'Still New?',
style: AppTextStyles.blackTextStyle.copyWith(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Text(
'Begin your journey!',
style: AppTextStyles.disableTextStyle.copyWith(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 16),
SvgPicture.asset(
'lib/features/history/assets/images/is_empty_illustration.svg',
width: 160,
),
const SizedBox(height: 32),
GlobalButton(
text: 'Explore',
backgroundColor: AppColors.yellowButtonColor,
textColor: AppColors.blackColor,
onPressed: () {
HomeScreen.navigateToTab(context, 1);
},
),
],
), ),
), ),
), Text(
], 'Begin your journey!',
style: AppTextStyles.disableTextStyle.copyWith(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 16),
SvgPicture.asset(
'lib/features/history/assets/images/is_empty_illustration.svg',
width: 160,
),
const SizedBox(height: 32),
GlobalButton(
text: 'Explore',
backgroundColor: AppColors.yellowButtonColor,
textColor: AppColors.blackColor,
onPressed: () {
HomeScreen.navigateToTab(context, 1);
},
),
],
),
), ),
); );
} }

View File

@ -1,6 +1,5 @@
import 'package:bootstrap_icons/bootstrap_icons.dart'; import 'package:bootstrap_icons/bootstrap_icons.dart';
import 'package:carousel_slider/carousel_slider.dart'; import 'package:carousel_slider/carousel_slider.dart';
import 'package:english_learning/core/widgets/back_handler.dart';
import 'package:english_learning/core/widgets/custom_button.dart'; import 'package:english_learning/core/widgets/custom_button.dart';
import 'package:english_learning/features/auth/provider/user_provider.dart'; import 'package:english_learning/features/auth/provider/user_provider.dart';
import 'package:english_learning/features/history/provider/history_provider.dart'; import 'package:english_learning/features/history/provider/history_provider.dart';
@ -40,7 +39,7 @@ class HomeScreen extends StatefulWidget {
} }
} }
class _HomeScreenState extends State<HomeScreen> with BackHandlerMixin { class _HomeScreenState extends State<HomeScreen> {
final PageController _pageController = PageController(); final PageController _pageController = PageController();
int _selectedIndex = 0; int _selectedIndex = 0;
@ -60,84 +59,83 @@ class _HomeScreenState extends State<HomeScreen> with BackHandlerMixin {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return wrapWithBackHandler( return DefaultTabController(
child: DefaultTabController( length: 4,
length: 4, child: Scaffold(
child: Scaffold( body: Container(
body: Container( color: AppColors.bgSoftColor,
color: AppColors.bgSoftColor, child: PageView(
child: PageView( physics: const NeverScrollableScrollPhysics(),
physics: const NeverScrollableScrollPhysics(), controller: _pageController,
controller: _pageController, children: _screens,
children: _screens, onPageChanged: (index) {
onPageChanged: (index) { setState(() {
setState(() { _selectedIndex = index;
_selectedIndex = index; });
}); },
}, ),
),
bottomNavigationBar: Container(
decoration: BoxDecoration(
gradient: AppColors.gradientTheme,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
), ),
), ),
bottomNavigationBar: Container( child: Padding(
decoration: BoxDecoration( padding: const EdgeInsets.only(
gradient: AppColors.gradientTheme, top: 20,
borderRadius: const BorderRadius.only( bottom: 24,
topLeft: Radius.circular(20), left: 16,
topRight: Radius.circular(20), right: 16,
),
), ),
child: Padding( child: GNav(
padding: const EdgeInsets.only( activeColor: AppColors.blueColor,
top: 20, tabBackgroundColor: AppColors.whiteColor,
bottom: 24, tabBorderRadius: 100,
left: 16, color: AppColors.whiteColor,
right: 16, iconSize: 20,
), gap: 8,
child: GNav( selectedIndex: _selectedIndex,
activeColor: AppColors.blueColor, onTabChange: (index) async {
tabBackgroundColor: AppColors.whiteColor, if (index == 2 && _selectedIndex != 2) {
tabBorderRadius: 100, // Only if switching TO history tab
color: AppColors.whiteColor, final historyProvider =
iconSize: 20, Provider.of<HistoryProvider>(context, listen: false);
gap: 8, final userProvider =
selectedIndex: _selectedIndex, Provider.of<UserProvider>(context, listen: false);
onTabChange: (index) async {
if (index == 2 && _selectedIndex != 2) {
final historyProvider =
Provider.of<HistoryProvider>(context, listen: false);
final userProvider =
Provider.of<UserProvider>(context, listen: false);
if (!historyProvider.isInitialized) { if (!historyProvider.isInitialized) {
await historyProvider await historyProvider
.loadInitialData(userProvider.jwtToken!); .loadInitialData(userProvider.jwtToken!);
}
} }
}
navigateToTab(index); navigateToTab(index);
}, },
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: 16,
vertical: 8, vertical: 8,
),
tabs: const [
GButton(
icon: BootstrapIcons.house,
text: 'Home',
),
GButton(
icon: BootstrapIcons.book,
text: 'Learning',
),
GButton(
icon: BootstrapIcons.clock_history,
text: 'History',
),
GButton(
icon: BootstrapIcons.gear,
text: 'Settings',
),
],
), ),
tabs: const [
GButton(
icon: BootstrapIcons.house,
text: 'Home',
),
GButton(
icon: BootstrapIcons.book,
text: 'Learning',
),
GButton(
icon: BootstrapIcons.clock_history,
text: 'History',
),
GButton(
icon: BootstrapIcons.gear,
text: 'Settings',
),
],
), ),
), ),
), ),
@ -171,8 +169,10 @@ class _HomeContentState extends State<HomeContent> {
Provider.of<CompletedTopicsProvider>(context, listen: false); Provider.of<CompletedTopicsProvider>(context, listen: false);
try { try {
// Reset data sebelum fetch
completedTopicsProvider.resetData(); completedTopicsProvider.resetData();
// Fetch completed topics
await completedTopicsProvider await completedTopicsProvider
.fetchCompletedTopics(userProvider.jwtToken!); .fetchCompletedTopics(userProvider.jwtToken!);
} catch (e) { } catch (e) {

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 60 KiB

View File

@ -1,7 +1,6 @@
import 'package:english_learning/features/learning/modules/result/widgets/complete_result_widget.dart'; import 'package:english_learning/features/learning/modules/result/widgets/complete_result_widget.dart';
import 'package:english_learning/features/learning/modules/result/widgets/down_result_widget.dart'; import 'package:english_learning/features/learning/modules/result/widgets/down_result_widget.dart';
import 'package:english_learning/features/learning/modules/result/widgets/jump_result_widget.dart'; import 'package:english_learning/features/learning/modules/result/widgets/jump_result_widget.dart';
import 'package:english_learning/features/learning/modules/result/widgets/stay_result_widget.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';
@ -27,49 +26,8 @@ class ResultScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget buildResultWidget() { // final mediaQuery = MediaQuery.of(context);
if (isCompleted) { // final screenHeight = mediaQuery.size.height;
return CompleteResultWidget(
currentLevel: currentLevel,
score: score,
stdLearningId: stdLearningId ?? '',
topicId: topicId,
topicTitle: topicTitle,
);
}
if (currentLevel == nextLevel) {
return StayResultWidget(
nextLevel: nextLevel,
score: score,
stdLearningId: stdLearningId ?? '',
topicId: topicId,
topicTitle: topicTitle,
);
}
// Menggunakan compareTo untuk membandingkan level
final currentLevelNum = _extractLevelNumber(currentLevel);
final nextLevelNum = _extractLevelNumber(nextLevel);
if (currentLevelNum < nextLevelNum) {
return JumpResultWidget(
nextLevel: nextLevel,
score: score,
stdLearningId: stdLearningId ?? '',
topicId: topicId,
topicTitle: topicTitle,
);
} else {
return DownResultWidget(
nextLevel: nextLevel,
score: score,
stdLearningId: stdLearningId ?? '',
topicId: topicId,
topicTitle: topicTitle,
);
}
}
return Scaffold( return Scaffold(
backgroundColor: AppColors.bgSoftColor, backgroundColor: AppColors.bgSoftColor,
@ -78,19 +36,33 @@ class ResultScreen extends StatelessWidget {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
buildResultWidget(), if (isCompleted)
CompleteResultWidget(
currentLevel: currentLevel,
score: score,
stdLearningId: stdLearningId ?? '',
topicId: topicId, // Tambahkan ini
topicTitle: topicTitle, // Tambahkan ini
)
else if (nextLevel != currentLevel)
JumpResultWidget(
nextLevel: nextLevel,
score: score,
stdLearningId: stdLearningId ?? '',
topicId: topicId, // Tambahkan ini
topicTitle: topicTitle, // Tambahkan ini
)
else
DownResultWidget(
nextLevel: nextLevel,
score: score,
stdLearningId: stdLearningId ?? '',
topicId: topicId, // Tambahkan ini
topicTitle: topicTitle, // Tambahkan ini
),
], ],
), ),
), ),
); );
} }
int _extractLevelNumber(String level) {
final regex = RegExp(r'\d+');
final match = regex.firstMatch(level);
if (match != null) {
return int.parse(match.group(0)!);
}
return 0;
}
} }

View File

@ -38,16 +38,23 @@ class CompleteResultWidget extends StatelessWidget {
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
Text( Text(
'You conquered LEVEL $currentLevel with a $score/100! You\'re a rock star!', 'Congratulations!',
style: AppTextStyles.tetriaryTextStyle.copyWith( style: AppTextStyles.disableTextStyle.copyWith(
fontSize: 14,
fontWeight: FontWeight.w400,
),
),
const SizedBox(height: 12),
Text(
'Way to go! You conquered LEVEL $currentLevel with a $score/100! You\'re a rock star!',
style: AppTextStyles.disableTextStyle.copyWith(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
), ),
textAlign: TextAlign.center,
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
GlobalButton( GlobalButton(
text: 'Topic Finished!', text: 'Discover More',
onPressed: () { onPressed: () {
Navigator.pushReplacement( Navigator.pushReplacement(
context, context,

View File

@ -55,15 +55,12 @@ class DownResultWidget extends StatelessWidget {
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
), ),
textAlign: TextAlign.center,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'$nextLevel', '$nextLevel',
style: AppTextStyles.redTextStyle.copyWith( style: AppTextStyles.redTextStyle
fontSize: 20, .copyWith(fontSize: 20, fontWeight: FontWeight.w900),
fontWeight: FontWeight.w900,
),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
GlobalButton( GlobalButton(
@ -74,8 +71,8 @@ class DownResultWidget extends StatelessWidget {
MaterialPageRoute( MaterialPageRoute(
builder: (context) => FeedbackScreen( builder: (context) => FeedbackScreen(
stdLearningId: stdLearningId, stdLearningId: stdLearningId,
topicId: topicId, topicId: topicId, // Tambahkan ini
topicTitle: topicTitle, topicTitle: topicTitle, // Tambahkan ini
), ),
), ),
); );

View File

@ -51,11 +51,10 @@ class JumpResultWidget extends StatelessWidget {
const SizedBox(height: 24), const SizedBox(height: 24),
Text( Text(
'Great job! You can jump to ...', 'Great job! You can jump to ...',
style: AppTextStyles.tetriaryTextStyle.copyWith( style: AppTextStyles.disableTextStyle.copyWith(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
), ),
textAlign: TextAlign.center,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
@ -72,8 +71,8 @@ class JumpResultWidget extends StatelessWidget {
MaterialPageRoute( MaterialPageRoute(
builder: (context) => FeedbackScreen( builder: (context) => FeedbackScreen(
stdLearningId: stdLearningId, stdLearningId: stdLearningId,
topicId: topicId, topicId: topicId, // Tambahkan ini
topicTitle: topicTitle, topicTitle: topicTitle, // Tambahkan ini
), ),
), ),
); );

View File

@ -1,87 +0,0 @@
import 'package:english_learning/core/utils/styles/theme.dart';
import 'package:english_learning/core/widgets/global_button.dart';
import 'package:english_learning/features/learning/modules/feedback/screens/feedback_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class StayResultWidget extends StatelessWidget {
final String? nextLevel;
final int? score;
final String stdLearningId;
final String topicId;
final String topicTitle;
const StayResultWidget({
super.key,
required this.nextLevel,
required this.score,
required this.stdLearningId,
required this.topicId,
required this.topicTitle,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'Your Result',
style: AppTextStyles.blackTextStyle.copyWith(
fontSize: 25,
fontWeight: FontWeight.w900,
),
),
Text(
'$score/100',
style: AppTextStyles.blackTextStyle.copyWith(
fontSize: 20,
fontWeight: FontWeight.w900,
),
),
],
),
const SizedBox(height: 24),
SvgPicture.asset(
'lib/features/learning/modules/result/assets/images/result_stay_illustration.svg',
width: 259,
),
const SizedBox(height: 24),
Text(
'Learning is a journey, let\'s explore this level further to deepen your knowledge.',
style: AppTextStyles.tetriaryTextStyle.copyWith(
fontSize: 14,
fontWeight: FontWeight.w400,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'$nextLevel',
style: AppTextStyles.blackTextStyle.copyWith(
fontSize: 20,
fontWeight: FontWeight.w900,
),
),
const SizedBox(height: 24),
GlobalButton(
text: 'Continue',
onPressed: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => FeedbackScreen(
stdLearningId: stdLearningId,
topicId: topicId, // Tambahkan ini
topicTitle: topicTitle, // Tambahkan ini
),
),
);
},
)
],
);
}
}

View File

@ -35,7 +35,8 @@ class _LearningScreenState extends State<LearningScreen>
Provider.of<SectionProvider>(context, listen: false); Provider.of<SectionProvider>(context, listen: false);
try { try {
sectionProvider.resetData(); // Reset data sebelum fetch
sectionProvider.resetData(); // Tambahkan method ini di SectionProvider
final token = await userProvider.getValidToken(); final token = await userProvider.getValidToken();
if (token != null) { if (token != null) {
@ -60,6 +61,26 @@ class _LearningScreenState extends State<LearningScreen>
} }
} }
// Future<void> _refreshSections() async {
// final userProvider = Provider.of<UserProvider>(context, listen: false);
// final sectionProvider =
// Provider.of<SectionProvider>(context, listen: false);
// try {
// final token = await userProvider.getValidToken();
// if (token != null) {
// await sectionProvider.fetchSections(token);
// }
// } catch (e) {
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(
// content: Text('Failed to refresh sections: $e'),
// backgroundColor: Colors.red,
// ),
// );
// }
// }
Widget _buildErrorWidget(String error) { Widget _buildErrorWidget(String error) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
@ -130,6 +151,7 @@ class _LearningScreenState extends State<LearningScreen>
Expanded( Expanded(
child: Consumer<SectionProvider>( child: Consumer<SectionProvider>(
builder: (context, sectionProvider, _) { builder: (context, sectionProvider, _) {
// Prioritaskan loading state
if (_isInitialLoading || sectionProvider.isLoading) { if (_isInitialLoading || sectionProvider.isLoading) {
return ListView.builder( return ListView.builder(
padding: const EdgeInsets.only(top: 8), padding: const EdgeInsets.only(top: 8),
@ -139,10 +161,12 @@ class _LearningScreenState extends State<LearningScreen>
); );
} }
// Tampilkan error jika ada
if (sectionProvider.error != null) { if (sectionProvider.error != null) {
return _buildErrorWidget(sectionProvider.error!); return _buildErrorWidget(sectionProvider.error!);
} }
// Tampilkan sections atau pesan jika kosong
if (sectionProvider.sections.isEmpty) { if (sectionProvider.sections.isEmpty) {
return RefreshIndicator( return RefreshIndicator(
onRefresh: _initializeSections, onRefresh: _initializeSections,
@ -178,6 +202,7 @@ class _LearningScreenState extends State<LearningScreen>
); );
} }
// Tampilkan daftar sections
return RefreshIndicator( return RefreshIndicator(
onRefresh: _initializeSections, onRefresh: _initializeSections,
child: ListView.builder( child: ListView.builder(