minor page tweaks

This commit is contained in:
2026-01-12 15:30:29 +00:00
parent 91f5391684
commit 5c0043146f
5 changed files with 337 additions and 92 deletions

View File

@@ -8,6 +8,7 @@ import 'package:mileograph_flutter/objects/objects.dart';
import 'package:mileograph_flutter/services/authservice.dart';
import 'package:mileograph_flutter/services/data_service.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'loco_timeline/timeline_grid.dart';
part 'loco_timeline/event_editor.dart';
@@ -27,15 +28,22 @@ class LocoTimelinePage extends StatefulWidget {
}
class _LocoTimelinePageState extends State<LocoTimelinePage> {
static const String _prefsKeyShowPending = 'timeline_show_pending';
final List<_EventDraft> _draftEvents = [];
bool _isSaving = false;
bool _isDeleting = false;
bool _isModerating = false;
final Set<int> _moderatingEventIds = {};
bool _showPending = true;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _load());
WidgetsBinding.instance.addPostFrameCallback((_) async {
await _restorePendingVisibility();
if (!mounted) return;
await _load();
});
}
dynamic _normalizeFieldValue(_FieldEntry field) {
@@ -65,10 +73,35 @@ class _LocoTimelinePageState extends State<LocoTimelinePage> {
data.fetchEventFields();
return data.fetchLocoTimeline(
widget.locoId,
includeAllPending: auth.isElevated,
includeAllPending: auth.isElevated && _showPending,
);
}
Future<void> _restorePendingVisibility() async {
final auth = context.read<AuthService>();
if (!auth.isElevated) return;
try {
final prefs = await SharedPreferences.getInstance();
final saved = prefs.getBool(_prefsKeyShowPending);
if (saved == null) return;
if (!mounted) return;
setState(() {
_showPending = saved;
});
} catch (_) {
// Ignore preference restore failures.
}
}
Future<void> _persistPendingVisibility(bool value) async {
try {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_prefsKeyShowPending, value);
} catch (_) {
// Ignore persistence failures.
}
}
void _addDraftEvent() {
setState(() {
_draftEvents.add(_EventDraft());
@@ -247,7 +280,6 @@ class _LocoTimelinePageState extends State<LocoTimelinePage> {
LocoAttrVersion entry,
_PendingModerationAction action,
) async {
if (_isModerating) return;
final eventId = entry.sourceEventId;
if (eventId == null) {
ScaffoldMessenger.of(context).showSnackBar(
@@ -257,6 +289,7 @@ class _LocoTimelinePageState extends State<LocoTimelinePage> {
);
return;
}
if (_moderatingEventIds.contains(eventId)) return;
final data = context.read<DataService>();
final approve = action == _PendingModerationAction.approve;
final messenger = ScaffoldMessenger.of(context);
@@ -283,7 +316,7 @@ class _LocoTimelinePageState extends State<LocoTimelinePage> {
if (ok != true || !mounted) return;
setState(() {
_isModerating = true;
_moderatingEventIds.add(eventId);
});
try {
if (approve) {
@@ -310,7 +343,7 @@ class _LocoTimelinePageState extends State<LocoTimelinePage> {
} finally {
if (mounted) {
setState(() {
_isModerating = false;
_moderatingEventIds.remove(eventId);
});
}
}
@@ -499,7 +532,11 @@ class _LocoTimelinePageState extends State<LocoTimelinePage> {
Widget build(BuildContext context) {
final data = context.watch<DataService>();
final timeline = data.timelineForLoco(widget.locoId);
final isElevated = context.select<AuthService, bool>((auth) => auth.isElevated);
final isLoading = data.isLocoTimelineLoading(widget.locoId);
final visibleTimeline = (!isElevated || _showPending)
? timeline
: timeline.where((entry) => !entry.isPending).toList();
return Scaffold(
appBar: AppBar(
@@ -516,7 +553,32 @@ class _LocoTimelinePageState extends State<LocoTimelinePage> {
if (isLoading && timeline.isEmpty) {
return const Center(child: CircularProgressIndicator());
}
if (timeline.isEmpty) {
if (visibleTimeline.isEmpty) {
if (timeline.isNotEmpty && isElevated && !_showPending) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Pending entries hidden',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 8),
const Text(
'Enable "Show pending entries" to view pending timeline blocks.',
),
],
),
),
),
);
}
return Padding(
padding: const EdgeInsets.all(16.0),
child: Card(
@@ -550,15 +612,27 @@ class _LocoTimelinePageState extends State<LocoTimelinePage> {
return ListView(
padding: const EdgeInsets.all(16),
children: [
if (isElevated)
SwitchListTile.adaptive(
contentPadding: EdgeInsets.zero,
title: const Text('Show pending entries'),
value: _showPending,
onChanged: (value) {
setState(() {
_showPending = value;
});
_persistPendingVisibility(value);
},
),
_TimelineGrid(
entries: timeline,
entries: visibleTimeline,
onEditEntry: (entry) => _prefillDraftFromEntry(
entry,
data.eventFields,
),
onDeleteEntry: _deleteEntry,
onModeratePending: _moderatePendingEntry,
pendingActionsBusy: _isModerating,
pendingActionEventIds: _moderatingEventIds,
),
const SizedBox(height: 16),
_EventEditor(