Refresh cached training catalog safely
This commit is contained in:
@@ -16,63 +16,80 @@ class TrainingLibraryScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _TrainingLibraryScreenState extends State<TrainingLibraryScreen> {
|
||||
late Future<TrainingCatalogResult> _plans;
|
||||
final TrainingPlanService _service = Get.find<TrainingPlanService>();
|
||||
TrainingCatalogResult? _result;
|
||||
bool _loading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_plans = Get.find<TrainingPlanService>().fetchPlans();
|
||||
_load();
|
||||
}
|
||||
|
||||
void _reload() => setState(() {
|
||||
_plans = Get.find<TrainingPlanService>().fetchPlans(refresh: true);
|
||||
Future<void> _load() async {
|
||||
final cached = await _service.readCachedPlans();
|
||||
if (!mounted) return;
|
||||
if (cached.isNotEmpty) {
|
||||
setState(() {
|
||||
_result = TrainingCatalogResult(plans: cached, fromCache: true);
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
final result = await _service.fetchPlans(refresh: true);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_result = result;
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _reload() {
|
||||
setState(() => _loading = true);
|
||||
_load();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: AppBar(title: const Text('خطط التمرين')),
|
||||
body: FutureBuilder<TrainingCatalogResult>(
|
||||
future: _plans,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
final result = snapshot.data;
|
||||
if (result == null) {
|
||||
return _CatalogMessage(
|
||||
message: 'تعذر تحميل مكتبة التمارين.',
|
||||
onRetry: _reload,
|
||||
);
|
||||
}
|
||||
if (result.plans.isEmpty) {
|
||||
return _CatalogMessage(
|
||||
message: result.message ??
|
||||
'لا توجد خطط منشورة بعد. ستظهر الخطط هنا بعد إضافتها من لوحة الإدارة.',
|
||||
onRetry: _reload,
|
||||
);
|
||||
}
|
||||
return ListView(
|
||||
padding: const EdgeInsets.all(18),
|
||||
children: [
|
||||
if (result.fromCache)
|
||||
const _CacheNotice(
|
||||
message: 'نعرض نسخة محفوظة على هذا الجهاز.'),
|
||||
if (result.message != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Text(result.message!,
|
||||
style: const TextStyle(color: _muted, height: 1.5)),
|
||||
),
|
||||
for (final plan in result.plans)
|
||||
_PlanCard(
|
||||
plan: plan,
|
||||
onTap: () =>
|
||||
Get.to(() => TrainingPlanDetailScreen(plan: plan)),
|
||||
),
|
||||
],
|
||||
body: Builder(builder: (context) {
|
||||
final result = _result;
|
||||
if (_loading && result == null) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (result == null) {
|
||||
return _CatalogMessage(
|
||||
message: 'تعذر تحميل مكتبة التمارين.',
|
||||
onRetry: _reload,
|
||||
);
|
||||
},
|
||||
),
|
||||
}
|
||||
if (result.plans.isEmpty) {
|
||||
return _CatalogMessage(
|
||||
message: result.message ??
|
||||
'لا توجد خطط منشورة بعد. ستظهر الخطط هنا بعد إضافتها من لوحة الإدارة.',
|
||||
onRetry: _reload,
|
||||
);
|
||||
}
|
||||
return ListView(
|
||||
padding: const EdgeInsets.all(18),
|
||||
children: [
|
||||
if (result.fromCache)
|
||||
const _CacheNotice(message: 'نعرض نسخة محفوظة على هذا الجهاز.'),
|
||||
if (result.message != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Text(result.message!,
|
||||
style: const TextStyle(color: _muted, height: 1.5)),
|
||||
),
|
||||
for (final plan in result.plans)
|
||||
_PlanCard(
|
||||
plan: plan,
|
||||
onTap: () =>
|
||||
Get.to(() => TrainingPlanDetailScreen(plan: plan)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -89,7 +106,7 @@ class _TrainingPlanSpotlightState extends State<TrainingPlanSpotlight> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_plans = Get.find<TrainingPlanService>().fetchPlans();
|
||||
_plans = Get.find<TrainingPlanService>().fetchPlans(refresh: true);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -503,17 +520,17 @@ String _levelLabel(String level) => switch (level) {
|
||||
};
|
||||
|
||||
String _goalLabel(String goal) => switch (goal) {
|
||||
'weight_loss' => 'خفض الوزن',
|
||||
'weight_management' => 'إدارة الوزن',
|
||||
'general_fitness' => 'لياقة عامة',
|
||||
'strength' => 'قوة',
|
||||
'mobility' => 'مرونة وحركة',
|
||||
'endurance' => 'التحمل',
|
||||
_ => goal,
|
||||
};
|
||||
|
||||
String _intensityLabel(String intensity) => switch (intensity) {
|
||||
'easy' => 'خفيفة',
|
||||
'moderate' => 'متوسطة',
|
||||
'hard' => 'عالية',
|
||||
'vigorous' => 'عالية',
|
||||
_ => intensity,
|
||||
};
|
||||
|
||||
|
||||
@@ -15,12 +15,14 @@ class TrainingPlanService {
|
||||
final AuthService _auth;
|
||||
final http.Client _client;
|
||||
|
||||
Future<List<TrainingPlan>> readCachedPlans() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return _decodeCache(prefs.getString(_cacheKey));
|
||||
}
|
||||
|
||||
Future<TrainingCatalogResult> fetchPlans({bool refresh = false}) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final cached = _decodeCache(prefs.getString(_cacheKey));
|
||||
if (!refresh && cached.isNotEmpty) {
|
||||
return TrainingCatalogResult(plans: cached, fromCache: true);
|
||||
}
|
||||
if (!_auth.isApiConfigured) {
|
||||
return TrainingCatalogResult(
|
||||
plans: cached,
|
||||
@@ -46,6 +48,15 @@ class TrainingPlanService {
|
||||
.map(TrainingPlan.fromJson)
|
||||
.where((plan) => plan.uuid.isNotEmpty)
|
||||
.toList(growable: false);
|
||||
// An empty catalog can be an unpublished/temporarily incomplete response.
|
||||
// Keep the last known good catalog rather than erasing offline content.
|
||||
if (plans.isEmpty && cached.isNotEmpty) {
|
||||
return TrainingCatalogResult(
|
||||
plans: cached,
|
||||
fromCache: true,
|
||||
message: 'لا توجد خطط منشورة حاليًا؛ نعرض آخر نسخة محفوظة.',
|
||||
);
|
||||
}
|
||||
await prefs.setString(
|
||||
_cacheKey,
|
||||
jsonEncode(plans.map(_toCacheJson).toList(growable: false)),
|
||||
|
||||
Reference in New Issue
Block a user