Skip to content

Commit

Permalink
feat: cache data in home page
Browse files Browse the repository at this point in the history
  • Loading branch information
fzlaziz committed Dec 26, 2024
1 parent 64e08d9 commit bae716d
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 66 deletions.
222 changes: 195 additions & 27 deletions lib/controller/home_controller.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
Expand All @@ -16,37 +17,224 @@ class HomeController extends GetxController {
var latestNews = <Berita>[].obs;
final RxList<CampusResponse> recommendedCampuses = <CampusResponse>[].obs;
final locationService = LocationService(Get.context!);
final RxBool isLocationReady = false.obs;
final RxBool isLocationDenied = false.obs;

// Cache duration constants
static const campusCacheDuration = Duration(minutes: 30);
static const newsCacheDuration = Duration(minutes: 1);
static const locationThreshold = 0.01; // Roughly 1km difference

@override
void onInit() {
super.onInit();
locationService.loadUserLocation();
locationService.updateLocation();
getAllCampusData();
getLatestNews();
fetchRecommendedCampuses();
initializeData();
}

Future<void> initializeData() async {
await loadCachedData();
await refreshDataIfNeeded();
}

Future<void> loadCachedData() async {
final prefs = await SharedPreferences.getInstance();

// Load campus data
final campusDataTime = DateTime.fromMillisecondsSinceEpoch(
prefs.getInt('campusDataTimestamp') ?? 0);

if (DateTime.now().difference(campusDataTime) < campusCacheDuration) {
final cachedTopCampus = prefs.getString('topCampusData');
if (cachedTopCampus != null) {
final topCampusData =
TopCampusList.fromJson(jsonDecode(cachedTopCampus));
ptnList.value = topCampusData.ptn;
politeknikList.value = topCampusData.politeknik;
swastaList.value = topCampusData.swasta;
}
}

// Load recommended campuses
final recommendedTime = DateTime.fromMillisecondsSinceEpoch(
prefs.getInt('recommendedTimestamp') ?? 0);
if (DateTime.now().difference(recommendedTime) < campusCacheDuration) {
final cachedRecommended = prefs.getString('recommendedCampuses');
if (cachedRecommended != null) {
List<dynamic> jsonList = jsonDecode(cachedRecommended);
recommendedCampuses.value =
jsonList.map((json) => CampusResponse.fromJson(json)).toList();
}
}

// Load news
final newsTime =
DateTime.fromMillisecondsSinceEpoch(prefs.getInt('newsTimestamp') ?? 0);
if (DateTime.now().difference(newsTime) < newsCacheDuration) {
final cachedNews = prefs.getString('latestNews');
if (cachedNews != null) {
List<dynamic> jsonList = jsonDecode(cachedNews);
latestNews.value =
jsonList.map((json) => Berita.fromJson(json)).toList();
}
}
}

Future<void> refreshDataIfNeeded() async {
final prefs = await SharedPreferences.getInstance();

// Check campus data cache
final lastCampusRefresh = DateTime.fromMillisecondsSinceEpoch(
prefs.getInt('campusDataTimestamp') ?? 0);
if (DateTime.now().difference(lastCampusRefresh) > campusCacheDuration) {
await getAllCampusData();
}

// Check news cache
final lastNewsRefresh =
DateTime.fromMillisecondsSinceEpoch(prefs.getInt('newsTimestamp') ?? 0);
if (DateTime.now().difference(lastNewsRefresh) > newsCacheDuration) {
await getLatestNews();
}

// Initialize location and recommended campuses
await initializeLocation();
}

void getAllCampusData() async {
Future<bool> shouldUpdateLocation() async {
final prefs = await SharedPreferences.getInstance();
final oldLat = prefs.getDouble('lastLatitude');
final oldLng = prefs.getDouble('lastLongitude');
final currentLat = prefs.getDouble('userLatitude');
final currentLng = prefs.getDouble('userLongitude');

if (oldLat == null || oldLng == null) return true;
if (currentLat == null || currentLng == null) return true;

return (oldLat - currentLat).abs() > locationThreshold ||
(oldLng - currentLng).abs() > locationThreshold;
}

Future<void> initializeLocation() async {
try {
await locationService.loadUserLocation();
final hasPermission = await locationService.hasLocationPermission();

if (hasPermission) {
await locationService.updateLocation();
isLocationReady.value = true;

if (await shouldUpdateLocation()) {
await fetchRecommendedCampuses();

final prefs = await SharedPreferences.getInstance();
await prefs.setDouble(
'lastLatitude', prefs.getDouble('userLatitude') ?? 0);
await prefs.setDouble(
'lastLongitude', prefs.getDouble('userLongitude') ?? 0);
}
} else {
isLocationDenied.value = true;
await fetchDefaultRecommendations();
}
} catch (e) {
print('Error initializing location: $e');
isLocationDenied.value = true;
await fetchDefaultRecommendations();
}
}

Future<void> getAllCampusData() async {
try {
final result = await TopCampusProvider().getAllCampuses();
ptnList.value = result.ptn;
politeknikList.value = result.politeknik;
swastaList.value = result.swasta;

// Cache the results
final prefs = await SharedPreferences.getInstance();
await prefs.setString('topCampusData', jsonEncode(result.toJson()));
await prefs.setInt(
'campusDataTimestamp', DateTime.now().millisecondsSinceEpoch);
} catch (e) {
print('Error fetching campus data: $e');
}
}

void getLatestNews() async {
Future<void> getLatestNews() async {
try {
var result = await NewsLatest().getBerita();
latestNews.value = result;

// Cache the results
final prefs = await SharedPreferences.getInstance();
await prefs.setString(
'latestNews', jsonEncode(result.map((e) => e.toJson()).toList()));
await prefs.setInt(
'newsTimestamp', DateTime.now().millisecondsSinceEpoch);
} catch (e) {
print('Error fetching latest news: $e');
}
}

Future<void> fetchRecommendedCampuses() async {
if (!isLocationReady.value) return;

try {
final prefs = await SharedPreferences.getInstance();
double? userLatitude = prefs.getDouble('userLatitude');
double? userLongitude = prefs.getDouble('userLongitude');

if (userLatitude == null || userLongitude == null) {
await fetchDefaultRecommendations();
return;
}

var campusService = ApiDataProvider();
var campuses = await campusService.getCampusesNearby(
latitude: userLatitude, longitude: userLongitude);

recommendedCampuses.value = campuses.take(4).toList();

// Cache the results
await prefs.setString('recommendedCampuses',
jsonEncode(recommendedCampuses.map((e) => e.toJson()).toList()));
await prefs.setInt(
'recommendedTimestamp', DateTime.now().millisecondsSinceEpoch);
} catch (e) {
print('Error fetching recommended campuses: $e');
await fetchDefaultRecommendations();
}
}

Future<void> fetchDefaultRecommendations() async {
try {
double userLatitude = -7.051834727886331;
double userLongitude = 110.4410954478625;
var campusService = ApiDataProvider();
var campuses = await campusService.getCampusesNearby(
latitude: userLatitude, longitude: userLongitude);

recommendedCampuses.value = campuses.take(4).toList();

// Cache the results
final prefs = await SharedPreferences.getInstance();
await prefs.setString('recommendedCampuses',
jsonEncode(recommendedCampuses.map((e) => e.toJson()).toList()));
await prefs.setInt(
'recommendedTimestamp', DateTime.now().millisecondsSinceEpoch);
} catch (e) {
print('Error fetching default recommendations: $e');
recommendedCampuses.value = [];
}
}

// Force refresh all data
Future<void> refreshAll() async {
await getAllCampusData();
await getLatestNews();
await initializeLocation();
}

RxBool isNavigating = false.obs;

Future<void> navigateToDetail(int beritaId) async {
Expand All @@ -65,24 +253,4 @@ class HomeController extends GetxController {
}
}
}

Future<void> fetchRecommendedCampuses() async {
try {
final prefs = await SharedPreferences.getInstance();

double? userLatitude = prefs.getDouble('userLatitude');
double? userLongitude = prefs.getDouble('userLongitude');

debugPrint("$userLatitude");
debugPrint("$userLongitude");

var campusService = ApiDataProvider();
var campuses = await campusService.getCampusesNearby(
latitude: userLatitude!, longitude: userLongitude!);

recommendedCampuses.value = campuses.take(4).toList();
} catch (e) {
print('Error fetching recommended campuses: $e');
}
}
}
2 changes: 1 addition & 1 deletion lib/screens/auth/login.dart
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ class _LoginPageState extends State<LoginPage> {
children: [
GestureDetector(
onTap: () {
Get.off(RegisterScreen());
Get.offNamed('/register');
},
child: Text(
"Daftar Sekarang",
Expand Down
4 changes: 1 addition & 3 deletions lib/screens/auth/profile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,7 @@ class ProfilePageState extends State<ProfilePage> {

if (response['status_code'] == 200) {
// Navigate to login screen and remove all previous routes
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const LoginPage()),
);
Navigator.of(context).pushReplacementNamed('/login');
} else {
// Show error message
ScaffoldMessenger.of(context).showSnackBar(
Expand Down
4 changes: 2 additions & 2 deletions lib/screens/auth/register.dart
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () => Get.offAll(() => const LoginPage()),
onPressed: () => Get.offAllNamed('/login'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue[800],
padding: const EdgeInsets.symmetric(vertical: 16),
Expand Down Expand Up @@ -438,7 +438,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
),
GestureDetector(
onTap: () {
Get.off(() => const LoginPage());
Get.offNamed('/login');
},
child: Text(
"Login",
Expand Down
4 changes: 2 additions & 2 deletions lib/screens/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ class Home extends StatelessWidget {
Align(
alignment: Alignment.centerLeft,
child: Text(
'Rekomendasi untuk Anda',
'Rekomendasi Kampus Terdekat',
style: GoogleFonts.poppins(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),

const SizedBox(height: 16),
GetX<HomeController>(
init: HomeController(),
builder: (controller) {
Expand Down
6 changes: 1 addition & 5 deletions lib/screens/splashscreen/splashscreen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,7 @@ class _SplashScreenState extends State<SplashScreen>

// Timer to navigate to the next screen after 3 seconds
Timer(const Duration(seconds: 4), () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) =>
const LoginPage()), // Replace with your next screen
);
Navigator.of(context).pushReplacementNamed('/login');
});
}

Expand Down
Loading

0 comments on commit bae716d

Please sign in to comment.