Merge branch 'home-navigation' into 'master'
refactor(learning): Enhance data loading and navigation flow See merge request profile-image/kedaireka/polinema-adapative-learning/mobile-adaptive-learning!12
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground>
|
||||||
|
<inset
|
||||||
|
android:drawable="@drawable/ic_launcher_foreground"
|
||||||
|
android:inset="16%" />
|
||||||
|
</foreground>
|
||||||
|
</adaptive-icon>
|
||||||
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 5.5 KiB |
4
android/app/src/main/res/values/colors.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#FFFFFF</color>
|
||||||
|
</resources>
|
||||||
BIN
assets/logo.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
|
|
@ -427,7 +427,7 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
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_ANALYZER_NONNULL = YES;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
|
@ -484,7 +484,7 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
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_ANALYZER_NONNULL = YES;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
|
|
||||||
|
|
@ -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":"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"}}
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 500 B |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 787 B |
|
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 704 B After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 762 B After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 4.8 KiB |
|
|
@ -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/features/history/widgets/custom_tab_bar.dart';
|
||||||
import 'package:english_learning/core/utils/styles/theme.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/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/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:shimmer/shimmer.dart';
|
||||||
|
|
||||||
class HistoryScreen extends StatelessWidget {
|
class HistoryScreen extends StatefulWidget {
|
||||||
const HistoryScreen({super.key});
|
const HistoryScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HistoryScreen> createState() => _HistoryScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HistoryScreenState extends State<HistoryScreen> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
// Memuat data saat HistoryScreen diinisialisasi
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
final historyProvider =
|
||||||
|
Provider.of<HistoryProvider>(context, listen: false);
|
||||||
|
final userProvider = Provider.of<UserProvider>(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) {
|
bool isNotFoundError(String error) {
|
||||||
return error.toLowerCase().contains('no learning history found') ||
|
return error.toLowerCase().contains('no learning history found') ||
|
||||||
error.toLowerCase().contains('not found');
|
error.toLowerCase().contains('not found');
|
||||||
|
|
@ -52,7 +94,7 @@ class HistoryScreen extends StatelessWidget {
|
||||||
|
|
||||||
Widget _buildContent(BuildContext context, HistoryProvider historyProvider) {
|
Widget _buildContent(BuildContext context, HistoryProvider historyProvider) {
|
||||||
if (historyProvider.isLoading) {
|
if (historyProvider.isLoading) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return _buildShimmerLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (historyProvider.error != null) {
|
if (historyProvider.error != null) {
|
||||||
|
|
@ -186,12 +228,7 @@ class HistoryScreen extends StatelessWidget {
|
||||||
backgroundColor: AppColors.yellowButtonColor,
|
backgroundColor: AppColors.yellowButtonColor,
|
||||||
textColor: AppColors.blackColor,
|
textColor: AppColors.blackColor,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.push(
|
HomeScreen.navigateToTab(context, 1);
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const LearningScreen(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -16,17 +16,28 @@ class CompletedTopicsProvider with ChangeNotifier {
|
||||||
bool get isLoading => _isLoading;
|
bool get isLoading => _isLoading;
|
||||||
String? get error => _error;
|
String? get error => _error;
|
||||||
|
|
||||||
Future<void> fetchCompletedTopics(String token) async {
|
void resetData() {
|
||||||
_isLoading = true;
|
_completedTopics = [];
|
||||||
|
_isLoading = false;
|
||||||
_error = null;
|
_error = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> fetchCompletedTopics(String token) async {
|
||||||
|
resetData();
|
||||||
|
_isLoading = true;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
_completedTopics = await _repository.getCompletedTopics(token);
|
final result = await _repository.getCompletedTopics(token);
|
||||||
|
_completedTopics = result;
|
||||||
|
_error = null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
// Tangani error
|
||||||
|
_completedTopics = [];
|
||||||
_error = e.toString();
|
_error = e.toString();
|
||||||
print('Error fetching completed topics: $_error');
|
|
||||||
} finally {
|
} finally {
|
||||||
|
// Selalu set loading ke false
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,13 @@ class HomeScreen extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
State<HomeScreen> createState() => _HomeScreenState();
|
State<HomeScreen> 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) {
|
static void navigateReplacing(BuildContext context) {
|
||||||
Navigator.of(context).pushReplacement(
|
Navigator.of(context).pushReplacement(
|
||||||
MaterialPageRoute(builder: (context) => const HomeScreen()),
|
MaterialPageRoute(builder: (context) => const HomeScreen()),
|
||||||
|
|
@ -43,9 +50,18 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
const SettingsScreen(),
|
const SettingsScreen(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
void navigateToTab(int index) {
|
||||||
|
setState(() {
|
||||||
|
_selectedIndex = index;
|
||||||
|
_pageController.jumpToPage(index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return DefaultTabController(
|
||||||
|
length: 4,
|
||||||
|
child: Scaffold(
|
||||||
body: Container(
|
body: Container(
|
||||||
color: AppColors.bgSoftColor,
|
color: AppColors.bgSoftColor,
|
||||||
child: PageView(
|
child: PageView(
|
||||||
|
|
@ -91,14 +107,12 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
Provider.of<UserProvider>(context, listen: false);
|
Provider.of<UserProvider>(context, listen: false);
|
||||||
|
|
||||||
if (!historyProvider.isInitialized) {
|
if (!historyProvider.isInitialized) {
|
||||||
await historyProvider.loadInitialData(userProvider.jwtToken!);
|
await historyProvider
|
||||||
|
.loadInitialData(userProvider.jwtToken!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(() {
|
navigateToTab(index);
|
||||||
_selectedIndex = index;
|
|
||||||
_pageController.jumpToPage(index);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 16,
|
horizontal: 16,
|
||||||
|
|
@ -125,6 +139,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -148,6 +163,7 @@ class _HomeContentState extends State<HomeContent> {
|
||||||
final userProvider = Provider.of<UserProvider>(context, listen: false);
|
final userProvider = Provider.of<UserProvider>(context, listen: false);
|
||||||
final completedTopicsProvider =
|
final completedTopicsProvider =
|
||||||
Provider.of<CompletedTopicsProvider>(context, listen: false);
|
Provider.of<CompletedTopicsProvider>(context, listen: false);
|
||||||
|
completedTopicsProvider.resetData();
|
||||||
completedTopicsProvider.fetchCompletedTopics(userProvider.jwtToken!);
|
completedTopicsProvider.fetchCompletedTopics(userProvider.jwtToken!);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -178,7 +194,9 @@ class _HomeContentState extends State<HomeContent> {
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 44,
|
height: 44,
|
||||||
color: AppColors.yellowButtonColor,
|
color: AppColors.yellowButtonColor,
|
||||||
onPressed: () {},
|
onPressed: () {
|
||||||
|
HomeScreen.navigateToTab(context, 1);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -421,29 +439,11 @@ class _HomeContentState extends State<HomeContent> {
|
||||||
),
|
),
|
||||||
completedTopicsProvider.isLoading
|
completedTopicsProvider.isLoading
|
||||||
? _buildShimmerEffect()
|
? _buildShimmerEffect()
|
||||||
: completedTopicsProvider.completedTopics.isEmpty
|
: completedTopicsProvider.completedTopics == null ||
|
||||||
|
completedTopicsProvider.completedTopics.isEmpty
|
||||||
? _buildNoDataWidget()
|
? _buildNoDataWidget()
|
||||||
: _buildCompletedTopicsContent(
|
: _buildCompletedTopicsContent(
|
||||||
completedTopicsProvider),
|
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
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
import 'package:english_learning/core/services/constants.dart';
|
import 'package:english_learning/core/services/constants.dart';
|
||||||
import 'package:english_learning/core/utils/styles/theme.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/models/completed_topics_model.dart';
|
||||||
import 'package:english_learning/features/home/widgets/progress_bar.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:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class ProgressCard extends StatelessWidget {
|
class ProgressCard extends StatelessWidget {
|
||||||
final List<CompletedTopic> completedTopic;
|
final List<CompletedTopic> completedTopic;
|
||||||
|
|
@ -15,6 +19,68 @@ class ProgressCard extends StatelessWidget {
|
||||||
: '${baseUrl}uploads/section/$thumbnail';
|
: '${baseUrl}uploads/section/$thumbnail';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _navigateToTopics(
|
||||||
|
BuildContext context, CompletedTopic topic) async {
|
||||||
|
// Get the SectionProvider
|
||||||
|
final sectionProvider =
|
||||||
|
Provider.of<SectionProvider>(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<UserProvider>(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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
|
|
@ -25,7 +91,7 @@ class ProgressCard extends StatelessWidget {
|
||||||
CompletedTopic topic = entry.value;
|
CompletedTopic topic = entry.value;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 12),
|
padding: const EdgeInsets.only(bottom: 12),
|
||||||
child: _buildTopicItem(topic),
|
child: _buildTopicItem(context, topic),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -33,8 +99,10 @@ class ProgressCard extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTopicItem(CompletedTopic topic) {
|
Widget _buildTopicItem(BuildContext context, CompletedTopic topic) {
|
||||||
return Container(
|
return GestureDetector(
|
||||||
|
onTap: () => _navigateToTopics(context, topic),
|
||||||
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(color: Colors.grey.shade300),
|
border: Border.all(color: Colors.grey.shade300),
|
||||||
|
|
@ -97,6 +165,7 @@ class ProgressCard extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,9 @@ class _LearningScreenState extends State<LearningScreen> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
_fetchSections();
|
_fetchSections();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _fetchSections() async {
|
Future<void> _fetchSections() async {
|
||||||
|
|
|
||||||
56
pubspec.lock
|
|
@ -1,6 +1,14 @@
|
||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
archive:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: archive
|
||||||
|
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.6.1"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -145,6 +153,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
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:
|
chewie:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -153,6 +169,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.5"
|
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:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -382,6 +406,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.0"
|
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:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
|
|
@ -568,6 +600,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.0"
|
version: "4.1.0"
|
||||||
|
image:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image
|
||||||
|
sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.3.0"
|
||||||
image_picker:
|
image_picker:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -648,6 +688,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.7"
|
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:
|
just_audio:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1301,6 +1349,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.5.0"
|
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:
|
youtube_player_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,15 @@ dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
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
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
# The following section is specific to Flutter packages.
|
# The following section is specific to Flutter packages.
|
||||||
|
|
|
||||||