fix edit widget issues
This commit is contained in:
@@ -19,6 +19,8 @@ class _LegShareEditNotificationCardState extends State<LegShareEditNotificationC
|
||||
int? _legId;
|
||||
int? _shareId;
|
||||
Leg? _currentLeg;
|
||||
LegShareData? _share;
|
||||
Future<LegShareData?>? _shareFuture;
|
||||
bool _loading = false;
|
||||
|
||||
static const int _summaryLimit = 3;
|
||||
@@ -33,11 +35,11 @@ class _LegShareEditNotificationCardState extends State<LegShareEditNotificationC
|
||||
void didUpdateWidget(covariant LegShareEditNotificationCard oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.notification != widget.notification) {
|
||||
_parseNotification();
|
||||
_parseNotification(notify: true);
|
||||
}
|
||||
}
|
||||
|
||||
void _parseNotification() {
|
||||
void _parseNotification({bool notify = false}) {
|
||||
final rawBody = widget.notification.body.trim();
|
||||
|
||||
// Reset
|
||||
@@ -45,11 +47,14 @@ class _LegShareEditNotificationCardState extends State<LegShareEditNotificationC
|
||||
_legId = null;
|
||||
_currentLeg = null;
|
||||
_changes = null;
|
||||
_share = null;
|
||||
_shareFuture = null;
|
||||
|
||||
final parsed = _decodeBody(rawBody);
|
||||
if (parsed != null) {
|
||||
_shareId = _parseInt(parsed['share_id']);
|
||||
_legId = _parseInt(parsed['leg_id']);
|
||||
_currentLeg = _findCurrentLeg(_legId);
|
||||
final accepted = _asStringKeyedMap(parsed['accepted_changes']);
|
||||
if (accepted != null) {
|
||||
_changes = accepted;
|
||||
@@ -58,6 +63,10 @@ class _LegShareEditNotificationCardState extends State<LegShareEditNotificationC
|
||||
|
||||
// Fallback: extract share_id from raw string if still missing.
|
||||
_shareId ??= _extractShareId(rawBody);
|
||||
_prepareShareFuture();
|
||||
if (notify) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
int? _parseInt(dynamic value) {
|
||||
@@ -164,6 +173,25 @@ class _LegShareEditNotificationCardState extends State<LegShareEditNotificationC
|
||||
}
|
||||
}
|
||||
|
||||
void _prepareShareFuture() {
|
||||
if (_shareId == null) return;
|
||||
_shareFuture = context.read<DataService>().fetchLegShare(_shareId!.toString());
|
||||
}
|
||||
|
||||
Future<void> _loadShareIfNeeded() async {
|
||||
if (_share != null) return;
|
||||
if (_shareId == null) return;
|
||||
try {
|
||||
final future = _shareFuture ??
|
||||
context.read<DataService>().fetchLegShare(_shareId!.toString());
|
||||
final share = await future;
|
||||
if (!mounted) return;
|
||||
_share = share;
|
||||
} catch (e) {
|
||||
// ignore: avoid_empty_catches
|
||||
}
|
||||
}
|
||||
|
||||
Leg? _findCurrentLeg(int? legId) {
|
||||
if (legId == null) return null;
|
||||
final data = context.read<DataService>();
|
||||
@@ -174,6 +202,79 @@ class _LegShareEditNotificationCardState extends State<LegShareEditNotificationC
|
||||
}
|
||||
}
|
||||
|
||||
String _formatDateTime(DateTime dateTime) {
|
||||
return '${dateTime.year.toString().padLeft(4, '0')}-'
|
||||
'${dateTime.month.toString().padLeft(2, '0')}-'
|
||||
'${dateTime.day.toString().padLeft(2, '0')} '
|
||||
'${dateTime.hour.toString().padLeft(2, '0')}:'
|
||||
'${dateTime.minute.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
Widget _buildLegSummary(BuildContext context) {
|
||||
final future = _shareFuture;
|
||||
if (future == null) {
|
||||
final leg = _currentLeg;
|
||||
return leg == null ? const SizedBox.shrink() : _legSummaryRow(context, leg);
|
||||
}
|
||||
|
||||
return FutureBuilder<LegShareData?>(
|
||||
future: future,
|
||||
builder: (context, snapshot) {
|
||||
final share = snapshot.data;
|
||||
if (share != null) {
|
||||
_share = share;
|
||||
}
|
||||
final leg = share?.entry ?? _currentLeg;
|
||||
if (leg == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return _legSummaryRow(context, leg);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _legSummaryRow(BuildContext context, Leg leg) {
|
||||
final start = leg.route.isNotEmpty ? leg.route.first : leg.start;
|
||||
final end = leg.route.isNotEmpty ? leg.route.last : leg.end;
|
||||
return Text('${_formatDateTime(leg.beginTime)} • $start → $end');
|
||||
}
|
||||
|
||||
String _asString(dynamic value) {
|
||||
if (value == null) return '';
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
Loco? _resolveLocoById(int locoId, {Leg? shareLeg}) {
|
||||
for (final loco in shareLeg?.locos ?? const <Loco>[]) {
|
||||
if (loco.id == locoId) return loco;
|
||||
}
|
||||
for (final loco in _currentLeg?.locos ?? const <Loco>[]) {
|
||||
if (loco.id == locoId) return loco;
|
||||
}
|
||||
for (final loco in context.read<DataService>().traction) {
|
||||
if (loco.id == locoId) return loco;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String _locoDisplayName(Map<String, dynamic> loco, {Leg? shareLeg}) {
|
||||
final locoId = _parseInt(loco['loco_id']);
|
||||
var locoClass = _asString(loco['class'] ?? loco['loco_class']);
|
||||
var number = _asString(loco['number'] ?? loco['loco_number']);
|
||||
if ((locoClass.isEmpty || number.isEmpty) && locoId != null) {
|
||||
final resolved = _resolveLocoById(locoId, shareLeg: shareLeg);
|
||||
if (resolved != null) {
|
||||
if (locoClass.isEmpty) locoClass = resolved.locoClass;
|
||||
if (number.isEmpty) number = resolved.number;
|
||||
}
|
||||
}
|
||||
final parts = <String>[];
|
||||
if (locoClass.isNotEmpty) parts.add(locoClass);
|
||||
if (number.isNotEmpty) parts.add(number);
|
||||
if (parts.isNotEmpty) return parts.join(' ');
|
||||
return 'Loco ${locoId ?? '?'}';
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -184,10 +285,14 @@ class _LegShareEditNotificationCardState extends State<LegShareEditNotificationC
|
||||
final entries = changes.entries.toList();
|
||||
final shown = entries.take(_summaryLimit).toList();
|
||||
final remaining = entries.length - shown.length;
|
||||
final legSummary = _buildLegSummary(context);
|
||||
final hasSummary = _shareFuture != null || _currentLeg != null;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (hasSummary) legSummary,
|
||||
if (hasSummary) const SizedBox(height: 8),
|
||||
...shown.map((e) => _changePreview(context, e)),
|
||||
if (remaining > 0)
|
||||
Padding(
|
||||
@@ -325,6 +430,7 @@ class _LegShareEditNotificationCardState extends State<LegShareEditNotificationC
|
||||
Future<void> _openDrawer(Map<String, dynamic> changes) async {
|
||||
setState(() => _loading = true);
|
||||
await _loadLegIdIfNeeded();
|
||||
await _loadShareIfNeeded();
|
||||
_currentLeg ??= _findCurrentLeg(_legId);
|
||||
if (!mounted) return;
|
||||
setState(() => _loading = false);
|
||||
@@ -398,7 +504,9 @@ class _LegShareEditNotificationCardState extends State<LegShareEditNotificationC
|
||||
(loco['alloc_powering'] == 1 || loco['alloc_powering'] == true)
|
||||
? Theme.of(context).colorScheme.primary.withValues(alpha: 0.12)
|
||||
: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
label: Text('Loco ${loco['loco_id'] ?? '?'} (pos ${loco['alloc_pos'] ?? '?'}'),
|
||||
label: Text(
|
||||
'${_locoDisplayName(loco, shareLeg: _share?.entry)} (pos ${loco['alloc_pos'] ?? '?'})',
|
||||
),
|
||||
avatar: Icon(
|
||||
Icons.train,
|
||||
size: 16,
|
||||
|
||||
@@ -7,15 +7,23 @@ import 'package:mileograph_flutter/objects/objects.dart';
|
||||
import 'package:mileograph_flutter/services/data_service.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class LegShareNotificationCard extends StatelessWidget {
|
||||
class LegShareNotificationCard extends StatefulWidget {
|
||||
const LegShareNotificationCard({super.key, required this.notification});
|
||||
|
||||
final UserNotification notification;
|
||||
|
||||
@override
|
||||
State<LegShareNotificationCard> createState() => _LegShareNotificationCardState();
|
||||
}
|
||||
|
||||
class _LegShareNotificationCardState extends State<LegShareNotificationCard> {
|
||||
bool _accepting = false;
|
||||
bool _rejecting = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final data = context.read<DataService>();
|
||||
final legShareId = _extractLegShareId(notification.body);
|
||||
final legShareId = _extractLegShareId(widget.notification.body);
|
||||
if (legShareId == null) {
|
||||
return const Text('Invalid leg share notification.');
|
||||
}
|
||||
@@ -78,16 +86,28 @@ class LegShareNotificationCard extends StatelessWidget {
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () => _accept(context, share),
|
||||
child: const Text('Accept'),
|
||||
onPressed: _accepting ? null : () => _accept(context, share),
|
||||
child: _accepting
|
||||
? const SizedBox(
|
||||
height: 18,
|
||||
width: 18,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Text('Accept'),
|
||||
),
|
||||
OutlinedButton(
|
||||
onPressed: () => _inspect(context, share),
|
||||
child: const Text('Inspect'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => _reject(context, share),
|
||||
child: const Text('Reject'),
|
||||
onPressed: _rejecting ? null : () => _reject(context, share),
|
||||
child: _rejecting
|
||||
? const SizedBox(
|
||||
height: 18,
|
||||
width: 18,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Text('Reject'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -98,12 +118,13 @@ class LegShareNotificationCard extends StatelessWidget {
|
||||
}
|
||||
|
||||
Future<void> _accept(BuildContext context, LegShareData share) async {
|
||||
setState(() => _accepting = true);
|
||||
final data = context.read<DataService>();
|
||||
final messenger = ScaffoldMessenger.maybeOf(context);
|
||||
try {
|
||||
await data.acceptLegShare(share);
|
||||
if (!context.mounted) return;
|
||||
await data.dismissNotifications([notification.id]);
|
||||
await data.dismissNotifications([widget.notification.id]);
|
||||
// Refresh legs in the background.
|
||||
unawaited(data.refreshLegs());
|
||||
messenger?.showSnackBar(
|
||||
@@ -113,16 +134,21 @@ class LegShareNotificationCard extends StatelessWidget {
|
||||
messenger?.showSnackBar(
|
||||
SnackBar(content: Text('Failed to add shared entry: $e')),
|
||||
);
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => _accepting = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _reject(BuildContext context, LegShareData share) async {
|
||||
setState(() => _rejecting = true);
|
||||
final data = context.read<DataService>();
|
||||
final messenger = ScaffoldMessenger.maybeOf(context);
|
||||
try {
|
||||
await data.rejectLegShare(share.id);
|
||||
if (!context.mounted) return;
|
||||
await data.dismissNotifications([notification.id]);
|
||||
await data.dismissNotifications([widget.notification.id]);
|
||||
messenger?.showSnackBar(
|
||||
const SnackBar(content: Text('Share rejected')),
|
||||
);
|
||||
@@ -130,6 +156,10 @@ class LegShareNotificationCard extends StatelessWidget {
|
||||
messenger?.showSnackBar(
|
||||
SnackBar(content: Text('Failed to reject share: $e')),
|
||||
);
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => _rejecting = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +170,7 @@ class LegShareNotificationCard extends StatelessWidget {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
final target = share.copyWith(notificationId: notification.id);
|
||||
final target = share.copyWith(notificationId: widget.notification.id);
|
||||
final ts = DateTime.now().millisecondsSinceEpoch;
|
||||
final path = '/add?share=${Uri.encodeComponent(share.id)}&ts=$ts';
|
||||
router.go(path, extra: target);
|
||||
|
||||
@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 0.7.2+10
|
||||
version: 0.7.3+11
|
||||
|
||||
environment:
|
||||
sdk: ^3.8.1
|
||||
|
||||
Reference in New Issue
Block a user