add refresh button to pending page
All checks were successful
Release / meta (push) Successful in 7s
Release / linux-build (push) Successful in 50s
Release / web-build (push) Successful in 2m23s
Release / android-build (push) Successful in 7m48s
Release / release-master (push) Successful in 18s
Release / release-dev (push) Successful in 20s
All checks were successful
Release / meta (push) Successful in 7s
Release / linux-build (push) Successful in 50s
Release / web-build (push) Successful in 2m23s
Release / android-build (push) Successful in 7m48s
Release / release-master (push) Successful in 18s
Release / release-dev (push) Successful in 20s
This commit is contained in:
@@ -18,10 +18,12 @@ class LocoTimelinePage extends StatefulWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
required this.locoId,
|
required this.locoId,
|
||||||
required this.locoLabel,
|
required this.locoLabel,
|
||||||
|
this.forceShowPending = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
final int locoId;
|
final int locoId;
|
||||||
final String locoLabel;
|
final String locoLabel;
|
||||||
|
final bool forceShowPending;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<LocoTimelinePage> createState() => _LocoTimelinePageState();
|
State<LocoTimelinePage> createState() => _LocoTimelinePageState();
|
||||||
@@ -41,7 +43,13 @@ class _LocoTimelinePageState extends State<LocoTimelinePage> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
if (widget.forceShowPending) {
|
||||||
|
setState(() {
|
||||||
|
_showPending = true;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
await _restorePendingVisibility();
|
await _restorePendingVisibility();
|
||||||
|
}
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
await _load();
|
await _load();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ enum _TractionMoreAction {
|
|||||||
classStats,
|
classStats,
|
||||||
classLeaderboard,
|
classLeaderboard,
|
||||||
adminPending,
|
adminPending,
|
||||||
|
adminPendingChanges,
|
||||||
}
|
}
|
||||||
|
|
||||||
class TractionPage extends StatefulWidget {
|
class TractionPage extends StatefulWidget {
|
||||||
@@ -708,6 +709,19 @@ class _TractionPageState extends State<TractionPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case _TractionMoreAction.adminPendingChanges:
|
||||||
|
final messenger = ScaffoldMessenger.of(context);
|
||||||
|
try {
|
||||||
|
await context.push('/traction/changes');
|
||||||
|
} catch (_) {
|
||||||
|
if (!mounted) return;
|
||||||
|
messenger.showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Unable to open pending changes'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
itemBuilder: (context) {
|
itemBuilder: (context) {
|
||||||
@@ -765,6 +779,12 @@ class _TractionPageState extends State<TractionPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
items.add(
|
||||||
|
const PopupMenuItem(
|
||||||
|
value: _TractionMoreAction.adminPendingChanges,
|
||||||
|
child: Text('Pending changes'),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return items;
|
return items;
|
||||||
},
|
},
|
||||||
|
|||||||
152
lib/components/pages/traction/traction_pending_changes_page.dart
Normal file
152
lib/components/pages/traction/traction_pending_changes_page.dart
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:mileograph_flutter/components/traction/traction_card.dart';
|
||||||
|
import 'package:mileograph_flutter/objects/objects.dart';
|
||||||
|
import 'package:mileograph_flutter/services/api_service.dart';
|
||||||
|
import 'package:mileograph_flutter/services/authservice.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class TractionPendingChangesPage extends StatefulWidget {
|
||||||
|
const TractionPendingChangesPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TractionPendingChangesPage> createState() =>
|
||||||
|
_TractionPendingChangesPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TractionPendingChangesPageState extends State<TractionPendingChangesPage> {
|
||||||
|
bool _isLoading = false;
|
||||||
|
String? _error;
|
||||||
|
List<LocoSummary> _locos = const [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => _load());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _load() async {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = true;
|
||||||
|
_error = null;
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
final api = context.read<ApiService>();
|
||||||
|
final json = await api.get('/event/pending/locos');
|
||||||
|
if (json is List) {
|
||||||
|
setState(() {
|
||||||
|
_locos = json
|
||||||
|
.whereType<Map>()
|
||||||
|
.map((e) => LocoSummary.fromJson(
|
||||||
|
e.map((k, v) => MapEntry(k.toString(), v)),
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
_error = 'Unexpected response';
|
||||||
|
_locos = const [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setState(() {
|
||||||
|
_error = e.toString();
|
||||||
|
_locos = const [];
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final isElevated = context.select<AuthService, bool>((auth) => auth.isElevated);
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: () => Navigator.of(context).maybePop(),
|
||||||
|
),
|
||||||
|
title: const Text('Pending changes'),
|
||||||
|
),
|
||||||
|
body: isElevated
|
||||||
|
? RefreshIndicator(
|
||||||
|
onRefresh: _load,
|
||||||
|
child: _buildBody(context),
|
||||||
|
)
|
||||||
|
: ListView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
children: const [
|
||||||
|
Text('Admin access required.'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBody(BuildContext context) {
|
||||||
|
if (_isLoading && _locos.isEmpty) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
if (_error != null) {
|
||||||
|
return ListView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Failed to load pending changes: $_error',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium
|
||||||
|
?.copyWith(color: Theme.of(context).colorScheme.error),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
FilledButton.icon(
|
||||||
|
onPressed: _load,
|
||||||
|
icon: const Icon(Icons.refresh),
|
||||||
|
label: const Text('Retry'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (_locos.isEmpty) {
|
||||||
|
return ListView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
children: const [
|
||||||
|
Text('No pending changes found.'),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ListView.builder(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
itemCount: _locos.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final loco = _locos[index];
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 6.0),
|
||||||
|
child: TractionCard(
|
||||||
|
loco: loco,
|
||||||
|
selectionMode: false,
|
||||||
|
isSelected: false,
|
||||||
|
onShowInfo: () => showTractionDetails(
|
||||||
|
context,
|
||||||
|
loco,
|
||||||
|
onActionComplete: _load,
|
||||||
|
),
|
||||||
|
onOpenTimeline: () => context.push(
|
||||||
|
'/traction/${loco.id}/timeline',
|
||||||
|
extra: {
|
||||||
|
'label': '${loco.locoClass} ${loco.number}'.trim(),
|
||||||
|
'showPending': true,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onOpenLegs: () => context.push('/traction/${loco.id}/legs'),
|
||||||
|
onActionComplete: _load,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ import 'package:mileograph_flutter/components/pages/settings.dart';
|
|||||||
import 'package:mileograph_flutter/components/pages/stats.dart';
|
import 'package:mileograph_flutter/components/pages/stats.dart';
|
||||||
import 'package:mileograph_flutter/components/pages/traction.dart';
|
import 'package:mileograph_flutter/components/pages/traction.dart';
|
||||||
import 'package:mileograph_flutter/components/pages/traction/traction_pending_page.dart';
|
import 'package:mileograph_flutter/components/pages/traction/traction_pending_page.dart';
|
||||||
|
import 'package:mileograph_flutter/components/pages/traction/traction_pending_changes_page.dart';
|
||||||
import 'package:mileograph_flutter/components/pages/more/user_profile_page.dart';
|
import 'package:mileograph_flutter/components/pages/more/user_profile_page.dart';
|
||||||
import 'package:mileograph_flutter/components/widgets/friend_request_notification_card.dart';
|
import 'package:mileograph_flutter/components/widgets/friend_request_notification_card.dart';
|
||||||
import 'package:mileograph_flutter/components/widgets/leg_share_edit_notification_card.dart';
|
import 'package:mileograph_flutter/components/widgets/leg_share_edit_notification_card.dart';
|
||||||
@@ -221,6 +222,11 @@ class _MyAppState extends State<MyApp> {
|
|||||||
path: '/traction/pending',
|
path: '/traction/pending',
|
||||||
builder: (context, state) => const TractionPendingPage(),
|
builder: (context, state) => const TractionPendingPage(),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/traction/changes',
|
||||||
|
builder: (context, state) =>
|
||||||
|
const TractionPendingChangesPage(),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/profile',
|
path: '/profile',
|
||||||
builder: (context, state) => const ProfilePage(),
|
builder: (context, state) => const ProfilePage(),
|
||||||
@@ -238,7 +244,15 @@ class _MyAppState extends State<MyApp> {
|
|||||||
label = extra;
|
label = extra;
|
||||||
}
|
}
|
||||||
if (label.trim().isEmpty) label = 'Loco $locoId';
|
if (label.trim().isEmpty) label = 'Loco $locoId';
|
||||||
return LocoTimelinePage(locoId: locoId, locoLabel: label);
|
bool showPending = false;
|
||||||
|
if (extra is Map && extra['showPending'] is bool) {
|
||||||
|
showPending = extra['showPending'] as bool;
|
||||||
|
}
|
||||||
|
return LocoTimelinePage(
|
||||||
|
locoId: locoId,
|
||||||
|
locoLabel: label,
|
||||||
|
forceShowPending: showPending,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
|
|||||||
Reference in New Issue
Block a user