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.