fix edit widget issues
This commit is contained in:
@@ -19,6 +19,8 @@ class _LegShareEditNotificationCardState extends State<LegShareEditNotificationC
|
|||||||
int? _legId;
|
int? _legId;
|
||||||
int? _shareId;
|
int? _shareId;
|
||||||
Leg? _currentLeg;
|
Leg? _currentLeg;
|
||||||
|
LegShareData? _share;
|
||||||
|
Future<LegShareData?>? _shareFuture;
|
||||||
bool _loading = false;
|
bool _loading = false;
|
||||||
|
|
||||||
static const int _summaryLimit = 3;
|
static const int _summaryLimit = 3;
|
||||||
@@ -33,11 +35,11 @@ class _LegShareEditNotificationCardState extends State<LegShareEditNotificationC
|
|||||||
void didUpdateWidget(covariant LegShareEditNotificationCard oldWidget) {
|
void didUpdateWidget(covariant LegShareEditNotificationCard oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (oldWidget.notification != widget.notification) {
|
if (oldWidget.notification != widget.notification) {
|
||||||
_parseNotification();
|
_parseNotification(notify: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _parseNotification() {
|
void _parseNotification({bool notify = false}) {
|
||||||
final rawBody = widget.notification.body.trim();
|
final rawBody = widget.notification.body.trim();
|
||||||
|
|
||||||
// Reset
|
// Reset
|
||||||
@@ -45,11 +47,14 @@ class _LegShareEditNotificationCardState extends State<LegShareEditNotificationC
|
|||||||
_legId = null;
|
_legId = null;
|
||||||
_currentLeg = null;
|
_currentLeg = null;
|
||||||
_changes = null;
|
_changes = null;
|
||||||
|
_share = null;
|
||||||
|
_shareFuture = null;
|
||||||
|
|
||||||
final parsed = _decodeBody(rawBody);
|
final parsed = _decodeBody(rawBody);
|
||||||
if (parsed != null) {
|
if (parsed != null) {
|
||||||
_shareId = _parseInt(parsed['share_id']);
|
_shareId = _parseInt(parsed['share_id']);
|
||||||
_legId = _parseInt(parsed['leg_id']);
|
_legId = _parseInt(parsed['leg_id']);
|
||||||
|
_currentLeg = _findCurrentLeg(_legId);
|
||||||
final accepted = _asStringKeyedMap(parsed['accepted_changes']);
|
final accepted = _asStringKeyedMap(parsed['accepted_changes']);
|
||||||
if (accepted != null) {
|
if (accepted != null) {
|
||||||
_changes = accepted;
|
_changes = accepted;
|
||||||
@@ -58,6 +63,10 @@ class _LegShareEditNotificationCardState extends State<LegShareEditNotificationC
|
|||||||
|
|
||||||
// Fallback: extract share_id from raw string if still missing.
|
// Fallback: extract share_id from raw string if still missing.
|
||||||
_shareId ??= _extractShareId(rawBody);
|
_shareId ??= _extractShareId(rawBody);
|
||||||
|
_prepareShareFuture();
|
||||||
|
if (notify) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int? _parseInt(dynamic value) {
|
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) {
|
Leg? _findCurrentLeg(int? legId) {
|
||||||
if (legId == null) return null;
|
if (legId == null) return null;
|
||||||
final data = context.read<DataService>();
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -184,10 +285,14 @@ class _LegShareEditNotificationCardState extends State<LegShareEditNotificationC
|
|||||||
final entries = changes.entries.toList();
|
final entries = changes.entries.toList();
|
||||||
final shown = entries.take(_summaryLimit).toList();
|
final shown = entries.take(_summaryLimit).toList();
|
||||||
final remaining = entries.length - shown.length;
|
final remaining = entries.length - shown.length;
|
||||||
|
final legSummary = _buildLegSummary(context);
|
||||||
|
final hasSummary = _shareFuture != null || _currentLeg != null;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
if (hasSummary) legSummary,
|
||||||
|
if (hasSummary) const SizedBox(height: 8),
|
||||||
...shown.map((e) => _changePreview(context, e)),
|
...shown.map((e) => _changePreview(context, e)),
|
||||||
if (remaining > 0)
|
if (remaining > 0)
|
||||||
Padding(
|
Padding(
|
||||||
@@ -325,6 +430,7 @@ class _LegShareEditNotificationCardState extends State<LegShareEditNotificationC
|
|||||||
Future<void> _openDrawer(Map<String, dynamic> changes) async {
|
Future<void> _openDrawer(Map<String, dynamic> changes) async {
|
||||||
setState(() => _loading = true);
|
setState(() => _loading = true);
|
||||||
await _loadLegIdIfNeeded();
|
await _loadLegIdIfNeeded();
|
||||||
|
await _loadShareIfNeeded();
|
||||||
_currentLeg ??= _findCurrentLeg(_legId);
|
_currentLeg ??= _findCurrentLeg(_legId);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() => _loading = false);
|
setState(() => _loading = false);
|
||||||
@@ -398,7 +504,9 @@ class _LegShareEditNotificationCardState extends State<LegShareEditNotificationC
|
|||||||
(loco['alloc_powering'] == 1 || loco['alloc_powering'] == true)
|
(loco['alloc_powering'] == 1 || loco['alloc_powering'] == true)
|
||||||
? Theme.of(context).colorScheme.primary.withValues(alpha: 0.12)
|
? Theme.of(context).colorScheme.primary.withValues(alpha: 0.12)
|
||||||
: Theme.of(context).colorScheme.surfaceContainerHighest,
|
: 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(
|
avatar: Icon(
|
||||||
Icons.train,
|
Icons.train,
|
||||||
size: 16,
|
size: 16,
|
||||||
|
|||||||
@@ -7,15 +7,23 @@ import 'package:mileograph_flutter/objects/objects.dart';
|
|||||||
import 'package:mileograph_flutter/services/data_service.dart';
|
import 'package:mileograph_flutter/services/data_service.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class LegShareNotificationCard extends StatelessWidget {
|
class LegShareNotificationCard extends StatefulWidget {
|
||||||
const LegShareNotificationCard({super.key, required this.notification});
|
const LegShareNotificationCard({super.key, required this.notification});
|
||||||
|
|
||||||
final UserNotification notification;
|
final UserNotification notification;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LegShareNotificationCard> createState() => _LegShareNotificationCardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LegShareNotificationCardState extends State<LegShareNotificationCard> {
|
||||||
|
bool _accepting = false;
|
||||||
|
bool _rejecting = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final data = context.read<DataService>();
|
final data = context.read<DataService>();
|
||||||
final legShareId = _extractLegShareId(notification.body);
|
final legShareId = _extractLegShareId(widget.notification.body);
|
||||||
if (legShareId == null) {
|
if (legShareId == null) {
|
||||||
return const Text('Invalid leg share notification.');
|
return const Text('Invalid leg share notification.');
|
||||||
}
|
}
|
||||||
@@ -78,16 +86,28 @@ class LegShareNotificationCard extends StatelessWidget {
|
|||||||
runSpacing: 8,
|
runSpacing: 8,
|
||||||
children: [
|
children: [
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () => _accept(context, share),
|
onPressed: _accepting ? null : () => _accept(context, share),
|
||||||
child: const Text('Accept'),
|
child: _accepting
|
||||||
|
? const SizedBox(
|
||||||
|
height: 18,
|
||||||
|
width: 18,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
|
)
|
||||||
|
: const Text('Accept'),
|
||||||
),
|
),
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onPressed: () => _inspect(context, share),
|
onPressed: () => _inspect(context, share),
|
||||||
child: const Text('Inspect'),
|
child: const Text('Inspect'),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => _reject(context, share),
|
onPressed: _rejecting ? null : () => _reject(context, share),
|
||||||
child: const Text('Reject'),
|
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 {
|
Future<void> _accept(BuildContext context, LegShareData share) async {
|
||||||
|
setState(() => _accepting = true);
|
||||||
final data = context.read<DataService>();
|
final data = context.read<DataService>();
|
||||||
final messenger = ScaffoldMessenger.maybeOf(context);
|
final messenger = ScaffoldMessenger.maybeOf(context);
|
||||||
try {
|
try {
|
||||||
await data.acceptLegShare(share);
|
await data.acceptLegShare(share);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
await data.dismissNotifications([notification.id]);
|
await data.dismissNotifications([widget.notification.id]);
|
||||||
// Refresh legs in the background.
|
// Refresh legs in the background.
|
||||||
unawaited(data.refreshLegs());
|
unawaited(data.refreshLegs());
|
||||||
messenger?.showSnackBar(
|
messenger?.showSnackBar(
|
||||||
@@ -113,16 +134,21 @@ class LegShareNotificationCard extends StatelessWidget {
|
|||||||
messenger?.showSnackBar(
|
messenger?.showSnackBar(
|
||||||
SnackBar(content: Text('Failed to add shared entry: $e')),
|
SnackBar(content: Text('Failed to add shared entry: $e')),
|
||||||
);
|
);
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() => _accepting = false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _reject(BuildContext context, LegShareData share) async {
|
Future<void> _reject(BuildContext context, LegShareData share) async {
|
||||||
|
setState(() => _rejecting = true);
|
||||||
final data = context.read<DataService>();
|
final data = context.read<DataService>();
|
||||||
final messenger = ScaffoldMessenger.maybeOf(context);
|
final messenger = ScaffoldMessenger.maybeOf(context);
|
||||||
try {
|
try {
|
||||||
await data.rejectLegShare(share.id);
|
await data.rejectLegShare(share.id);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
await data.dismissNotifications([notification.id]);
|
await data.dismissNotifications([widget.notification.id]);
|
||||||
messenger?.showSnackBar(
|
messenger?.showSnackBar(
|
||||||
const SnackBar(content: Text('Share rejected')),
|
const SnackBar(content: Text('Share rejected')),
|
||||||
);
|
);
|
||||||
@@ -130,6 +156,10 @@ class LegShareNotificationCard extends StatelessWidget {
|
|||||||
messenger?.showSnackBar(
|
messenger?.showSnackBar(
|
||||||
SnackBar(content: Text('Failed to reject share: $e')),
|
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();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
await Future<void>.delayed(Duration.zero);
|
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 ts = DateTime.now().millisecondsSinceEpoch;
|
||||||
final path = '/add?share=${Uri.encodeComponent(share.id)}&ts=$ts';
|
final path = '/add?share=${Uri.encodeComponent(share.id)}&ts=$ts';
|
||||||
router.go(path, extra: target);
|
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
|
# 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
|
# 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.
|
# 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:
|
environment:
|
||||||
sdk: ^3.8.1
|
sdk: ^3.8.1
|
||||||
|
|||||||
Reference in New Issue
Block a user