import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:url_launcher/url_launcher.dart'; /// Dialog yang menampilkan daftar link transaksi Firefly III dengan tombol /// copy dan open link per baris. Pengganti TransactionLinksScreen. class TransactionLinksDialog extends StatelessWidget { final List urls; final String title; const TransactionLinksDialog({ super.key, required this.urls, this.title = 'Link Transaksi', }); Future _copyLink(BuildContext context, String url) async { await Clipboard.setData(ClipboardData(text: url)); if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Link disalin'), duration: Duration(seconds: 1), ), ); } Future _openLink(BuildContext context, String url) async { try { final uri = Uri.parse(url); final launched = await launchUrl(uri, mode: LaunchMode.externalApplication); if (!launched) { throw 'Tidak dapat membuka URL'; } } catch (e) { if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Gagal membuka link: $e')), ); } } @override Widget build(BuildContext context) { final theme = Theme.of(context); final colorScheme = theme.colorScheme; final isDark = theme.brightness == Brightness.dark; return AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), title: Row( children: [ Icon(Icons.link, color: colorScheme.primary), const SizedBox(width: 8), Expanded( child: Text( title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), ), ], ), content: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 480, maxHeight: 400), child: SizedBox( width: double.maxFinite, child: urls.isEmpty ? Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.link_off, size: 48, color: theme.hintColor), const SizedBox(height: 8), Text('Tidak ada link transaksi', style: TextStyle(color: theme.hintColor)), ], ) : ListView.separated( shrinkWrap: true, itemCount: urls.length, separatorBuilder: (_, __) => Divider( height: 1, color: isDark ? colorScheme.outlineVariant.withOpacity(0.4) : Colors.grey.shade200, ), itemBuilder: (context, index) { final url = urls[index]; return ListTile( dense: true, contentPadding: const EdgeInsets.symmetric(horizontal: 2, vertical: 2), leading: CircleAvatar( radius: 13, backgroundColor: colorScheme.primary.withAlpha(25), child: Text( '${index + 1}', style: TextStyle( fontSize: 11, color: colorScheme.primary, fontWeight: FontWeight.bold, ), ), ), title: Text( url, style: const TextStyle(fontSize: 11.5), maxLines: 1, overflow: TextOverflow.ellipsis, ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: const Icon(Icons.copy, size: 18), padding: const EdgeInsets.all(8), constraints: const BoxConstraints(minWidth: 36, minHeight: 36), tooltip: 'Salin link', onPressed: () => _copyLink(context, url), ), IconButton( icon: const Icon(Icons.open_in_new, size: 18), padding: const EdgeInsets.all(8), constraints: const BoxConstraints(minWidth: 36, minHeight: 36), tooltip: 'Buka di browser', color: colorScheme.primary, onPressed: () => _openLink(context, url), ), ], ), ); }, ), ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Tutup'), ), ], ); } }