diff --git a/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..6e224f6
Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..2089248
Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..59e3a69
Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..83f5c0d
Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..1c84ad0
Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..c79c58a
--- /dev/null
+++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
index 9dec1a7..33b3d8e 100644
Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
index 17987b7..79f8e52 100644
Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index 09d4391..1dc5dfb 100644
Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index d5f1c8d..6e39eda 100644
Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index 4d6372e..1a15184 100644
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..c5d5899
--- /dev/null
+++ b/android/app/src/main/res/values/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #FFFFFF
+
\ No newline at end of file
diff --git a/assets/logo.png b/assets/logo.png
new file mode 100644
index 0000000..9dec1a7
Binary files /dev/null and b/assets/logo.png differ
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index e75003a..b8b78c8 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -427,7 +427,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
- ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@@ -484,7 +484,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
- ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
index d36b1fa..d0d98aa 100644
--- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -1,122 +1 @@
-{
- "images" : [
- {
- "size" : "20x20",
- "idiom" : "iphone",
- "filename" : "Icon-App-20x20@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "20x20",
- "idiom" : "iphone",
- "filename" : "Icon-App-20x20@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29x29@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29x29@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29x29@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "Icon-App-40x40@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "Icon-App-40x40@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "Icon-App-60x60@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "Icon-App-60x60@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "20x20",
- "idiom" : "ipad",
- "filename" : "Icon-App-20x20@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "20x20",
- "idiom" : "ipad",
- "filename" : "Icon-App-20x20@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "Icon-App-29x29@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "Icon-App-29x29@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "ipad",
- "filename" : "Icon-App-40x40@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "40x40",
- "idiom" : "ipad",
- "filename" : "Icon-App-40x40@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "Icon-App-76x76@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "Icon-App-76x76@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "83.5x83.5",
- "idiom" : "ipad",
- "filename" : "Icon-App-83.5x83.5@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "1024x1024",
- "idiom" : "ios-marketing",
- "filename" : "Icon-App-1024x1024@1x.png",
- "scale" : "1x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
+{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}
\ No newline at end of file
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
index dc9ada4..107feda 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
index 7353c41..ee29fce 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
index 797d452..8b75ad2 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
index 6ed2d93..1d71775 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
index 4cd7b00..22407b2 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
index fe73094..81242d1 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
index 321773c..003ef85 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
index 797d452..8b75ad2 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
index 502f463..ff408b6 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
index 0ec3034..52babb9 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png
new file mode 100644
index 0000000..c1030a6
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png
new file mode 100644
index 0000000..207bd09
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
new file mode 100644
index 0000000..2c72469
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
new file mode 100644
index 0000000..9e8ca4b
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
index 0ec3034..52babb9 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
index e9f5fea..db80331 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
new file mode 100644
index 0000000..33b3d8e
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
new file mode 100644
index 0000000..6e39eda
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
index 84ac32a..5420cf6 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
index 8953cba..818532c 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
index 0467bf1..f7ad547 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/lib/features/history/screens/history_screen.dart b/lib/features/history/screens/history_screen.dart
index 055846b..a9c4fa8 100644
--- a/lib/features/history/screens/history_screen.dart
+++ b/lib/features/history/screens/history_screen.dart
@@ -5,14 +5,56 @@ import 'package:english_learning/features/history/provider/history_provider.dart
import 'package:english_learning/features/history/widgets/custom_tab_bar.dart';
import 'package:english_learning/core/utils/styles/theme.dart';
import 'package:english_learning/features/history/widgets/exercise_history_card.dart';
-import 'package:english_learning/features/learning/screens/learning_screen.dart';
+import 'package:english_learning/features/home/screens/home_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
+import 'package:shimmer/shimmer.dart';
-class HistoryScreen extends StatelessWidget {
+class HistoryScreen extends StatefulWidget {
const HistoryScreen({super.key});
+ @override
+ State createState() => _HistoryScreenState();
+}
+
+class _HistoryScreenState extends State {
+ @override
+ void initState() {
+ super.initState();
+ // Memuat data saat HistoryScreen diinisialisasi
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ final historyProvider =
+ Provider.of(context, listen: false);
+ final userProvider = Provider.of(context, listen: false);
+ historyProvider.fetchLearningHistory(userProvider.jwtToken!);
+ });
+ }
+
+ // Tambahkan method untuk shimmer loading
+ Widget _buildShimmerLoading() {
+ return ListView.builder(
+ itemCount: 5, // Jumlah item shimmer
+ itemBuilder: (context, index) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
+ child: Shimmer.fromColors(
+ baseColor: Colors.grey[300]!,
+ highlightColor: Colors.grey[100]!,
+ child: Container(
+ width: double.infinity,
+ height: 100, // Sesuaikan dengan tinggi card history
+ decoration: BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.circular(12),
+ ),
+ ),
+ ),
+ );
+ },
+ );
+ }
+
bool isNotFoundError(String error) {
return error.toLowerCase().contains('no learning history found') ||
error.toLowerCase().contains('not found');
@@ -52,7 +94,7 @@ class HistoryScreen extends StatelessWidget {
Widget _buildContent(BuildContext context, HistoryProvider historyProvider) {
if (historyProvider.isLoading) {
- return const Center(child: CircularProgressIndicator());
+ return _buildShimmerLoading();
}
if (historyProvider.error != null) {
@@ -186,12 +228,7 @@ class HistoryScreen extends StatelessWidget {
backgroundColor: AppColors.yellowButtonColor,
textColor: AppColors.blackColor,
onPressed: () {
- Navigator.push(
- context,
- MaterialPageRoute(
- builder: (context) => const LearningScreen(),
- ),
- );
+ HomeScreen.navigateToTab(context, 1);
},
),
],
diff --git a/lib/features/home/provider/completed_topics_provider.dart b/lib/features/home/provider/completed_topics_provider.dart
index fbd9cd4..f1d4943 100644
--- a/lib/features/home/provider/completed_topics_provider.dart
+++ b/lib/features/home/provider/completed_topics_provider.dart
@@ -16,17 +16,28 @@ class CompletedTopicsProvider with ChangeNotifier {
bool get isLoading => _isLoading;
String? get error => _error;
- Future fetchCompletedTopics(String token) async {
- _isLoading = true;
+ void resetData() {
+ _completedTopics = [];
+ _isLoading = false;
_error = null;
notifyListeners();
+ }
+
+ Future fetchCompletedTopics(String token) async {
+ resetData();
+ _isLoading = true;
+ notifyListeners();
try {
- _completedTopics = await _repository.getCompletedTopics(token);
+ final result = await _repository.getCompletedTopics(token);
+ _completedTopics = result;
+ _error = null;
} catch (e) {
+ // Tangani error
+ _completedTopics = [];
_error = e.toString();
- print('Error fetching completed topics: $_error');
} finally {
+ // Selalu set loading ke false
_isLoading = false;
notifyListeners();
}
diff --git a/lib/features/home/screens/home_screen.dart b/lib/features/home/screens/home_screen.dart
index 3b4dfd8..dfd1eb3 100644
--- a/lib/features/home/screens/home_screen.dart
+++ b/lib/features/home/screens/home_screen.dart
@@ -25,6 +25,13 @@ class HomeScreen extends StatefulWidget {
@override
State createState() => _HomeScreenState();
+ static void navigateToTab(BuildContext context, int index) {
+ final state = context.findAncestorStateOfType<_HomeScreenState>();
+ if (state != null) {
+ state.navigateToTab(index);
+ }
+ }
+
static void navigateReplacing(BuildContext context) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => const HomeScreen()),
@@ -43,85 +50,93 @@ class _HomeScreenState extends State {
const SettingsScreen(),
];
+ void navigateToTab(int index) {
+ setState(() {
+ _selectedIndex = index;
+ _pageController.jumpToPage(index);
+ });
+ }
+
@override
Widget build(BuildContext context) {
- return Scaffold(
- body: Container(
- color: AppColors.bgSoftColor,
- child: PageView(
- physics: const NeverScrollableScrollPhysics(),
- controller: _pageController,
- children: _screens,
- onPageChanged: (index) {
- setState(() {
- _selectedIndex = index;
- });
- },
- ),
- ),
- bottomNavigationBar: Container(
- decoration: BoxDecoration(
- gradient: AppColors.gradientTheme,
- borderRadius: const BorderRadius.only(
- topLeft: Radius.circular(20),
- topRight: Radius.circular(20),
- ),
- ),
- child: Padding(
- padding: const EdgeInsets.only(
- top: 20,
- bottom: 24,
- left: 16,
- right: 16,
- ),
- child: GNav(
- activeColor: AppColors.blueColor,
- tabBackgroundColor: AppColors.whiteColor,
- tabBorderRadius: 100,
- color: AppColors.whiteColor,
- iconSize: 20,
- gap: 8,
- selectedIndex: _selectedIndex,
- onTabChange: (index) async {
- if (index == 2 && _selectedIndex != 2) {
- // Only if switching TO history tab
- final historyProvider =
- Provider.of(context, listen: false);
- final userProvider =
- Provider.of(context, listen: false);
-
- if (!historyProvider.isInitialized) {
- await historyProvider.loadInitialData(userProvider.jwtToken!);
- }
- }
-
+ return DefaultTabController(
+ length: 4,
+ child: Scaffold(
+ body: Container(
+ color: AppColors.bgSoftColor,
+ child: PageView(
+ physics: const NeverScrollableScrollPhysics(),
+ controller: _pageController,
+ children: _screens,
+ onPageChanged: (index) {
setState(() {
_selectedIndex = index;
- _pageController.jumpToPage(index);
});
},
- padding: const EdgeInsets.symmetric(
- horizontal: 16,
- vertical: 8,
+ ),
+ ),
+ bottomNavigationBar: Container(
+ decoration: BoxDecoration(
+ gradient: AppColors.gradientTheme,
+ borderRadius: const BorderRadius.only(
+ topLeft: Radius.circular(20),
+ topRight: Radius.circular(20),
),
- tabs: const [
- GButton(
- icon: BootstrapIcons.house,
- text: 'Home',
+ ),
+ child: Padding(
+ padding: const EdgeInsets.only(
+ top: 20,
+ bottom: 24,
+ left: 16,
+ right: 16,
+ ),
+ child: GNav(
+ activeColor: AppColors.blueColor,
+ tabBackgroundColor: AppColors.whiteColor,
+ tabBorderRadius: 100,
+ color: AppColors.whiteColor,
+ iconSize: 20,
+ gap: 8,
+ selectedIndex: _selectedIndex,
+ onTabChange: (index) async {
+ if (index == 2 && _selectedIndex != 2) {
+ // Only if switching TO history tab
+ final historyProvider =
+ Provider.of(context, listen: false);
+ final userProvider =
+ Provider.of(context, listen: false);
+
+ if (!historyProvider.isInitialized) {
+ await historyProvider
+ .loadInitialData(userProvider.jwtToken!);
+ }
+ }
+
+ navigateToTab(index);
+ },
+ padding: const EdgeInsets.symmetric(
+ horizontal: 16,
+ vertical: 8,
),
- 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',
+ ),
+ ],
+ ),
),
),
),
@@ -148,6 +163,7 @@ class _HomeContentState extends State {
final userProvider = Provider.of(context, listen: false);
final completedTopicsProvider =
Provider.of(context, listen: false);
+ completedTopicsProvider.resetData();
completedTopicsProvider.fetchCompletedTopics(userProvider.jwtToken!);
});
}
@@ -178,7 +194,9 @@ class _HomeContentState extends State {
width: double.infinity,
height: 44,
color: AppColors.yellowButtonColor,
- onPressed: () {},
+ onPressed: () {
+ HomeScreen.navigateToTab(context, 1);
+ },
),
],
),
@@ -421,29 +439,11 @@ class _HomeContentState extends State {
),
completedTopicsProvider.isLoading
? _buildShimmerEffect()
- : completedTopicsProvider.completedTopics.isEmpty
+ : completedTopicsProvider.completedTopics == null ||
+ completedTopicsProvider.completedTopics.isEmpty
? _buildNoDataWidget()
: _buildCompletedTopicsContent(
completedTopicsProvider),
-
- // completedTopicsProvider.isLoading
- // ? _buildShimmerEffect()
- // : completedTopicsProvider.completedTopics.isEmpty
- // ? _buildNoDataWidget()
- // : ListView.builder(
- // shrinkWrap: true,
- // physics: const NeverScrollableScrollPhysics(),
- // padding: const EdgeInsets.symmetric(
- // horizontal: 16.0,
- // ),
- // itemCount: 1,
- // itemBuilder: (context, index) {
- // return ProgressCard(
- // completedTopic: completedTopicsProvider
- // .completedTopics, // Kirim seluruh list
- // );
- // },
- // ),
],
),
),
diff --git a/lib/features/home/widgets/progress_card.dart b/lib/features/home/widgets/progress_card.dart
index 83618d0..58eae0d 100644
--- a/lib/features/home/widgets/progress_card.dart
+++ b/lib/features/home/widgets/progress_card.dart
@@ -1,8 +1,12 @@
import 'package:english_learning/core/services/constants.dart';
import 'package:english_learning/core/utils/styles/theme.dart';
+import 'package:english_learning/features/auth/provider/user_provider.dart';
import 'package:english_learning/features/home/models/completed_topics_model.dart';
import 'package:english_learning/features/home/widgets/progress_bar.dart';
+import 'package:english_learning/features/learning/modules/topics/screens/topics_list_screen.dart';
+import 'package:english_learning/features/learning/provider/section_provider.dart';
import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
class ProgressCard extends StatelessWidget {
final List completedTopic;
@@ -15,6 +19,68 @@ class ProgressCard extends StatelessWidget {
: '${baseUrl}uploads/section/$thumbnail';
}
+ Future _navigateToTopics(
+ BuildContext context, CompletedTopic topic) async {
+ // Get the SectionProvider
+ final sectionProvider =
+ Provider.of(context, listen: false);
+
+ // Show loading indicator while checking/loading data
+ showDialog(
+ context: context,
+ barrierDismissible: false,
+ builder: (context) => const Center(
+ child: CircularProgressIndicator(),
+ ),
+ );
+
+ try {
+ // If sections aren't loaded yet, load them
+ if (sectionProvider.sections.isEmpty) {
+ final userProvider = Provider.of(context, listen: false);
+ final token = await userProvider.getValidToken();
+
+ if (token != null) {
+ await sectionProvider.fetchSections(token);
+ } else {
+ throw Exception('No valid token found');
+ }
+ }
+
+ // Remove loading indicator
+ Navigator.pop(context);
+
+ // Navigate to topics screen
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => TopicsListScreen(
+ sectionId: topic.idSection,
+ ),
+ ),
+ );
+ } catch (e) {
+ // Remove loading indicator
+ Navigator.pop(context);
+
+ // Show error dialog
+ showDialog(
+ context: context,
+ builder: (context) => AlertDialog(
+ title: const Text('Error'),
+ content: const Text(
+ 'Unable to load section data. Please try again later.'),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.pop(context),
+ child: const Text('OK'),
+ ),
+ ],
+ ),
+ );
+ }
+ }
+
@override
Widget build(BuildContext context) {
return Column(
@@ -25,7 +91,7 @@ class ProgressCard extends StatelessWidget {
CompletedTopic topic = entry.value;
return Padding(
padding: const EdgeInsets.only(bottom: 12),
- child: _buildTopicItem(topic),
+ child: _buildTopicItem(context, topic),
);
},
),
@@ -33,68 +99,71 @@ class ProgressCard extends StatelessWidget {
);
}
- Widget _buildTopicItem(CompletedTopic topic) {
- return Container(
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(12),
- border: Border.all(color: Colors.grey.shade300),
- ),
- child: Padding(
- padding: const EdgeInsets.all(12.0),
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- ClipRRect(
- borderRadius: BorderRadius.circular(8),
- child: Image.network(
- _getFullImageUrl(topic.thumbnail),
- width: 90,
- height: 130,
- fit: BoxFit.cover,
- errorBuilder: (context, error, stackTrace) {
- return Container(
- width: 90,
- height: 130,
- color: Colors.grey[300],
- child: const Icon(
- Icons.image_not_supported,
- color: Colors.grey,
- ),
- );
- },
+ Widget _buildTopicItem(BuildContext context, CompletedTopic topic) {
+ return GestureDetector(
+ onTap: () => _navigateToTopics(context, topic),
+ child: Container(
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(12),
+ border: Border.all(color: Colors.grey.shade300),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(12.0),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ ClipRRect(
+ borderRadius: BorderRadius.circular(8),
+ child: Image.network(
+ _getFullImageUrl(topic.thumbnail),
+ width: 90,
+ height: 130,
+ fit: BoxFit.cover,
+ errorBuilder: (context, error, stackTrace) {
+ return Container(
+ width: 90,
+ height: 130,
+ color: Colors.grey[300],
+ child: const Icon(
+ Icons.image_not_supported,
+ color: Colors.grey,
+ ),
+ );
+ },
+ ),
),
- ),
- const SizedBox(width: 16),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- topic.nameSection,
- style: AppTextStyles.blackTextStyle.copyWith(
- fontSize: 16,
- fontWeight: FontWeight.w900,
+ const SizedBox(width: 16),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ topic.nameSection,
+ style: AppTextStyles.blackTextStyle.copyWith(
+ fontSize: 16,
+ fontWeight: FontWeight.w900,
+ ),
),
- ),
- const SizedBox(height: 4),
- Text(
- topic.descriptionSection,
- maxLines: 2,
- overflow: TextOverflow.ellipsis,
- style: AppTextStyles.disableTextStyle.copyWith(
- fontSize: 12,
- fontWeight: FontWeight.bold,
+ const SizedBox(height: 4),
+ Text(
+ topic.descriptionSection,
+ maxLines: 2,
+ overflow: TextOverflow.ellipsis,
+ style: AppTextStyles.disableTextStyle.copyWith(
+ fontSize: 12,
+ fontWeight: FontWeight.bold,
+ ),
),
- ),
- const SizedBox(height: 24),
- ProgressBar(
- completedTopics: topic.completedTopics,
- totalTopics: topic.totalTopics,
- ),
- ],
+ const SizedBox(height: 24),
+ ProgressBar(
+ completedTopics: topic.completedTopics,
+ totalTopics: topic.totalTopics,
+ ),
+ ],
+ ),
),
- ),
- ],
+ ],
+ ),
),
),
);
diff --git a/lib/features/learning/screens/learning_screen.dart b/lib/features/learning/screens/learning_screen.dart
index 5b66e46..6916e2c 100644
--- a/lib/features/learning/screens/learning_screen.dart
+++ b/lib/features/learning/screens/learning_screen.dart
@@ -20,7 +20,9 @@ class _LearningScreenState extends State {
@override
void initState() {
super.initState();
- _fetchSections();
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ _fetchSections();
+ });
}
Future _fetchSections() async {
diff --git a/pubspec.lock b/pubspec.lock
index 58857cb..1079e51 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -1,6 +1,14 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
+ archive:
+ dependency: transitive
+ description:
+ name: archive
+ sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.6.1"
args:
dependency: transitive
description:
@@ -145,6 +153,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.1"
+ checked_yaml:
+ dependency: transitive
+ description:
+ name: checked_yaml
+ sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.3"
chewie:
dependency: transitive
description:
@@ -153,6 +169,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.8.5"
+ cli_util:
+ dependency: transitive
+ description:
+ name: cli_util
+ sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.4.2"
clock:
dependency: transitive
description:
@@ -382,6 +406,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.0"
+ flutter_launcher_icons:
+ dependency: "direct dev"
+ description:
+ name: flutter_launcher_icons
+ sha256: "619817c4b65b322b5104b6bb6dfe6cda62d9729bd7ad4303ecc8b4e690a67a77"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.14.1"
flutter_lints:
dependency: "direct dev"
description:
@@ -568,6 +600,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.1.0"
+ image:
+ dependency: transitive
+ description:
+ name: image
+ sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.3.0"
image_picker:
dependency: "direct main"
description:
@@ -648,6 +688,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.7"
+ json_annotation:
+ dependency: transitive
+ description:
+ name: json_annotation
+ sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.9.0"
just_audio:
dependency: transitive
description:
@@ -1301,6 +1349,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.5.0"
+ yaml:
+ dependency: transitive
+ description:
+ name: yaml
+ sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.2"
youtube_player_flutter:
dependency: "direct main"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index fd54a28..d7fb67e 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -58,6 +58,15 @@ dev_dependencies:
flutter_test:
sdk: flutter
+ flutter_launcher_icons: ^0.14.1
+
+flutter_launcher_icons:
+ android: true
+ ios: true
+ image_path: 'assets/logo.png'
+ adaptive_icon_background: '#FFFFFF'
+ adaptive_icon_foreground: 'assets/logo.png'
+
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.